@aws-amplify/datastore 4.1.5 → 4.1.6-lerna-upgrade.4

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 (30) hide show
  1. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +1 -27
  2. package/lib/storage/adapter/AsyncStorageAdapter.js +0 -133
  3. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  4. package/lib/storage/adapter/IndexedDBAdapter.d.ts +2 -28
  5. package/lib/storage/adapter/IndexedDBAdapter.js +0 -102
  6. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  7. package/lib/storage/adapter/StorageAdapterBase.d.ts +3 -15
  8. package/lib/storage/adapter/StorageAdapterBase.js +77 -117
  9. package/lib/storage/adapter/StorageAdapterBase.js.map +1 -1
  10. package/lib/sync/index.d.ts +14 -0
  11. package/lib/sync/index.js +60 -0
  12. package/lib/sync/index.js.map +1 -1
  13. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +1 -27
  14. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +1 -134
  15. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  16. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +2 -28
  17. package/lib-esm/storage/adapter/IndexedDBAdapter.js +1 -103
  18. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  19. package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +3 -15
  20. package/lib-esm/storage/adapter/StorageAdapterBase.js +79 -119
  21. package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -1
  22. package/lib-esm/sync/index.d.ts +14 -0
  23. package/lib-esm/sync/index.js +62 -2
  24. package/lib-esm/sync/index.js.map +1 -1
  25. package/package.json +7 -7
  26. package/src/storage/adapter/AsyncStorageAdapter.ts +0 -162
  27. package/src/storage/adapter/IndexedDBAdapter.ts +1 -110
  28. package/src/storage/adapter/StorageAdapterBase.ts +44 -138
  29. package/src/sync/index.ts +76 -1
  30. package/CHANGELOG.md +0 -1044
@@ -8,20 +8,15 @@ import {
8
8
  PersistentModelConstructor,
9
9
  PredicatesGroup,
10
10
  QueryOne,
11
- RelationType,
12
11
  } from '../../types';
13
12
  import {
14
13
  DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR,
15
- getIndex,
16
- getIndexFromAssociation,
17
14
  traverseModel,
18
15
  validatePredicate,
19
16
  inMemoryPagination,
20
- NAMESPACES,
21
17
  keysEqual,
22
18
  getStorename,
23
19
  getIndexKeys,
24
- IDENTIFIER_KEY_SEPARATOR,
25
20
  } from '../../util';
26
21
  import { StorageAdapterBase } from './StorageAdapterBase';
27
22
 
@@ -235,163 +230,6 @@ export class AsyncStorageAdapter extends StorageAdapterBase {
235
230
  }
236
231
  }
237
232
 
238
- /**
239
- * Gets related Has One record for `model`
240
- *
241
- * @param model
242
- * @param srcModel
243
- * @param namespace
244
- * @param rel
245
- * @returns
246
- */
247
- protected async getHasOneChild<T extends PersistentModel>(
248
- model: T,
249
- srcModel: string,
250
- namespace: NAMESPACES,
251
- rel: RelationType
252
- ) {
253
- let hasOneIndex;
254
- const { modelName, targetNames, associatedWith } = rel;
255
- const storeName = getStorename(namespace, modelName);
256
- const index: string | undefined =
257
- getIndex(
258
- this.schema.namespaces[namespace].relationships![modelName]
259
- .relationTypes,
260
- srcModel
261
- ) ||
262
- // if we were unable to find an index via relationTypes
263
- // i.e. for keyName connections, attempt to find one by the
264
- // associatedWith property
265
- getIndexFromAssociation(
266
- this.schema.namespaces[namespace].relationships![modelName].indexes,
267
- rel.associatedWith!
268
- );
269
-
270
- if (index) {
271
- hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
272
- } else if (associatedWith) {
273
- if (Array.isArray(associatedWith)) {
274
- hasOneIndex = associatedWith;
275
- } else {
276
- hasOneIndex = [associatedWith];
277
- }
278
- }
279
-
280
- // iterate over targetNames array and see if each key is present in model object
281
- // targetNames here being the keys for the CHILD model
282
- const hasConnectedModelFields = targetNames!.every(targetName =>
283
- model.hasOwnProperty(targetName)
284
- );
285
-
286
- // PK / Composite key for the parent model
287
- const keyValuesPath: string = this.getIndexKeyValuesPath(model);
288
-
289
- let values;
290
-
291
- const isUnidirectionalConnection = hasOneIndex === associatedWith;
292
-
293
- if (hasConnectedModelFields && isUnidirectionalConnection) {
294
- // Values will be that of the child model
295
- values = targetNames!
296
- .filter(targetName => model[targetName] ?? false)
297
- .map(targetName => model[targetName]);
298
- } else {
299
- // values will be that of the parent model
300
- values = keyValuesPath.split(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
301
- }
302
-
303
- if (values.length === 0) return;
304
-
305
- const allRecords = await this.db.getAll(storeName);
306
-
307
- let recordToDelete;
308
-
309
- // values === targetNames
310
- if (hasConnectedModelFields) {
311
- /**
312
- * Retrieve record by finding the record where all
313
- * targetNames are present on the connected model.
314
- *
315
- */
316
-
317
- recordToDelete = allRecords.find(childItem =>
318
- hasOneIndex.every(index => values.includes(childItem[index]))
319
- );
320
- } else {
321
- // values === keyValuePath
322
- recordToDelete = allRecords.find(
323
- childItem => childItem[hasOneIndex] === values
324
- ) as T[];
325
- }
326
-
327
- return recordToDelete;
328
- }
329
-
330
- /**
331
- * Backwards compatability for pre-CPK codegen
332
- * TODO - deprecate this in v6; will need to re-gen MIPR for older unit
333
- * tests that hit this path
334
- */
335
- protected async getHasOneChildLegacy<T extends PersistentModel>(
336
- model: T,
337
- srcModel: string,
338
- namespace: NAMESPACES,
339
- rel: RelationType
340
- ) {
341
- const { modelName, targetName, associatedWith } = rel;
342
- const storeName = getStorename(namespace, modelName);
343
- const index: string | undefined =
344
- getIndex(
345
- this.schema.namespaces[namespace].relationships![modelName]
346
- .relationTypes,
347
- srcModel
348
- ) ||
349
- // if we were unable to find an index via relationTypes
350
- // i.e. for keyName connections, attempt to find one by the
351
- // associatedWith property
352
- getIndexFromAssociation(
353
- this.schema.namespaces[namespace].relationships![modelName].indexes,
354
- rel.associatedWith!
355
- );
356
- const hasOneIndex = index || associatedWith;
357
- const hasOneCustomField = targetName! in model;
358
- const keyValuesPath: string = this.getIndexKeyValuesPath(model);
359
- const value = hasOneCustomField ? model[targetName!] : keyValuesPath;
360
-
361
- if (!value) return;
362
-
363
- const allRecords = await this.db.getAll(storeName);
364
-
365
- const recordToDelete = allRecords.find(
366
- childItem => childItem[hasOneIndex as string] === value
367
- ) as T;
368
-
369
- return recordToDelete;
370
- }
371
-
372
- /**
373
- * Gets related Has Many records by given `storeName`, `index`, and `keyValues`
374
- *
375
- * @param storeName
376
- * @param index
377
- * @param keyValues
378
- * @returns
379
- */
380
- protected async getHasManyChildren<T extends PersistentModel>(
381
- storeName: string,
382
- index: string,
383
- keyValues: string[]
384
- ): Promise<T[]> {
385
- const allRecords = await this.db.getAll(storeName);
386
- const indices = index!.split(IDENTIFIER_KEY_SEPARATOR);
387
-
388
- const childRecords = allRecords.filter(childItem =>
389
- indices.every(index => keyValues.includes(childItem[index]))
390
- ) as T[];
391
-
392
- return childRecords;
393
- }
394
-
395
233
  //#region platform-specific helper methods
396
234
 
397
235
  /**
@@ -12,15 +12,12 @@ import {
12
12
  PredicateObject,
13
13
  PredicatesGroup,
14
14
  QueryOne,
15
- RelationType,
16
15
  } from '../../types';
17
16
  import {
18
- getIndex,
19
17
  isPrivateMode,
20
18
  traverseModel,
21
19
  validatePredicate,
22
20
  inMemoryPagination,
23
- NAMESPACES,
24
21
  keysEqual,
25
22
  getStorename,
26
23
  isSafariCompatabilityMode,
@@ -384,7 +381,7 @@ class IndexedDBAdapter extends StorageAdapterBase {
384
381
  }
385
382
 
386
383
  protected async deleteItem<T extends PersistentModel>(
387
- deleteQueue?: {
384
+ deleteQueue: {
388
385
  storeName: string;
389
386
  items: T[] | IDBValidKey[];
390
387
  }[]
@@ -420,112 +417,6 @@ class IndexedDBAdapter extends StorageAdapterBase {
420
417
  }
421
418
  }
422
419
 
423
- /**
424
- * Gets related Has One record for `model`
425
- *
426
- * @param model
427
- * @param srcModel
428
- * @param namespace
429
- * @param rel
430
- * @returns
431
- */
432
- protected async getHasOneChild<T extends PersistentModel>(
433
- model: T,
434
- srcModel: string,
435
- namespace: NAMESPACES,
436
- rel: RelationType
437
- ) {
438
- const hasOneIndex = 'byPk';
439
- const { modelName, targetNames } = rel;
440
- const storeName = getStorename(namespace, modelName);
441
-
442
- const values = targetNames!
443
- .filter(targetName => model[targetName] ?? false)
444
- .map(targetName => model[targetName]);
445
-
446
- if (values.length === 0) return;
447
-
448
- const recordToDelete = <T>(
449
- await this.db
450
- .transaction(storeName, 'readwrite')
451
- .objectStore(storeName)
452
- .index(hasOneIndex)
453
- .get(this.canonicalKeyPath(values))
454
- );
455
-
456
- return recordToDelete;
457
- }
458
-
459
- /**
460
- * Backwards compatability for pre-CPK codegen
461
- * TODO - deprecate this in v6; will need to re-gen MIPR for older unit
462
- * tests that hit this path
463
- */
464
- protected async getHasOneChildLegacy<T extends PersistentModel>(
465
- model: T,
466
- srcModel: string,
467
- namespace: NAMESPACES,
468
- rel: RelationType
469
- ) {
470
- const hasOneIndex = 'byPk';
471
- const { modelName, targetName } = rel;
472
- const storeName = getStorename(namespace, modelName);
473
-
474
- let index;
475
- let values: string[];
476
-
477
- if (targetName && targetName in model) {
478
- index = hasOneIndex;
479
- const value = model[targetName];
480
- if (value === null) {
481
- return;
482
- }
483
- values = [value];
484
- } else {
485
- // backwards compatability for older versions of codegen that did not emit targetName for HAS_ONE relations
486
- index = getIndex(
487
- this.schema.namespaces[namespace].relationships![modelName]
488
- .relationTypes,
489
- srcModel
490
- );
491
- values = this.getIndexKeyValuesFromModel(model);
492
- }
493
-
494
- if (!values || !index) return;
495
-
496
- const recordToDelete = <T>(
497
- await this.db
498
- .transaction(storeName, 'readwrite')
499
- .objectStore(storeName)
500
- .index(index)
501
- .get(this.canonicalKeyPath(values))
502
- );
503
-
504
- return recordToDelete;
505
- }
506
-
507
- /**
508
- * Gets related Has Many records by given `storeName`, `index`, and `keyValues`
509
- *
510
- * @param storeName
511
- * @param index
512
- * @param keyValues
513
- * @returns
514
- */
515
- protected async getHasManyChildren<T extends PersistentModel>(
516
- storeName: string,
517
- index: string,
518
- keyValues: string[]
519
- ): Promise<T[]> {
520
- const childRecords = await this.db
521
- .transaction(storeName, 'readwrite')
522
- .objectStore(storeName)
523
- .index(index as string)
524
- .getAll(this.canonicalKeyPath(keyValues));
525
-
526
- return childRecords;
527
- }
528
-
529
420
  //#region platform-specific helper methods
530
421
 
531
422
  private async checkPrivate() {
@@ -15,7 +15,6 @@ import {
15
15
  PredicateObject,
16
16
  PredicatesGroup,
17
17
  QueryOne,
18
- RelationType,
19
18
  } from '../../types';
20
19
  import {
21
20
  NAMESPACES,
@@ -24,12 +23,12 @@ import {
24
23
  extractPrimaryKeyValues,
25
24
  traverseModel,
26
25
  validatePredicate,
27
- getIndex,
28
- getIndexFromAssociation,
29
26
  isModelConstructor,
27
+ extractPrimaryKeyFieldNames,
30
28
  } from '../../util';
31
29
  import type { IDBPDatabase, IDBPObjectStore } from 'idb';
32
30
  import type AsyncStorageDatabase from './AsyncStorageDatabase';
31
+ import { ModelRelationship } from '../relationship';
33
32
 
34
33
  const logger = new Logger('DataStore');
35
34
  const DB_NAME = 'amplify-datastore';
@@ -382,17 +381,12 @@ export abstract class StorageAdapterBase implements Adapter {
382
381
  const modelConstructor =
383
382
  modelOrModelConstructor as PersistentModelConstructor<T>;
384
383
  const namespace = this.namespaceResolver(modelConstructor) as NAMESPACES;
385
-
386
384
  const models = await this.query(modelConstructor, condition);
387
- const relations =
388
- this.schema.namespaces![namespace].relationships![modelConstructor.name]
389
- .relationTypes;
390
385
 
391
386
  if (condition !== undefined) {
392
387
  await this.deleteTraverse(
393
- relations,
394
388
  models,
395
- modelConstructor.name,
389
+ modelConstructor,
396
390
  namespace,
397
391
  deleteQueue
398
392
  );
@@ -407,9 +401,8 @@ export abstract class StorageAdapterBase implements Adapter {
407
401
  return [models, deletedModels];
408
402
  } else {
409
403
  await this.deleteTraverse(
410
- relations,
411
404
  models,
412
- modelConstructor.name,
405
+ modelConstructor,
413
406
  namespace,
414
407
  deleteQueue
415
408
  );
@@ -428,6 +421,7 @@ export abstract class StorageAdapterBase implements Adapter {
428
421
 
429
422
  const modelConstructor = Object.getPrototypeOf(model)
430
423
  .constructor as PersistentModelConstructor<T>;
424
+
431
425
  const namespaceName = this.namespaceResolver(
432
426
  modelConstructor
433
427
  ) as NAMESPACES;
@@ -457,28 +451,16 @@ export abstract class StorageAdapterBase implements Adapter {
457
451
  throw new Error(msg);
458
452
  }
459
453
 
460
- const relations =
461
- this.schema.namespaces[namespaceName].relationships![
462
- modelConstructor.name
463
- ].relationTypes;
464
-
465
454
  await this.deleteTraverse(
466
- relations,
467
455
  [model],
468
- modelConstructor.name,
456
+ modelConstructor,
469
457
  namespaceName,
470
458
  deleteQueue
471
459
  );
472
460
  } else {
473
- const relations =
474
- this.schema.namespaces[namespaceName].relationships![
475
- modelConstructor.name
476
- ].relationTypes;
477
-
478
461
  await this.deleteTraverse(
479
- relations,
480
462
  [model],
481
- modelConstructor.name,
463
+ modelConstructor,
482
464
  namespaceName,
483
465
  deleteQueue
484
466
  );
@@ -501,139 +483,63 @@ export abstract class StorageAdapterBase implements Adapter {
501
483
  }[]
502
484
  );
503
485
 
504
- protected abstract getHasOneChild<T extends PersistentModel>(
505
- model: T,
506
- srcModel: string,
507
- namespace: NAMESPACES,
508
- rel: RelationType
509
- ): Promise<T | undefined>;
510
-
511
- /**
512
- * Backwards compatability for pre-CPK codegen
513
- * TODO - deprecate this in v6; will need to re-gen MIPR for older unit
514
- * tests that hit this path
515
- */
516
- protected abstract getHasOneChildLegacy<T extends PersistentModel>(
517
- model: T,
518
- srcModel: string,
519
- namespace: NAMESPACES,
520
- rel: RelationType
521
- ): Promise<T | undefined>;
522
-
523
- protected abstract getHasManyChildren<T extends PersistentModel>(
524
- storeName: string,
525
- index: string,
526
- keyValues: string[]
527
- ): Promise<T[] | undefined>;
528
-
529
486
  /**
530
487
  * Recursively traverse relationship graph and add
531
488
  * all Has One and Has Many relations to `deleteQueue` param
532
489
  *
533
490
  * Actual deletion of records added to `deleteQueue` occurs in the `delete` method
534
491
  *
535
- * @param relations
536
492
  * @param models
537
- * @param srcModel
493
+ * @param modelConstructor
538
494
  * @param namespace
539
495
  * @param deleteQueue
540
496
  */
541
- protected async deleteTraverse<T extends PersistentModel>(
542
- relations: RelationType[],
497
+ private async deleteTraverse<T extends PersistentModel>(
543
498
  models: T[],
544
- srcModel: string,
499
+ modelConstructor: PersistentModelConstructor<T>,
545
500
  namespace: NAMESPACES,
546
501
  deleteQueue: { storeName: string; items: T[] }[]
547
502
  ): Promise<void> {
548
- for await (const rel of relations) {
549
- const { modelName, relationType, targetNames, associatedWith } = rel;
550
-
551
- const storeName = getStorename(namespace, modelName);
552
- const index: string =
553
- getIndex(
554
- this.schema.namespaces[namespace].relationships![modelName]
555
- .relationTypes,
556
- srcModel
557
- ) ||
558
- // if we were unable to find an index via relationTypes
559
- // i.e. for keyName connections, attempt to find one by the
560
- // associatedWith property
561
- getIndexFromAssociation(
562
- this.schema.namespaces[namespace].relationships![modelName].indexes,
563
- associatedWith!
564
- )!;
565
-
566
- for await (const model of models) {
567
- const childRecords: PersistentModel[] = [];
568
-
569
- switch (relationType) {
570
- case 'HAS_ONE':
571
- let childRecord;
572
- if (targetNames?.length) {
573
- childRecord = await this.getHasOneChild(
574
- model,
575
- srcModel,
576
- namespace,
577
- rel
578
- );
579
- } else {
580
- childRecord = await this.getHasOneChildLegacy(
581
- model,
582
- srcModel,
583
- namespace,
584
- rel
585
- );
586
- }
587
-
588
- if (childRecord) {
589
- childRecords.push(childRecord);
590
- }
591
-
592
- break;
593
- case 'HAS_MANY':
594
- const keyValues: string[] = this.getIndexKeyValuesFromModel(model);
595
-
596
- const records = await this.getHasManyChildren(
597
- storeName,
598
- index,
599
- keyValues
600
- );
601
-
602
- if (records?.length) {
603
- childRecords.push(...records);
604
- }
605
-
606
- break;
607
- case 'BELONGS_TO':
608
- // Intentionally blank
609
- break;
610
- default:
611
- throw new Error(`Invalid relation type ${relationType}`);
612
- }
503
+ const cascadingRelationTypes = ['HAS_ONE', 'HAS_MANY'];
613
504
 
614
- // instantiate models before passing them to next recursive call
615
- // necessary for extracting PK metadata in `getHasOneChild` and `getHasManyChildren`
616
- const childModels = await this.load(namespace, modelName, childRecords);
505
+ for await (const model of models) {
506
+ const modelDefinition =
507
+ this.schema.namespaces[namespace].models[modelConstructor.name];
617
508
 
618
- await this.deleteTraverse(
619
- this.schema.namespaces[namespace].relationships![modelName]
620
- .relationTypes,
621
- childModels,
622
- modelName,
623
- namespace,
624
- deleteQueue
625
- );
509
+ const modelMeta = {
510
+ builder: modelConstructor,
511
+ schema: modelDefinition,
512
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
513
+ };
514
+
515
+ const relationships = ModelRelationship.allFrom(modelMeta).filter(r =>
516
+ cascadingRelationTypes.includes(r.type)
517
+ );
518
+
519
+ for await (const r of relationships) {
520
+ const queryObject = r.createRemoteQueryObject(model);
521
+ if (queryObject !== null) {
522
+ const relatedRecords = await this.query(
523
+ r.remoteModelConstructor,
524
+ ModelPredicateCreator.createFromFlatEqualities(
525
+ r.remoteDefinition!,
526
+ queryObject
527
+ )
528
+ );
529
+
530
+ await this.deleteTraverse(
531
+ relatedRecords,
532
+ r.remoteModelConstructor,
533
+ namespace,
534
+ deleteQueue
535
+ );
536
+ }
626
537
  }
627
538
  }
628
539
 
629
540
  deleteQueue.push({
630
- storeName: getStorename(namespace, srcModel),
631
- items: models.map(record =>
632
- this.modelInstanceCreator(
633
- this.getModelConstructorByModelName!(namespace, srcModel),
634
- record
635
- )
636
- ),
541
+ storeName: getStorename(namespace, modelConstructor.name),
542
+ items: models,
637
543
  });
638
544
  }
639
545
  }
package/src/sync/index.ts CHANGED
@@ -2,8 +2,13 @@ import {
2
2
  browserOrNode,
3
3
  ConsoleLogger as Logger,
4
4
  BackgroundProcessManager,
5
+ Hub,
5
6
  } from '@aws-amplify/core';
6
- import { CONTROL_MSG as PUBSUB_CONTROL_MSG } from '@aws-amplify/pubsub';
7
+ import {
8
+ CONTROL_MSG as PUBSUB_CONTROL_MSG,
9
+ CONNECTION_STATE_CHANGE as PUBSUB_CONNECTION_STATE_CHANGE,
10
+ ConnectionState,
11
+ } from '@aws-amplify/pubsub';
7
12
  import Observable, { ZenObservable } from 'zen-observable-ts';
8
13
  import { ModelInstanceCreator } from '../datastore/datastore';
9
14
  import { ModelPredicateCreator } from '../predicates';
@@ -116,6 +121,13 @@ export class SyncEngine {
116
121
  PersistentModelConstructor<any>,
117
122
  boolean
118
123
  > = new WeakMap();
124
+ private unsleepSyncQueriesObservable: (() => void) | null;
125
+ private waitForSleepState: Promise<void>;
126
+ private syncQueriesObservableStartSleeping: (
127
+ value?: void | PromiseLike<void>
128
+ ) => void;
129
+ private stopDisruptionListener: () => void;
130
+ private connectionDisrupted = false;
119
131
 
120
132
  private runningProcesses: BackgroundProcessManager;
121
133
 
@@ -144,6 +156,9 @@ export class SyncEngine {
144
156
  private readonly connectivityMonitor?: DataStoreConnectivity
145
157
  ) {
146
158
  this.runningProcesses = new BackgroundProcessManager();
159
+ this.waitForSleepState = new Promise(resolve => {
160
+ this.syncQueriesObservableStartSleeping = resolve;
161
+ });
147
162
 
148
163
  const MutationEvent = this.modelClasses[
149
164
  'MutationEvent'
@@ -237,6 +252,8 @@ export class SyncEngine {
237
252
  'Realtime disabled when in a server-side environment'
238
253
  );
239
254
  } else {
255
+ this.stopDisruptionListener =
256
+ this.startDisruptionListener();
240
257
  //#region GraphQL Subscriptions
241
258
  [ctlSubsObservable, dataSubsObservable] =
242
259
  this.subscriptionsProcessor.start();
@@ -769,11 +786,19 @@ export class SyncEngine {
769
786
 
770
787
  onTerminate.then(() => {
771
788
  terminated = true;
789
+ this.syncQueriesObservableStartSleeping();
772
790
  unsleep();
773
791
  });
774
792
 
793
+ this.unsleepSyncQueriesObservable = unsleep;
794
+ this.syncQueriesObservableStartSleeping();
775
795
  return sleep;
776
796
  }, 'syncQueriesObservable sleep');
797
+
798
+ this.unsleepSyncQueriesObservable = null;
799
+ this.waitForSleepState = new Promise(resolve => {
800
+ this.syncQueriesObservableStartSleeping = resolve;
801
+ });
777
802
  }
778
803
  }, 'syncQueriesObservable main');
779
804
  });
@@ -808,6 +833,11 @@ export class SyncEngine {
808
833
  */
809
834
  this.unsubscribeConnectivity();
810
835
 
836
+ /**
837
+ * Stop listening for websocket connection disruption
838
+ */
839
+ this.stopDisruptionListener && this.stopDisruptionListener();
840
+
811
841
  /**
812
842
  * aggressively shut down any lingering background processes.
813
843
  * some of this might be semi-redundant with unsubscribing. however,
@@ -1053,4 +1083,49 @@ export class SyncEngine {
1053
1083
  };
1054
1084
  return namespace;
1055
1085
  }
1086
+
1087
+ /**
1088
+ * listen for websocket connection disruption
1089
+ *
1090
+ * May indicate there was a period of time where messages
1091
+ * from AppSync were missed. A sync needs to be triggered to
1092
+ * retrieve the missed data.
1093
+ */
1094
+ private startDisruptionListener() {
1095
+ return Hub.listen('api', (data: any) => {
1096
+ if (
1097
+ data.source === 'PubSub' &&
1098
+ data.payload.event === PUBSUB_CONNECTION_STATE_CHANGE
1099
+ ) {
1100
+ const connectionState = data.payload.data
1101
+ .connectionState as ConnectionState;
1102
+
1103
+ switch (connectionState) {
1104
+ // Do not need to listen for ConnectionDisruptedPendingNetwork
1105
+ // Normal network reconnection logic will handle the sync
1106
+ case ConnectionState.ConnectionDisrupted:
1107
+ this.connectionDisrupted = true;
1108
+ break;
1109
+
1110
+ case ConnectionState.Connected:
1111
+ if (this.connectionDisrupted) {
1112
+ this.scheduleSync();
1113
+ }
1114
+ this.connectionDisrupted = false;
1115
+ break;
1116
+ }
1117
+ }
1118
+ });
1119
+ }
1120
+
1121
+ /*
1122
+ * Schedule a sync to start when syncQueriesObservable enters sleep state
1123
+ * Start sync immediately if syncQueriesObservable is already in sleep state
1124
+ */
1125
+ private scheduleSync(): Promise<void> {
1126
+ return this.waitForSleepState.then(() => {
1127
+ // unsleepSyncQueriesObservable will be set if waitForSleepState has resolved
1128
+ this.unsleepSyncQueriesObservable!();
1129
+ });
1130
+ }
1056
1131
  }