@aws-amplify/datastore 3.12.6-next.13 → 3.12.6-next.32

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 (162) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/lib/authModeStrategies/multiAuthStrategy.js +17 -64
  3. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  4. package/lib/datastore/datastore.js +682 -469
  5. package/lib/datastore/datastore.js.map +1 -1
  6. package/lib/index.js +2 -4
  7. package/lib/index.js.map +1 -1
  8. package/lib/predicates/index.js +12 -2
  9. package/lib/predicates/index.js.map +1 -1
  10. package/lib/storage/adapter/AsyncStorageAdapter.js +393 -298
  11. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  12. package/lib/storage/adapter/AsyncStorageDatabase.js +97 -122
  13. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  14. package/lib/storage/adapter/InMemoryStore.js +16 -67
  15. package/lib/storage/adapter/InMemoryStore.js.map +1 -1
  16. package/lib/storage/adapter/InMemoryStore.native.js +2 -4
  17. package/lib/storage/adapter/InMemoryStore.native.js.map +1 -1
  18. package/lib/storage/adapter/IndexedDBAdapter.js +497 -404
  19. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  20. package/lib/storage/adapter/getDefaultAdapter/index.js +3 -5
  21. package/lib/storage/adapter/getDefaultAdapter/index.js.map +1 -1
  22. package/lib/storage/adapter/getDefaultAdapter/index.native.js +2 -4
  23. package/lib/storage/adapter/getDefaultAdapter/index.native.js.map +1 -1
  24. package/lib/storage/storage.js +129 -151
  25. package/lib/storage/storage.js.map +1 -1
  26. package/lib/sync/datastoreConnectivity.js +13 -17
  27. package/lib/sync/datastoreConnectivity.js.map +1 -1
  28. package/lib/sync/datastoreReachability/index.native.js +2 -4
  29. package/lib/sync/datastoreReachability/index.native.js.map +1 -1
  30. package/lib/sync/index.js +544 -488
  31. package/lib/sync/index.js.map +1 -1
  32. package/lib/sync/merger.js +21 -80
  33. package/lib/sync/merger.js.map +1 -1
  34. package/lib/sync/outbox.js +95 -162
  35. package/lib/sync/outbox.js.map +1 -1
  36. package/lib/sync/processors/errorMaps.js +4 -34
  37. package/lib/sync/processors/errorMaps.js.map +1 -1
  38. package/lib/sync/processors/mutation.js +285 -312
  39. package/lib/sync/processors/mutation.js.map +1 -1
  40. package/lib/sync/processors/subscription.js +218 -259
  41. package/lib/sync/processors/subscription.js.map +1 -1
  42. package/lib/sync/processors/sync.js +141 -212
  43. package/lib/sync/processors/sync.js.map +1 -1
  44. package/lib/sync/utils.js +50 -61
  45. package/lib/sync/utils.js.map +1 -1
  46. package/lib/types.js +13 -39
  47. package/lib/types.js.map +1 -1
  48. package/lib/util.js +429 -242
  49. package/lib/util.js.map +1 -1
  50. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +11 -0
  51. package/lib-esm/authModeStrategies/multiAuthStrategy.js +13 -57
  52. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  53. package/lib-esm/datastore/datastore.d.ts +107 -17
  54. package/lib-esm/datastore/datastore.js +649 -433
  55. package/lib-esm/datastore/datastore.js.map +1 -1
  56. package/lib-esm/index.d.ts +3 -19
  57. package/lib-esm/predicates/index.d.ts +3 -2
  58. package/lib-esm/predicates/index.js +13 -3
  59. package/lib-esm/predicates/index.js.map +1 -1
  60. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  61. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +356 -258
  62. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  64. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +67 -92
  65. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  66. package/lib-esm/storage/adapter/InMemoryStore.js +1 -52
  67. package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
  68. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +26 -4
  69. package/lib-esm/storage/adapter/IndexedDBAdapter.js +446 -346
  70. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  71. package/lib-esm/storage/adapter/index.d.ts +1 -1
  72. package/lib-esm/storage/storage.d.ts +1 -1
  73. package/lib-esm/storage/storage.js +94 -113
  74. package/lib-esm/storage/storage.js.map +1 -1
  75. package/lib-esm/sync/datastoreConnectivity.d.ts +1 -0
  76. package/lib-esm/sync/datastoreConnectivity.js +10 -11
  77. package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
  78. package/lib-esm/sync/index.d.ts +31 -5
  79. package/lib-esm/sync/index.js +525 -466
  80. package/lib-esm/sync/index.js.map +1 -1
  81. package/lib-esm/sync/merger.d.ts +9 -3
  82. package/lib-esm/sync/merger.js +14 -73
  83. package/lib-esm/sync/merger.js.map +1 -1
  84. package/lib-esm/sync/outbox.d.ts +2 -2
  85. package/lib-esm/sync/outbox.js +79 -146
  86. package/lib-esm/sync/outbox.js.map +1 -1
  87. package/lib-esm/sync/processors/errorMaps.js +1 -31
  88. package/lib-esm/sync/processors/errorMaps.js.map +1 -1
  89. package/lib-esm/sync/processors/mutation.d.ts +2 -0
  90. package/lib-esm/sync/processors/mutation.js +271 -295
  91. package/lib-esm/sync/processors/mutation.js.map +1 -1
  92. package/lib-esm/sync/processors/subscription.d.ts +2 -0
  93. package/lib-esm/sync/processors/subscription.js +214 -245
  94. package/lib-esm/sync/processors/subscription.js.map +1 -1
  95. package/lib-esm/sync/processors/sync.d.ts +2 -1
  96. package/lib-esm/sync/processors/sync.js +127 -195
  97. package/lib-esm/sync/processors/sync.js.map +1 -1
  98. package/lib-esm/sync/utils.d.ts +3 -2
  99. package/lib-esm/sync/utils.js +45 -57
  100. package/lib-esm/sync/utils.js.map +1 -1
  101. package/lib-esm/types.d.ts +65 -26
  102. package/lib-esm/types.js +10 -38
  103. package/lib-esm/types.js.map +1 -1
  104. package/lib-esm/util.d.ts +67 -24
  105. package/lib-esm/util.js +420 -233
  106. package/lib-esm/util.js.map +1 -1
  107. package/package.json +14 -7
  108. package/src/authModeStrategies/multiAuthStrategy.ts +12 -1
  109. package/src/datastore/datastore.ts +798 -397
  110. package/src/predicates/index.ts +32 -10
  111. package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
  112. package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
  113. package/src/storage/adapter/IndexedDBAdapter.ts +358 -134
  114. package/src/storage/adapter/index.ts +1 -1
  115. package/src/storage/storage.ts +69 -22
  116. package/src/sync/datastoreConnectivity.ts +6 -0
  117. package/src/sync/index.ts +521 -412
  118. package/src/sync/merger.ts +20 -4
  119. package/src/sync/outbox.ts +22 -9
  120. package/src/sync/processors/mutation.ts +188 -150
  121. package/src/sync/processors/subscription.ts +289 -253
  122. package/src/sync/processors/sync.ts +151 -138
  123. package/src/sync/utils.ts +67 -12
  124. package/src/types.ts +182 -30
  125. package/src/util.ts +505 -176
  126. package/build.js +0 -5
  127. package/dist/aws-amplify-datastore.js +0 -98255
  128. package/dist/aws-amplify-datastore.js.map +0 -1
  129. package/dist/aws-amplify-datastore.min.js +0 -66
  130. package/dist/aws-amplify-datastore.min.js.map +0 -1
  131. package/index.js +0 -7
  132. package/lib/authModeStrategies/defaultAuthStrategy.d.ts +0 -2
  133. package/lib/authModeStrategies/index.d.ts +0 -2
  134. package/lib/authModeStrategies/multiAuthStrategy.d.ts +0 -2
  135. package/lib/datastore/datastore.d.ts +0 -66
  136. package/lib/index.d.ts +0 -31
  137. package/lib/predicates/index.d.ts +0 -15
  138. package/lib/predicates/sort.d.ts +0 -8
  139. package/lib/ssr/index.d.ts +0 -3
  140. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +0 -40
  141. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +0 -29
  142. package/lib/storage/adapter/InMemoryStore.d.ts +0 -11
  143. package/lib/storage/adapter/InMemoryStore.native.d.ts +0 -1
  144. package/lib/storage/adapter/IndexedDBAdapter.d.ts +0 -37
  145. package/lib/storage/adapter/getDefaultAdapter/index.d.ts +0 -3
  146. package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +0 -3
  147. package/lib/storage/adapter/index.d.ts +0 -9
  148. package/lib/storage/storage.d.ts +0 -49
  149. package/lib/sync/datastoreConnectivity.d.ts +0 -15
  150. package/lib/sync/datastoreReachability/index.d.ts +0 -3
  151. package/lib/sync/datastoreReachability/index.native.d.ts +0 -3
  152. package/lib/sync/index.d.ts +0 -63
  153. package/lib/sync/merger.d.ts +0 -11
  154. package/lib/sync/outbox.d.ts +0 -27
  155. package/lib/sync/processors/errorMaps.d.ts +0 -17
  156. package/lib/sync/processors/mutation.d.ts +0 -56
  157. package/lib/sync/processors/subscription.d.ts +0 -31
  158. package/lib/sync/processors/sync.d.ts +0 -27
  159. package/lib/sync/utils.d.ts +0 -41
  160. package/lib/types.d.ts +0 -462
  161. package/lib/util.d.ts +0 -113
  162. package/webpack.config.dev.js +0 -6
@@ -1,7 +1,13 @@
1
- import API from '@aws-amplify/api';
2
- import { Amplify, ConsoleLogger as Logger, Hub, JS } from '@aws-amplify/core';
1
+ import { API } from '@aws-amplify/api';
3
2
  import { Auth } from '@aws-amplify/auth';
4
- import Cache from '@aws-amplify/cache';
3
+ import { BrowserStorageCache as Cache } from '@aws-amplify/cache';
4
+ import {
5
+ Amplify,
6
+ ConsoleLogger as Logger,
7
+ Hub,
8
+ browserOrNode,
9
+ BackgroundProcessManager,
10
+ } from '@aws-amplify/core';
5
11
  import {
6
12
  Draft,
7
13
  immerable,
@@ -29,6 +35,7 @@ import {
29
35
  GraphQLScalarType,
30
36
  InternalSchema,
31
37
  isGraphQLScalarType,
38
+ isSchemaModelWithAttributes,
32
39
  ModelFieldType,
33
40
  ModelInit,
34
41
  ModelInstanceMetadata,
@@ -57,10 +64,18 @@ import {
57
64
  isNonModelFieldType,
58
65
  isModelFieldType,
59
66
  ObserveQueryOptions,
67
+ ManagedIdentifier,
68
+ PersistentModelMetaData,
69
+ IdentifierFieldOrIdentifierObject,
70
+ isIdentifierObject,
60
71
  AmplifyContext,
61
72
  } from '../types';
73
+ // tslint:disable:no-duplicate-imports
74
+ import type { __modelMeta__ } from '../types';
75
+
62
76
  import {
63
77
  DATASTORE,
78
+ errorMessages,
64
79
  establishRelationAndKeys,
65
80
  exhaustiveCheck,
66
81
  isModelConstructor,
@@ -73,9 +88,15 @@ import {
73
88
  registerNonModelClass,
74
89
  sortCompareFunction,
75
90
  DeferredCallbackResolver,
91
+ extractPrimaryKeyFieldNames,
92
+ extractPrimaryKeysAndValues,
93
+ isIdManaged,
94
+ isIdOptionallyManaged,
76
95
  validatePredicate,
77
96
  mergePatches,
78
97
  } from '../util';
98
+ import { getIdentifierValue } from '../sync/utils';
99
+ import DataStoreConnectivity from '../sync/datastoreConnectivity';
79
100
 
80
101
  setAutoFreeze(true);
81
102
  enablePatches();
@@ -83,13 +104,18 @@ enablePatches();
83
104
  const logger = new Logger('DataStore');
84
105
 
85
106
  const ulid = monotonicUlidFactory(Date.now());
86
- const { isNode } = JS.browserOrNode();
107
+ const { isNode } = browserOrNode();
87
108
 
109
+ type SettingMetaData = {
110
+ identifier: ManagedIdentifier<Setting, 'id'>;
111
+ readOnlyFields: never;
112
+ };
88
113
  declare class Setting {
89
- constructor(init: ModelInit<Setting>);
114
+ public readonly [__modelMeta__]: SettingMetaData;
115
+ constructor(init: ModelInit<Setting, SettingMetaData>);
90
116
  static copyOf(
91
117
  src: Setting,
92
- mutator: (draft: MutableModel<Setting>) => void | Setting
118
+ mutator: (draft: MutableModel<Setting, SettingMetaData>) => void | Setting
93
119
  ): Setting;
94
120
  public readonly id: string;
95
121
  public readonly key: string;
@@ -228,10 +254,14 @@ const initSchema = (userSchema: Schema) => {
228
254
  return userClasses;
229
255
  };
230
256
 
231
- /* Checks if the schema has been initialized by initSchema().
257
+ /**
258
+ * Throws an exception if the schema has *not* been initialized
259
+ * by `initSchema()`.
232
260
  *
233
- * Call this function before accessing schema.
234
- * Currently this only needs to be called in start() and clear() because all other functions will call start first.
261
+ * **To be called before trying to access schema.**
262
+ *
263
+ * Currently this only needs to be called in `start()` and `clear()` because
264
+ * all other functions will call start first.
235
265
  */
236
266
  const checkSchemaInitialized = () => {
237
267
  if (schema === undefined) {
@@ -264,18 +294,31 @@ const createTypeClasses: (
264
294
  return classes;
265
295
  };
266
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
+ */
267
306
  export declare type ModelInstanceCreator = typeof modelInstanceCreator;
268
307
 
269
- const instancesMetadata = new WeakSet<
270
- ModelInit<PersistentModel & Partial<ModelInstanceMetadata>>
271
- >();
272
- function modelInstanceCreator<T extends PersistentModel = PersistentModel>(
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
+ */
313
+ const instancesMetadata = new WeakSet<ModelInit<unknown, unknown>>();
314
+
315
+ function modelInstanceCreator<T extends PersistentModel>(
273
316
  modelConstructor: PersistentModelConstructor<T>,
274
- init: ModelInit<T> & Partial<ModelInstanceMetadata>
317
+ init: Partial<T>
275
318
  ): T {
276
319
  instancesMetadata.add(init);
277
320
 
278
- return <T>new modelConstructor(init);
321
+ return new modelConstructor(<ModelInit<T, PersistentModelMetaData<T>>>init);
279
322
  }
280
323
 
281
324
  const validateModelFields =
@@ -293,6 +336,17 @@ const validateModelFields =
293
336
  throw new Error(`Field ${name} is required`);
294
337
  }
295
338
 
339
+ if (
340
+ isSchemaModelWithAttributes(modelDefinition) &&
341
+ !isIdManaged(modelDefinition)
342
+ ) {
343
+ const keys = extractPrimaryKeyFieldNames(modelDefinition);
344
+ if (keys.includes(k) && v === '') {
345
+ logger.error(errorMessages.idEmptyString, { k, value: v });
346
+ throw new Error(errorMessages.idEmptyString);
347
+ }
348
+ }
349
+
296
350
  if (isGraphQLScalarType(type)) {
297
351
  const jsType = GraphQLScalarType.getJSType(type);
298
352
  const validateScalar = GraphQLScalarType.getValidationFunction(type);
@@ -370,6 +424,56 @@ const validateModelFields =
370
424
  `Field ${name} should be of type ${type}, validation failed. ${v}`
371
425
  );
372
426
  }
427
+ } else if (isNonModelFieldType(type)) {
428
+ // do not check non model fields if undefined or null
429
+ if (!isNullOrUndefined(v)) {
430
+ const subNonModelDefinition =
431
+ schema.namespaces.user.nonModels[type.nonModel];
432
+ const modelValidator = validateModelFields(subNonModelDefinition);
433
+
434
+ if (isArray) {
435
+ let errorTypeText: string = type.nonModel;
436
+ if (!isRequired) {
437
+ errorTypeText = `${type.nonModel} | null | undefined`;
438
+ }
439
+ if (!Array.isArray(v)) {
440
+ throw new Error(
441
+ `Field ${name} should be of type [${errorTypeText}], ${typeof v} received. ${v}`
442
+ );
443
+ }
444
+
445
+ v.forEach(item => {
446
+ if (
447
+ (isNullOrUndefined(item) && isRequired) ||
448
+ (typeof item !== 'object' && typeof item !== 'undefined')
449
+ ) {
450
+ throw new Error(
451
+ `All elements in the ${name} array should be of type ${
452
+ type.nonModel
453
+ }, [${typeof item}] received. ${item}`
454
+ );
455
+ }
456
+
457
+ if (!isNullOrUndefined(item)) {
458
+ Object.keys(subNonModelDefinition.fields).forEach(subKey => {
459
+ modelValidator(subKey, item[subKey]);
460
+ });
461
+ }
462
+ });
463
+ } else {
464
+ if (typeof v !== 'object') {
465
+ throw new Error(
466
+ `Field ${name} should be of type ${
467
+ type.nonModel
468
+ }, ${typeof v} recieved. ${v}`
469
+ );
470
+ }
471
+
472
+ Object.keys(subNonModelDefinition.fields).forEach(subKey => {
473
+ modelValidator(subKey, v[subKey]);
474
+ });
475
+ }
476
+ }
373
477
  }
374
478
  }
375
479
  };
@@ -403,7 +507,7 @@ const castInstanceType = (
403
507
  return v;
404
508
  };
405
509
 
406
- const initializeInstance = <T>(
510
+ const initializeInstance = <T extends PersistentModel>(
407
511
  init: ModelInit<T>,
408
512
  modelDefinition: SchemaModel | SchemaNonModel,
409
513
  draft: Draft<T & ModelInstanceMetadata>
@@ -427,31 +531,39 @@ const createModelClass = <T extends PersistentModel>(
427
531
  (draft: Draft<T & ModelInstanceMetadata>) => {
428
532
  initializeInstance(init, modelDefinition, draft);
429
533
 
534
+ // model is initialized inside a DataStore component (e.g. by Sync Engine, Storage Engine, etc.)
535
+ const isInternallyInitialized = instancesMetadata.has(init);
536
+
430
537
  const modelInstanceMetadata: ModelInstanceMetadata =
431
- instancesMetadata.has(init)
538
+ isInternallyInitialized
432
539
  ? <ModelInstanceMetadata>(<unknown>init)
433
540
  : <ModelInstanceMetadata>{};
434
- const {
435
- id: _id,
436
- _version,
437
- _lastChangedAt,
438
- _deleted,
439
- } = modelInstanceMetadata;
440
-
441
- // instancesIds are set by modelInstanceCreator, it is accessible only internally
442
- const isInternal = _id !== null && _id !== undefined;
443
-
444
- const id = isInternal
445
- ? _id
446
- : modelDefinition.syncable
447
- ? uuid4()
448
- : ulid();
449
-
450
- if (!isInternal) {
541
+
542
+ type ModelWithIDIdentifier = { id: string };
543
+
544
+ const { id: _id } =
545
+ modelInstanceMetadata as unknown as ModelWithIDIdentifier;
546
+
547
+ if (isIdManaged(modelDefinition)) {
548
+ const isInternalModel = _id !== null && _id !== undefined;
549
+
550
+ const id = isInternalModel
551
+ ? _id
552
+ : modelDefinition.syncable
553
+ ? uuid4()
554
+ : ulid();
555
+
556
+ (<ModelWithIDIdentifier>(<unknown>draft)).id = id;
557
+ } else if (isIdOptionallyManaged(modelDefinition)) {
558
+ // only auto-populate if the id was not provided
559
+ (<ModelWithIDIdentifier>(<unknown>draft)).id = draft.id || uuid4();
560
+ }
561
+
562
+ if (!isInternallyInitialized) {
451
563
  checkReadOnlyPropertyOnCreate(draft, modelDefinition);
452
564
  }
453
565
 
454
- draft.id = id;
566
+ const { _version, _lastChangedAt, _deleted } = modelInstanceMetadata;
455
567
 
456
568
  if (modelDefinition.syncable) {
457
569
  draft._version = _version;
@@ -476,8 +588,12 @@ const createModelClass = <T extends PersistentModel>(
476
588
  const model = produce(
477
589
  source,
478
590
  draft => {
479
- fn(<MutableModel<T>>(draft as unknown));
480
- draft.id = source.id;
591
+ fn(<MutableModel<T>>draft);
592
+
593
+ const keyNames = extractPrimaryKeyFieldNames(modelDefinition);
594
+ // Keys are immutable
595
+ keyNames.forEach(key => ((draft as Object)[key] = source[key]));
596
+
481
597
  const modelValidator = validateModelFields(modelDefinition);
482
598
  Object.entries(draft).forEach(([k, v]) => {
483
599
  const parsedValue = castInstanceType(modelDefinition, k, v);
@@ -489,6 +605,7 @@ const createModelClass = <T extends PersistentModel>(
489
605
  );
490
606
 
491
607
  const hasExistingPatches = modelPatchesMap.has(source);
608
+
492
609
  if (patches.length || hasExistingPatches) {
493
610
  if (hasExistingPatches) {
494
611
  const [existingPatches, existingSource] = modelPatchesMap.get(source);
@@ -516,6 +633,7 @@ const createModelClass = <T extends PersistentModel>(
516
633
  }
517
634
 
518
635
  const instance = modelInstanceCreator(clazz, json);
636
+
519
637
  const modelValidator = validateModelFields(modelDefinition);
520
638
 
521
639
  Object.entries(instance).forEach(([k, v]) => {
@@ -563,7 +681,9 @@ const checkReadOnlyPropertyOnUpdate = (
563
681
  });
564
682
  };
565
683
 
566
- const createNonModelClass = <T>(typeDefinition: SchemaNonModel) => {
684
+ const createNonModelClass = <T extends PersistentModel>(
685
+ typeDefinition: SchemaNonModel
686
+ ) => {
567
687
  const clazz = <NonModelTypeConstructor<T>>(<unknown>class Model {
568
688
  constructor(init: ModelInit<T>) {
569
689
  const instance = produce(
@@ -634,6 +754,18 @@ function getModelConstructorByModelName(
634
754
  }
635
755
  }
636
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
+ */
637
769
  async function checkSchemaVersion(
638
770
  storage: Storage,
639
771
  version: string
@@ -647,7 +779,6 @@ async function checkSchemaVersion(
647
779
  const [schemaVersionSetting] = await s.query(
648
780
  Setting,
649
781
  ModelPredicateCreator.createFromExisting(modelDefinition, c =>
650
- // @ts-ignore Argument of type '"eq"' is not assignable to parameter of type 'never'.
651
782
  c.key('eq', SETTING_SCHEMA_VERSION)
652
783
  ),
653
784
  { page: 0, limit: 1 }
@@ -713,6 +844,14 @@ function getNamespace(): SchemaNamespace {
713
844
  return namespace;
714
845
  }
715
846
 
847
+ enum DataStoreState {
848
+ NotRunning = 'Not Running',
849
+ Starting = 'Starting',
850
+ Running = 'Running',
851
+ Stopping = 'Stopping',
852
+ Clearing = 'Clearing',
853
+ }
854
+
716
855
  class DataStore {
717
856
  // reference to configured category instances. Used for preserving SSR context
718
857
  private Auth = Auth;
@@ -724,12 +863,12 @@ class DataStore {
724
863
  private conflictHandler: ConflictHandler;
725
864
  private errorHandler: (error: SyncError<PersistentModel>) => void;
726
865
  private fullSyncInterval: number;
727
- private initialized: Promise<void>;
866
+ private initialized?: Promise<void>;
728
867
  private initReject: Function;
729
868
  private initResolve: Function;
730
869
  private maxRecordsToSync: number;
731
- private storage: Storage;
732
- private sync: SyncEngine;
870
+ private storage?: Storage;
871
+ private sync?: SyncEngine;
733
872
  private syncPageSize: number;
734
873
  private syncExpressions: SyncExpression[];
735
874
  private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>> =
@@ -742,104 +881,197 @@ class DataStore {
742
881
  API: this.API,
743
882
  Cache: this.Cache,
744
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;
745
926
 
746
927
  getModuleName() {
747
928
  return 'DataStore';
748
929
  }
749
930
 
750
- start = async (): Promise<void> => {
751
- if (this.initialized === undefined) {
752
- logger.debug('Starting DataStore');
753
- this.initialized = new Promise((res, rej) => {
754
- this.initResolve = res;
755
- this.initReject = rej;
756
- });
757
- } else {
758
- 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
+ };
759
959
 
760
- return;
761
- }
960
+ return handler;
961
+ }
762
962
 
763
- this.storage = new Storage(
764
- schema,
765
- namespaceResolver,
766
- getModelConstructorByModelName,
767
- modelInstanceCreator,
768
- this.storageAdapter,
769
- this.sessionId
770
- );
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
+ }
771
984
 
772
- await this.storage.init();
985
+ this.storage = new Storage(
986
+ schema,
987
+ namespaceResolver,
988
+ getModelConstructorByModelName,
989
+ modelInstanceCreator,
990
+ this.storageAdapter,
991
+ this.sessionId
992
+ );
773
993
 
774
- checkSchemaInitialized();
775
- await checkSchemaVersion(this.storage, schema.version);
994
+ await this.storage.init();
776
995
 
777
- const { aws_appsync_graphqlEndpoint } = this.amplifyConfig;
996
+ checkSchemaInitialized();
997
+ await checkSchemaVersion(this.storage, schema.version);
778
998
 
779
- if (aws_appsync_graphqlEndpoint) {
780
- logger.debug('GraphQL endpoint available', aws_appsync_graphqlEndpoint);
999
+ const { aws_appsync_graphqlEndpoint } = this.amplifyConfig;
781
1000
 
782
- this.syncPredicates = await this.processSyncExpressions();
1001
+ if (aws_appsync_graphqlEndpoint) {
1002
+ logger.debug(
1003
+ 'GraphQL endpoint available',
1004
+ aws_appsync_graphqlEndpoint
1005
+ );
783
1006
 
784
- this.sync = new SyncEngine(
785
- schema,
786
- namespaceResolver,
787
- syncClasses,
788
- userClasses,
789
- this.storage,
790
- modelInstanceCreator,
791
- this.conflictHandler,
792
- this.errorHandler,
793
- this.syncPredicates,
794
- this.amplifyConfig,
795
- this.authModeStrategy,
796
- this.amplifyContext
797
- );
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
+ );
798
1024
 
799
- // tslint:disable-next-line:max-line-length
800
- const fullSyncIntervalInMilliseconds = this.fullSyncInterval * 1000 * 60; // fullSyncInterval from param is in minutes
801
- syncSubscription = this.sync
802
- .start({ fullSyncInterval: fullSyncIntervalInMilliseconds })
803
- .subscribe({
804
- next: ({ type, data }) => {
805
- // In Node, we need to wait for queries to be synced to prevent returning empty arrays.
806
- // In the Browser, we can begin returning data once subscriptions are in place.
807
- const readyType = isNode
808
- ? ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
809
- : ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED;
810
-
811
- if (type === readyType) {
812
- this.initResolve();
813
- }
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
+ }
814
1040
 
815
- Hub.dispatch('datastore', {
816
- event: type,
817
- 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
+ },
818
1050
  });
819
- },
820
- error: err => {
821
- logger.warn('Sync error', err);
822
- this.initReject();
823
- },
824
- });
825
- } else {
826
- logger.warn(
827
- "Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?",
828
- {
829
- config: this.amplifyConfig,
830
- }
831
- );
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
+ );
832
1058
 
833
- this.initResolve();
834
- }
1059
+ this.initResolve();
1060
+ }
835
1061
 
836
- await this.initialized;
1062
+ await this.initialized;
1063
+ this.state = DataStoreState.Running;
1064
+ }, 'datastore start')
1065
+ .catch(this.handleAddProcError('DataStore.start()'));
837
1066
  };
838
1067
 
839
1068
  query: {
840
1069
  <T extends PersistentModel>(
841
1070
  modelConstructor: PersistentModelConstructor<T>,
842
- id: string
1071
+ identifier: IdentifierFieldOrIdentifierObject<
1072
+ T,
1073
+ PersistentModelMetaData<T>
1074
+ >
843
1075
  ): Promise<T | undefined>;
844
1076
  <T extends PersistentModel>(
845
1077
  modelConstructor: PersistentModelConstructor<T>,
@@ -848,112 +1080,142 @@ class DataStore {
848
1080
  ): Promise<T[]>;
849
1081
  } = async <T extends PersistentModel>(
850
1082
  modelConstructor: PersistentModelConstructor<T>,
851
- idOrCriteria?: string | ProducerModelPredicate<T> | typeof PredicateAll,
1083
+ identifierOrCriteria?:
1084
+ | IdentifierFieldOrIdentifierObject<T, PersistentModelMetaData<T>>
1085
+ | ProducerModelPredicate<T>
1086
+ | typeof PredicateAll,
852
1087
  paginationProducer?: ProducerPaginationInput<T>
853
1088
  ): Promise<T | T[] | undefined> => {
854
- await this.start();
1089
+ return this.runningProcesses
1090
+ .add(async () => {
1091
+ await this.start();
855
1092
 
856
- //#region Input validation
1093
+ //#region Input validation
857
1094
 
858
- if (!isValidModelConstructor(modelConstructor)) {
859
- const msg = 'Constructor is not for a valid model';
860
- logger.error(msg, { modelConstructor });
1095
+ if (!isValidModelConstructor(modelConstructor)) {
1096
+ const msg = 'Constructor is not for a valid model';
1097
+ logger.error(msg, { modelConstructor });
861
1098
 
862
- throw new Error(msg);
863
- }
1099
+ throw new Error(msg);
1100
+ }
864
1101
 
865
- if (typeof idOrCriteria === 'string') {
866
- if (paginationProducer !== undefined) {
867
- logger.warn('Pagination is ignored when querying by id');
868
- }
869
- }
1102
+ if (typeof identifierOrCriteria === 'string') {
1103
+ if (paginationProducer !== undefined) {
1104
+ logger.warn('Pagination is ignored when querying by id');
1105
+ }
1106
+ }
870
1107
 
871
- const modelDefinition = getModelDefinition(modelConstructor);
872
- let predicate: ModelPredicate<T>;
1108
+ const modelDefinition = getModelDefinition(modelConstructor);
1109
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
873
1110
 
874
- if (isQueryOne(idOrCriteria)) {
875
- predicate = ModelPredicateCreator.createForId<T>(
876
- modelDefinition,
877
- idOrCriteria
878
- );
879
- } else {
880
- if (isPredicatesAll(idOrCriteria)) {
881
- // Predicates.ALL means "all records", so no predicate (undefined)
882
- predicate = undefined;
883
- } else {
884
- predicate = ModelPredicateCreator.createFromExisting(
1111
+ let predicate: ModelPredicate<T>;
1112
+
1113
+ if (isQueryOne(identifierOrCriteria)) {
1114
+ if (keyFields.length > 1) {
1115
+ const msg = errorMessages.queryByPkWithCompositeKeyPresent;
1116
+ logger.error(msg, { keyFields });
1117
+
1118
+ throw new Error(msg);
1119
+ }
1120
+
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(
885
1145
  modelDefinition,
886
- idOrCriteria
1146
+ paginationProducer
887
1147
  );
888
- }
889
- }
890
1148
 
891
- const pagination = this.processPagination(
892
- modelDefinition,
893
- paginationProducer
894
- );
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
+ });
895
1162
 
896
- //#endregion
897
-
898
- logger.debug('params ready', {
899
- modelConstructor,
900
- predicate: ModelPredicateCreator.getPredicates(predicate, false),
901
- pagination: {
902
- ...pagination,
903
- sort: ModelSortPredicateCreator.getPredicates(
904
- pagination && pagination.sort,
905
- false
906
- ),
907
- },
908
- });
1163
+ const result = await this.storage.query(
1164
+ modelConstructor,
1165
+ predicate,
1166
+ pagination
1167
+ );
909
1168
 
910
- const result = await this.storage.query(
911
- modelConstructor,
912
- predicate,
913
- pagination
914
- );
1169
+ const returnOne =
1170
+ isQueryOne(identifierOrCriteria) ||
1171
+ isIdentifierObject(identifierOrCriteria, modelDefinition);
915
1172
 
916
- return isQueryOne(idOrCriteria) ? result[0] : result;
1173
+ return returnOne ? result[0] : result;
1174
+ }, 'datastore query')
1175
+ .catch(this.handleAddProcError('DataStore.query()'));
917
1176
  };
918
1177
 
919
1178
  save = async <T extends PersistentModel>(
920
1179
  model: T,
921
1180
  condition?: ProducerModelPredicate<T>
922
1181
  ): Promise<T> => {
923
- await this.start();
1182
+ return this.runningProcesses
1183
+ .add(async () => {
1184
+ await this.start();
924
1185
 
925
- // Immer patches for constructing a correct update mutation input
926
- // Allows us to only include changed fields for updates
927
- 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);
928
1189
 
929
- const modelConstructor: PersistentModelConstructor<T> = model
930
- ? <PersistentModelConstructor<T>>model.constructor
931
- : undefined;
1190
+ const modelConstructor: PersistentModelConstructor<T> | undefined =
1191
+ model ? <PersistentModelConstructor<T>>model.constructor : undefined;
932
1192
 
933
- if (!isValidModelConstructor(modelConstructor)) {
934
- const msg = 'Object is not an instance of a valid model';
935
- 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 });
936
1196
 
937
- throw new Error(msg);
938
- }
1197
+ throw new Error(msg);
1198
+ }
939
1199
 
940
- const modelDefinition = getModelDefinition(modelConstructor);
1200
+ const modelDefinition = getModelDefinition(modelConstructor);
941
1201
 
942
- const producedCondition = ModelPredicateCreator.createFromExisting(
943
- modelDefinition,
944
- condition
945
- );
1202
+ const producedCondition = ModelPredicateCreator.createFromExisting(
1203
+ modelDefinition,
1204
+ condition!
1205
+ );
946
1206
 
947
- const [savedModel] = await this.storage.runExclusive(async s => {
948
- await s.save(model, producedCondition, undefined, patchesTuple);
1207
+ const [savedModel] = await this.storage.runExclusive(async s => {
1208
+ await s.save(model, producedCondition, undefined, patchesTuple);
949
1209
 
950
- return s.query(
951
- modelConstructor,
952
- ModelPredicateCreator.createForId(modelDefinition, model.id)
953
- );
954
- });
1210
+ return s.query<T>(
1211
+ modelConstructor,
1212
+ ModelPredicateCreator.createForPk(modelDefinition, model)
1213
+ );
1214
+ });
955
1215
 
956
- return savedModel;
1216
+ return savedModel;
1217
+ }, 'datastore save')
1218
+ .catch(this.handleAddProcError('DataStore.save()'));
957
1219
  };
958
1220
 
959
1221
  setConflictHandler = (config: DataStoreConfig): ConflictHandler => {
@@ -989,126 +1251,171 @@ class DataStore {
989
1251
  };
990
1252
 
991
1253
  delete: {
992
- <T extends PersistentModel>(
993
- model: T,
994
- condition?: ProducerModelPredicate<T>
995
- ): Promise<T>;
996
1254
  <T extends PersistentModel>(
997
1255
  modelConstructor: PersistentModelConstructor<T>,
998
- id: string
1256
+ identifier: IdentifierFieldOrIdentifierObject<
1257
+ T,
1258
+ PersistentModelMetaData<T>
1259
+ >
999
1260
  ): Promise<T[]>;
1000
1261
  <T extends PersistentModel>(
1001
1262
  modelConstructor: PersistentModelConstructor<T>,
1002
1263
  condition: ProducerModelPredicate<T> | typeof PredicateAll
1003
1264
  ): Promise<T[]>;
1265
+ <T extends PersistentModel>(
1266
+ model: T,
1267
+ condition?: ProducerModelPredicate<T>
1268
+ ): Promise<T>;
1004
1269
  } = async <T extends PersistentModel>(
1005
1270
  modelOrConstructor: T | PersistentModelConstructor<T>,
1006
- idOrCriteria?: string | ProducerModelPredicate<T> | typeof PredicateAll
1007
- ) => {
1008
- await this.start();
1271
+ identifierOrCriteria?:
1272
+ | IdentifierFieldOrIdentifierObject<T, PersistentModelMetaData<T>>
1273
+ | ProducerModelPredicate<T>
1274
+ | typeof PredicateAll
1275
+ ): Promise<T | T[]> => {
1276
+ return this.runningProcesses
1277
+ .add(async () => {
1278
+ await this.start();
1009
1279
 
1010
- let condition: ModelPredicate<T>;
1280
+ let condition: ModelPredicate<T>;
1011
1281
 
1012
- if (!modelOrConstructor) {
1013
- const msg = 'Model or Model Constructor required';
1014
- logger.error(msg, { modelOrConstructor });
1282
+ if (!modelOrConstructor) {
1283
+ const msg = 'Model or Model Constructor required';
1284
+ logger.error(msg, { modelOrConstructor });
1015
1285
 
1016
- throw new Error(msg);
1017
- }
1286
+ throw new Error(msg);
1287
+ }
1018
1288
 
1019
- if (isValidModelConstructor(modelOrConstructor)) {
1020
- const modelConstructor = modelOrConstructor;
1289
+ if (isValidModelConstructor<T>(modelOrConstructor)) {
1290
+ const modelConstructor = modelOrConstructor;
1021
1291
 
1022
- if (!idOrCriteria) {
1023
- const msg =
1024
- 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL';
1025
- logger.error(msg, { idOrCriteria });
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 });
1026
1296
 
1027
- throw new Error(msg);
1028
- }
1297
+ throw new Error(msg);
1298
+ }
1029
1299
 
1030
- if (typeof idOrCriteria === 'string') {
1031
- condition = ModelPredicateCreator.createForId<T>(
1032
- getModelDefinition(modelConstructor),
1033
- idOrCriteria
1034
- );
1035
- } else {
1036
- condition = ModelPredicateCreator.createFromExisting(
1037
- getModelDefinition(modelConstructor),
1038
- /**
1039
- * idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
1040
- * The symbol is used only for typing purposes. e.g. see Predicates.ALL
1041
- */
1042
- idOrCriteria as ProducerModelPredicate<T>
1043
- );
1300
+ const modelDefinition = getModelDefinition(modelConstructor);
1044
1301
 
1045
- if (!condition || !ModelPredicateCreator.isValidPredicate(condition)) {
1046
- const msg =
1047
- 'Criteria required. Do you want to delete all? Pass Predicates.ALL';
1048
- logger.error(msg, { condition });
1302
+ if (typeof identifierOrCriteria === 'string') {
1303
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1049
1304
 
1050
- throw new Error(msg);
1051
- }
1052
- }
1305
+ if (keyFields.length > 1) {
1306
+ const msg = errorMessages.deleteByPkWithCompositeKeyPresent;
1307
+ logger.error(msg, { keyFields });
1053
1308
 
1054
- const [deleted] = await this.storage.delete(modelConstructor, condition);
1309
+ throw new Error(msg);
1310
+ }
1055
1311
 
1056
- return deleted;
1057
- } else {
1058
- const model = modelOrConstructor;
1059
- const modelConstructor = Object.getPrototypeOf(model || {})
1060
- .constructor as PersistentModelConstructor<T>;
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
+ }
1061
1333
 
1062
- if (!isValidModelConstructor(modelConstructor)) {
1063
- const msg = 'Object is not an instance of a valid model';
1064
- logger.error(msg, { model });
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 });
1065
1341
 
1066
- throw new Error(msg);
1067
- }
1342
+ throw new Error(msg);
1343
+ }
1344
+ }
1068
1345
 
1069
- const modelDefinition = getModelDefinition(modelConstructor);
1346
+ const [deleted] = await this.storage.delete(
1347
+ modelConstructor,
1348
+ condition
1349
+ );
1070
1350
 
1071
- const idPredicate = ModelPredicateCreator.createForId<T>(
1072
- modelDefinition,
1073
- model.id
1074
- );
1351
+ return deleted;
1352
+ } else {
1353
+ const model = modelOrConstructor;
1354
+ const modelConstructor = Object.getPrototypeOf(model || {})
1355
+ .constructor as PersistentModelConstructor<T>;
1075
1356
 
1076
- if (idOrCriteria) {
1077
- if (typeof idOrCriteria !== 'function') {
1078
- const msg = 'Invalid criteria';
1079
- logger.error(msg, { idOrCriteria });
1357
+ if (!isValidModelConstructor(modelConstructor)) {
1358
+ const msg = 'Object is not an instance of a valid model';
1359
+ logger.error(msg, { model });
1080
1360
 
1081
- throw new Error(msg);
1082
- }
1361
+ throw new Error(msg);
1362
+ }
1083
1363
 
1084
- condition = idOrCriteria(idPredicate);
1085
- } else {
1086
- condition = idPredicate;
1087
- }
1364
+ const modelDefinition = getModelDefinition(modelConstructor);
1365
+
1366
+ const pkPredicate = ModelPredicateCreator.createForPk<T>(
1367
+ modelDefinition,
1368
+ model
1369
+ );
1088
1370
 
1089
- const [[deleted]] = await this.storage.delete(model, condition);
1371
+ if (identifierOrCriteria) {
1372
+ if (typeof identifierOrCriteria !== 'function') {
1373
+ const msg = 'Invalid criteria';
1374
+ logger.error(msg, { identifierOrCriteria });
1090
1375
 
1091
- return deleted;
1092
- }
1376
+ throw new Error(msg);
1377
+ }
1378
+
1379
+ condition = (<ProducerModelPredicate<T>>identifierOrCriteria)(
1380
+ pkPredicate
1381
+ );
1382
+ } else {
1383
+ condition = pkPredicate;
1384
+ }
1385
+
1386
+ const [[deleted]] = await this.storage.delete(model, condition);
1387
+
1388
+ return deleted;
1389
+ }
1390
+ }, 'datastore delete')
1391
+ .catch(this.handleAddProcError('DataStore.delete()'));
1093
1392
  };
1094
1393
 
1095
1394
  observe: {
1096
1395
  (): Observable<SubscriptionMessage<PersistentModel>>;
1097
1396
 
1098
- <T extends PersistentModel>(model: T): Observable<SubscriptionMessage<T>>;
1397
+ <T extends PersistentModel>(
1398
+ modelConstructor: PersistentModelConstructor<T>,
1399
+ identifier: string
1400
+ ): Observable<SubscriptionMessage<T>>;
1099
1401
 
1100
1402
  <T extends PersistentModel>(
1101
1403
  modelConstructor: PersistentModelConstructor<T>,
1102
- criteria?: string | ProducerModelPredicate<T>
1404
+ criteria?: ProducerModelPredicate<T> | typeof PredicateAll
1103
1405
  ): Observable<SubscriptionMessage<T>>;
1104
- } = <T extends PersistentModel = PersistentModel>(
1406
+
1407
+ <T extends PersistentModel>(model: T): Observable<SubscriptionMessage<T>>;
1408
+ } = <T extends PersistentModel>(
1105
1409
  modelOrConstructor?: T | PersistentModelConstructor<T>,
1106
- idOrCriteria?: string | ProducerModelPredicate<T>
1410
+ identifierOrCriteria?:
1411
+ | string
1412
+ | ProducerModelPredicate<T>
1413
+ | typeof PredicateAll
1107
1414
  ): Observable<SubscriptionMessage<T>> => {
1108
1415
  let predicate: ModelPredicate<T>;
1109
1416
 
1110
- const modelConstructor: PersistentModelConstructor<T> =
1111
- modelOrConstructor && isValidModelConstructor(modelOrConstructor)
1417
+ const modelConstructor: PersistentModelConstructor<T> | undefined =
1418
+ modelOrConstructor && isValidModelConstructor<T>(modelOrConstructor)
1112
1419
  ? modelOrConstructor
1113
1420
  : undefined;
1114
1421
 
@@ -1118,10 +1425,10 @@ class DataStore {
1118
1425
  model && (<Object>Object.getPrototypeOf(model)).constructor;
1119
1426
 
1120
1427
  if (isValidModelConstructor<T>(modelConstructor)) {
1121
- if (idOrCriteria) {
1428
+ if (identifierOrCriteria) {
1122
1429
  logger.warn('idOrCriteria is ignored when using a model instance', {
1123
1430
  model,
1124
- idOrCriteria,
1431
+ identifierOrCriteria,
1125
1432
  });
1126
1433
  }
1127
1434
 
@@ -1135,9 +1442,24 @@ class DataStore {
1135
1442
  }
1136
1443
  }
1137
1444
 
1138
- if (idOrCriteria !== undefined && modelConstructor === undefined) {
1445
+ // observe should not accept object literal syntax
1446
+ if (
1447
+ identifierOrCriteria &&
1448
+ modelConstructor &&
1449
+ isIdentifierObject(
1450
+ identifierOrCriteria,
1451
+ getModelDefinition(modelConstructor)
1452
+ )
1453
+ ) {
1454
+ const msg = errorMessages.observeWithObjectLiteral;
1455
+ logger.error(msg, { objectLiteral: identifierOrCriteria });
1456
+
1457
+ throw new Error(msg);
1458
+ }
1459
+
1460
+ if (identifierOrCriteria !== undefined && modelConstructor === undefined) {
1139
1461
  const msg = 'Cannot provide criteria without a modelConstructor';
1140
- logger.error(msg, idOrCriteria);
1462
+ logger.error(msg, identifierOrCriteria);
1141
1463
  throw new Error(msg);
1142
1464
  }
1143
1465
 
@@ -1148,64 +1470,89 @@ class DataStore {
1148
1470
  throw new Error(msg);
1149
1471
  }
1150
1472
 
1151
- if (typeof idOrCriteria === 'string') {
1152
- predicate = ModelPredicateCreator.createForId<T>(
1473
+ if (typeof identifierOrCriteria === 'string') {
1474
+ const modelDefinition = getModelDefinition(modelConstructor);
1475
+ const [keyField] = extractPrimaryKeyFieldNames(modelDefinition);
1476
+
1477
+ predicate = ModelPredicateCreator.createForSingleField<T>(
1153
1478
  getModelDefinition(modelConstructor),
1154
- idOrCriteria
1479
+ keyField,
1480
+ identifierOrCriteria
1155
1481
  );
1156
1482
  } else {
1157
- predicate =
1158
- modelConstructor &&
1159
- ModelPredicateCreator.createFromExisting<T>(
1160
- getModelDefinition(modelConstructor),
1161
- idOrCriteria
1162
- );
1483
+ if (isPredicatesAll(identifierOrCriteria)) {
1484
+ predicate = undefined;
1485
+ } else {
1486
+ predicate =
1487
+ modelConstructor &&
1488
+ ModelPredicateCreator.createFromExisting<T>(
1489
+ getModelDefinition(modelConstructor),
1490
+ identifierOrCriteria
1491
+ );
1492
+ }
1163
1493
  }
1164
1494
 
1165
1495
  return new Observable<SubscriptionMessage<T>>(observer => {
1166
1496
  let handle: ZenObservable.Subscription;
1167
1497
 
1168
- (async () => {
1169
- await this.start();
1170
-
1171
- // Filter the events returned by Storage according to namespace,
1172
- // append original element data, and subscribe to the observable
1173
- handle = this.storage
1174
- .observe(modelConstructor, predicate)
1175
- .filter(({ model }) => namespaceResolver(model) === USER)
1176
- .subscribe({
1177
- next: async item => {
1178
- // the `element` doesn't necessarily contain all item details or
1179
- // have related records attached consistently with that of a query()
1180
- // result item. for consistency, we attach them here.
1181
-
1182
- let message = item;
1183
-
1184
- // as lnog as we're not dealing with a DELETE, we need to fetch a fresh
1185
- // item from storage to ensure it's fully populated.
1186
- if (item.opType !== 'DELETE') {
1187
- const freshElement = await this.query(
1188
- item.model,
1189
- item.element.id
1190
- );
1191
- message = {
1192
- ...message,
1193
- element: freshElement as T,
1194
- };
1195
- }
1196
-
1197
- observer.next(message as SubscriptionMessage<T>);
1198
- },
1199
- error: err => observer.error(err),
1200
- complete: () => observer.complete(),
1201
- });
1202
- })();
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
+ });
1203
1547
 
1204
- 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 () => {
1205
1552
  if (handle) {
1206
1553
  handle.unsubscribe();
1207
1554
  }
1208
- };
1555
+ }, 'DataStore.observe() cleanup');
1209
1556
  });
1210
1557
  };
1211
1558
 
@@ -1215,7 +1562,7 @@ class DataStore {
1215
1562
  criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
1216
1563
  paginationProducer?: ObserveQueryOptions<T>
1217
1564
  ): Observable<DataStoreSnapshot<T>>;
1218
- } = <T extends PersistentModel = PersistentModel>(
1565
+ } = <T extends PersistentModel>(
1219
1566
  model: PersistentModelConstructor<T>,
1220
1567
  criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
1221
1568
  options?: ObserveQueryOptions<T>
@@ -1254,9 +1601,12 @@ class DataStore {
1254
1601
  const sortOptions = sort ? { sort } : undefined;
1255
1602
 
1256
1603
  const modelDefinition = getModelDefinition(model);
1604
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1605
+
1257
1606
  if (isQueryOne(criteria)) {
1258
- predicate = ModelPredicateCreator.createForId<T>(
1607
+ predicate = ModelPredicateCreator.createForSingleField<T>(
1259
1608
  modelDefinition,
1609
+ keyFields[0],
1260
1610
  criteria
1261
1611
  );
1262
1612
  } else {
@@ -1275,68 +1625,79 @@ class DataStore {
1275
1625
  ModelPredicateCreator.getPredicates(predicate, false) || {};
1276
1626
  const hasPredicate = !!predicates;
1277
1627
 
1278
- (async () => {
1279
- try {
1280
- // first, query and return any locally-available records
1281
- (await this.query(model, criteria, sortOptions)).forEach(item =>
1282
- items.set(item.id, item)
1283
- );
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 => {
1633
+ const itemModelDefinition = getModelDefinition(model);
1634
+ const idOrPk = getIdentifierValue(itemModelDefinition, item);
1635
+ items.set(idOrPk, item);
1636
+ });
1284
1637
 
1285
- // Observe the model and send a stream of updates (debounced).
1286
- // We need to post-filter results instead of passing criteria through
1287
- // to have visibility into items that move from in-set to out-of-set.
1288
- // We need to explicitly remove those items from the existing snapshot.
1289
- handle = this.observe(model).subscribe(
1290
- ({ element, model, opType }) => {
1291
- if (
1292
- hasPredicate &&
1293
- !validatePredicate(element, predicateGroupType, predicates)
1294
- ) {
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);
1295
1646
  if (
1296
- opType === 'UPDATE' &&
1297
- (items.has(element.id) || itemsChanged.has(element.id))
1647
+ hasPredicate &&
1648
+ !validatePredicate(element, predicateGroupType, predicates)
1298
1649
  ) {
1299
- // tracking as a "deleted item" will include the item in
1300
- // page limit calculations and ensure it is removed from the
1301
- // final items collection, regardless of which collection(s)
1302
- // it is currently in. (I mean, it could be in both, right!?)
1303
- deletedItemIds.push(element.id);
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') {
1670
+ deletedItemIds.push(idOrPk);
1304
1671
  } else {
1305
- // ignore updates for irrelevant/filtered items.
1306
- return;
1672
+ itemsChanged.set(idOrPk, element);
1307
1673
  }
1308
- }
1309
1674
 
1310
- // Flag items which have been recently deleted
1311
- // NOTE: Merging of separate operations to the same model instance is handled upstream
1312
- // in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
1313
- // depends on the LATEST record (for a given id).
1314
- if (opType === 'DELETE') {
1315
- deletedItemIds.push(element.id);
1316
- } else {
1317
- itemsChanged.set(element.id, element);
1318
- }
1675
+ const isSynced =
1676
+ this.sync?.getModelSyncedStatus(model) ?? false;
1319
1677
 
1320
- const isSynced = this.sync?.getModelSyncedStatus(model) ?? false;
1678
+ const limit =
1679
+ itemsChanged.size - deletedItemIds.length >=
1680
+ this.syncPageSize;
1321
1681
 
1322
- const limit =
1323
- itemsChanged.size - deletedItemIds.length >= this.syncPageSize;
1682
+ if (limit || isSynced) {
1683
+ limitTimerRace.resolve();
1684
+ }
1324
1685
 
1325
- if (limit || isSynced) {
1326
- limitTimerRace.resolve();
1686
+ // kicks off every subsequent race as results sync down
1687
+ limitTimerRace.start();
1327
1688
  }
1689
+ );
1328
1690
 
1329
- // kicks off every subsequent race as results sync down
1330
- limitTimerRace.start();
1331
- }
1332
- );
1333
-
1334
- // returns a set of initial/locally-available results
1335
- generateAndEmitSnapshot();
1336
- } catch (err) {
1337
- observer.error(err);
1338
- }
1339
- })();
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
+ });
1340
1701
 
1341
1702
  /**
1342
1703
  * Combines the `items`, `itemsChanged`, and `deletedItemIds` collections into
@@ -1356,10 +1717,14 @@ class DataStore {
1356
1717
  }
1357
1718
 
1358
1719
  items.clear();
1359
- itemsArray.forEach(item => items.set(item.id, item));
1720
+ itemsArray.forEach(item => {
1721
+ const itemModelDefinition = getModelDefinition(model);
1722
+ const idOrPk = getIdentifierValue(itemModelDefinition, item);
1723
+ items.set(idOrPk, item);
1724
+ });
1360
1725
 
1361
1726
  // remove deleted items from the final result set
1362
- deletedItemIds.forEach(id => items.delete(id));
1727
+ deletedItemIds.forEach(idOrPk => items.delete(idOrPk));
1363
1728
 
1364
1729
  return {
1365
1730
  items: Array.from(items.values()),
@@ -1376,7 +1741,8 @@ class DataStore {
1376
1741
  * @param snapshot The generated items data to emit.
1377
1742
  */
1378
1743
  const emitSnapshot = (snapshot: DataStoreSnapshot<T>): void => {
1379
- // 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 ...
1380
1746
  observer.next(snapshot);
1381
1747
 
1382
1748
  // reset the changed items sets
@@ -1419,16 +1785,16 @@ class DataStore {
1419
1785
  data?.model?.name === model.name
1420
1786
  ) {
1421
1787
  generateAndEmitSnapshot();
1422
- Hub.remove('api', hubCallback);
1788
+ Hub.remove('datastore', hubCallback);
1423
1789
  }
1424
1790
  };
1425
1791
  Hub.listen('datastore', hubCallback);
1426
1792
 
1427
- return () => {
1793
+ return this.runningProcesses.addCleaner(async () => {
1428
1794
  if (handle) {
1429
1795
  handle.unsubscribe();
1430
1796
  }
1431
- };
1797
+ }, 'datastore observequery cleaner');
1432
1798
  });
1433
1799
  };
1434
1800
 
@@ -1515,11 +1881,21 @@ class DataStore {
1515
1881
  this.storageAdapter ||
1516
1882
  undefined;
1517
1883
 
1518
- this.sessionId = this.retrieveSessionId();
1884
+ this.sessionId = this.retrieveSessionId()!;
1519
1885
  };
1520
1886
 
1521
- 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() {
1522
1896
  checkSchemaInitialized();
1897
+ this.state = DataStoreState.Clearing;
1898
+ await this.runningProcesses.close();
1523
1899
  if (this.storage === undefined) {
1524
1900
  // connect to storage so that it can be cleared without fully starting DataStore
1525
1901
  this.storage = new Storage(
@@ -1537,35 +1913,53 @@ class DataStore {
1537
1913
  syncSubscription.unsubscribe();
1538
1914
  }
1539
1915
 
1540
- await this.storage.clear();
1541
-
1542
1916
  if (this.sync) {
1543
- this.sync.unsubscribeConnectivity();
1917
+ await this.sync.stop();
1544
1918
  }
1545
1919
 
1920
+ await this.storage!.clear();
1921
+
1546
1922
  this.initialized = undefined; // Should re-initialize when start() is called.
1547
1923
  this.storage = undefined;
1548
1924
  this.sync = undefined;
1549
1925
  this.syncPredicates = new WeakMap<SchemaModel, ModelPredicate<any>>();
1550
- };
1551
1926
 
1552
- stop = async function stop() {
1553
- if (this.initialized !== undefined) {
1554
- await this.start();
1555
- }
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();
1556
1941
 
1557
1942
  if (syncSubscription && !syncSubscription.closed) {
1558
1943
  syncSubscription.unsubscribe();
1559
1944
  }
1560
1945
 
1561
1946
  if (this.sync) {
1562
- this.sync.unsubscribeConnectivity();
1947
+ await this.sync.stop();
1563
1948
  }
1564
1949
 
1565
1950
  this.initialized = undefined; // Should re-initialize when start() is called.
1566
1951
  this.sync = undefined;
1567
- };
1952
+ await this.runningProcesses.open();
1953
+ this.state = DataStoreState.NotRunning;
1954
+ }
1568
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
+ */
1569
1963
  private processPagination<T extends PersistentModel>(
1570
1964
  modelDefinition: SchemaModel,
1571
1965
  paginationProducer: ProducerPaginationInput<T>
@@ -1615,6 +2009,10 @@ class DataStore {
1615
2009
  };
1616
2010
  }
1617
2011
 
2012
+ /**
2013
+ * Examines the configured `syncExpressions` and produces a WeakMap of
2014
+ * SchemaModel -> predicate to use during sync.
2015
+ */
1618
2016
  private async processSyncExpressions(): Promise<
1619
2017
  WeakMap<SchemaModel, ModelPredicate<any>>
1620
2018
  > {
@@ -1700,7 +2098,10 @@ class DataStore {
1700
2098
  }, new WeakMap<SchemaModel, ModelPredicate<any>>());
1701
2099
  }
1702
2100
 
1703
- // 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
+ */
1704
2105
  private retrieveSessionId(): string | undefined {
1705
2106
  try {
1706
2107
  const sessionId = sessionStorage.getItem('datastoreSessionId');
@@ -1713,9 +2114,9 @@ class DataStore {
1713
2114
 
1714
2115
  return `${sessionId}-${appSyncId}`;
1715
2116
  }
1716
- } catch {
1717
- return undefined;
1718
- }
2117
+ } catch {}
2118
+
2119
+ return undefined;
1719
2120
  }
1720
2121
  }
1721
2122