@@ -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.
@@ -465,12 +471,22 @@ export class LocalStore {
465
471
// any preexisting value.
466
472
const resumeToken = change . resumeToken ;
467
473
if ( resumeToken . length > 0 ) {
474
+ const oldQueryData = queryData ;
468
475
queryData = queryData . copy ( {
469
476
resumeToken,
470
477
snapshotVersion : remoteEvent . snapshotVersion
471
478
} ) ;
472
479
this . targetIds [ targetId ] = queryData ;
473
- promises . push ( this . queryCache . updateQueryData ( txn , queryData ) ) ;
480
+
481
+ if (
482
+ LocalStore . shouldPersistResumeToken (
483
+ oldQueryData ,
484
+ queryData ,
485
+ change
486
+ )
487
+ ) {
488
+ promises . push ( this . queryCache . updateQueryData ( txn , queryData ) ) ;
489
+ }
474
490
}
475
491
}
476
492
) ;
@@ -546,6 +562,42 @@ export class LocalStore {
546
562
} ) ;
547
563
}
548
564
565
+ /**
566
+ * Returns true if the the resume token in newQueryData should be persisted.
567
+ */
568
+ private static shouldPersistResumeToken (
569
+ oldQueryData : QueryData ,
570
+ newQueryData : QueryData ,
571
+ change : TargetChange
572
+ ) : boolean {
573
+ // Avoid clearing any existing value
574
+ if ( newQueryData . resumeToken . length === 0 ) return false ;
575
+
576
+ // Any resume token is interesting if there isn't one already.
577
+ if ( oldQueryData . resumeToken . length === 0 ) return true ;
578
+
579
+ // Don't allow resume token changes to be buffered indefinitely. This
580
+ // allows us to be reasonably up-to-date after a crash and avoids needing
581
+ // to loop over all active queries on shutdown. Especially in the browser
582
+ // we may not get time to do anything interesting while the current tab is
583
+ // closing.
584
+ const timeDelta =
585
+ newQueryData . snapshotVersion . toMicroseconds ( ) -
586
+ oldQueryData . snapshotVersion . toMicroseconds ( ) ;
587
+ if ( timeDelta >= this . MAX_RESUME_TOKEN_BUFFERING_MICROS ) return true ;
588
+
589
+ // Otherwise if the only thing that has changed about a target is its resume
590
+ // token it's not worth persisting. Note that the RemoteStore keeps an
591
+ // in-memory view of the currently active targets which includes the current
592
+ // resume token, so stream failure or user changes will still use an
593
+ // up-to-date resume token regardless of what we do here.
594
+ const changes =
595
+ change . addedDocuments . size +
596
+ change . modifiedDocuments . size +
597
+ change . removedDocuments . size ;
598
+ return changes > 0 ;
599
+ }
600
+
549
601
/**
550
602
* Notify local store of the changed views to locally pin documents.
551
603
*/
0 commit comments