@@ -111,6 +111,12 @@ export interface LocalWriteResult {
111
111
* unrecoverable error (should be caught / reported by the async_queue).
112
112
*/
113
113
export class LocalStore {
114
+ /**
115
+ * The maximum time to leave a resume token buffered without writing it
116
+ * out.
117
+ */
118
+ private static readonly MAX_RESUME_TOKEN_BUFFERING_MICROS = 5 * 60 * 1e6 ;
119
+
114
120
/**
115
121
* The set of all mutations that have been sent but not yet been applied to
116
122
* the backend.
@@ -469,12 +475,22 @@ export class LocalStore {
469
475
// any preexisting value.
470
476
const resumeToken = change . resumeToken ;
471
477
if ( resumeToken . length > 0 ) {
478
+ const oldQueryData = queryData ;
472
479
queryData = queryData . copy ( {
473
480
resumeToken,
474
481
snapshotVersion : remoteEvent . snapshotVersion
475
482
} ) ;
476
483
this . targetIds [ targetId ] = queryData ;
477
- promises . push ( this . queryCache . updateQueryData ( txn , queryData ) ) ;
484
+
485
+ if (
486
+ LocalStore . shouldPersistResumeToken (
487
+ oldQueryData ,
488
+ queryData ,
489
+ change
490
+ )
491
+ ) {
492
+ promises . push ( this . queryCache . updateQueryData ( txn , queryData ) ) ;
493
+ }
478
494
}
479
495
}
480
496
) ;
@@ -550,6 +566,42 @@ export class LocalStore {
550
566
} ) ;
551
567
}
552
568
569
+ /**
570
+ * Returns true if the the resume token in newQueryData should be persisted.
571
+ */
572
+ private static shouldPersistResumeToken (
573
+ oldQueryData : QueryData ,
574
+ newQueryData : QueryData ,
575
+ change : TargetChange
576
+ ) : boolean {
577
+ // Avoid clearing any existing value
578
+ if ( newQueryData . resumeToken . length === 0 ) return false ;
579
+
580
+ // Any resume token is interesting if there isn't one already.
581
+ if ( oldQueryData . resumeToken . length === 0 ) return true ;
582
+
583
+ // Don't allow resume token changes to be buffered indefinitely. This
584
+ // allows us to be reasonably up-to-date after a crash and avoids needing
585
+ // to loop over all active queries on shutdown. Especially in the browser
586
+ // we may not get time to do anything interesting while the current tab is
587
+ // closing.
588
+ const timeDelta =
589
+ newQueryData . snapshotVersion . toMicroseconds ( ) -
590
+ oldQueryData . snapshotVersion . toMicroseconds ( ) ;
591
+ if ( timeDelta >= this . MAX_RESUME_TOKEN_BUFFERING_MICROS ) return true ;
592
+
593
+ // Otherwise if the only thing that has changed about a target is its resume
594
+ // token it's not worth persisting. Note that the RemoteStore keeps an
595
+ // in-memory view of the currently active targets which includes the current
596
+ // resume token, so stream failure or user changes will still use an
597
+ // up-to-date resume token regardless of what we do here.
598
+ const changes =
599
+ change . addedDocuments . size +
600
+ change . modifiedDocuments . size +
601
+ change . removedDocuments . size ;
602
+ return changes > 0 ;
603
+ }
604
+
553
605
/**
554
606
* Notify local store of the changed views to locally pin documents.
555
607
*/
0 commit comments