@@ -68,6 +68,12 @@ class MemoryOfflineNavigationCacheStorage
6868 return this . routeEntries . get ( this . getKey ( key ) )
6969 }
7070
71+ async getRoutes ( buildId : string ) : Promise < OfflineNavigationRouteRecord [ ] > {
72+ return Array . from ( this . routeEntries . values ( ) ) . filter (
73+ ( entry ) => entry . buildId === buildId
74+ )
75+ }
76+
7177 async putRoute ( entry : OfflineNavigationRouteRecord ) : Promise < void > {
7278 this . routeEntries . set ( this . getKey ( [ entry . buildId , entry . key ] ) , entry )
7379 }
@@ -91,6 +97,14 @@ class MemoryOfflineNavigationCacheStorage
9197 return this . segmentEntries . get ( this . getKey ( key ) )
9298 }
9399
100+ async getSegments (
101+ buildId : string
102+ ) : Promise < OfflineNavigationSegmentRecord [ ] > {
103+ return Array . from ( this . segmentEntries . values ( ) ) . filter (
104+ ( entry ) => entry . buildId === buildId
105+ )
106+ }
107+
94108 async putSegment ( entry : OfflineNavigationSegmentRecord ) : Promise < void > {
95109 this . segmentEntries . set ( this . getKey ( [ entry . buildId , entry . key ] ) , entry )
96110 }
@@ -144,6 +158,10 @@ class FailingOfflineNavigationCacheStorage
144158 throw new Error ( 'get route failed' )
145159 }
146160
161+ async getRoutes ( ) : Promise < OfflineNavigationRouteRecord [ ] > {
162+ throw new Error ( 'get routes failed' )
163+ }
164+
147165 async putRoute ( ) : Promise < void > {
148166 throw new Error ( 'put route failed' )
149167 }
@@ -164,6 +182,10 @@ class FailingOfflineNavigationCacheStorage
164182 throw new Error ( 'get segment failed' )
165183 }
166184
185+ async getSegments ( ) : Promise < OfflineNavigationSegmentRecord [ ] > {
186+ throw new Error ( 'get segments failed' )
187+ }
188+
167189 async putSegment ( ) : Promise < void > {
168190 throw new Error ( 'put segment failed' )
169191 }
@@ -708,6 +730,110 @@ describe('offline navigation cache', () => {
708730 ) . resolves . toBe ( null )
709731 } )
710732
733+ it ( 'lists only fresh current-epoch route and segment records' , async ( ) => {
734+ const storage = new MemoryOfflineNavigationCacheStorage ( )
735+ const cache = createOfflineNavigationRouterCache ( storage )
736+ const routeVaryPath = serializeOfflineNavigationVaryPath ( {
737+ id : null ,
738+ value : '/dashboard' ,
739+ parent : null ,
740+ } )
741+ const segmentVaryPath = serializeOfflineNavigationVaryPath ( {
742+ id : null ,
743+ value : 'children/page' ,
744+ parent : null ,
745+ } )
746+
747+ await cache . writeRoute ( {
748+ buildId : 'build-a' ,
749+ key : 'route:/dashboard' ,
750+ now : 100 ,
751+ staleAt : 200 ,
752+ expiresAt : 300 ,
753+ route : {
754+ pathname : '/dashboard' ,
755+ search : '' ,
756+ nextUrl : null ,
757+ canonicalUrl : '/dashboard' ,
758+ renderedSearch : '' ,
759+ couldBeIntercepted : false ,
760+ supportsPerSegmentPrefetching : true ,
761+ hasDynamicRewrite : false ,
762+ } ,
763+ routeVaryPath,
764+ tree : { segment : 'dashboard' } ,
765+ metadata : { segment : 'metadata' } ,
766+ } )
767+ await cache . writeRoute ( {
768+ buildId : 'build-a' ,
769+ key : 'route:/expired' ,
770+ now : 100 ,
771+ staleAt : 110 ,
772+ expiresAt : 120 ,
773+ route : {
774+ pathname : '/expired' ,
775+ search : '' ,
776+ nextUrl : null ,
777+ canonicalUrl : '/expired' ,
778+ renderedSearch : '' ,
779+ couldBeIntercepted : false ,
780+ supportsPerSegmentPrefetching : true ,
781+ hasDynamicRewrite : false ,
782+ } ,
783+ routeVaryPath,
784+ tree : { segment : 'expired' } ,
785+ metadata : { segment : 'metadata' } ,
786+ } )
787+ await cache . writeSegment ( {
788+ buildId : 'build-a' ,
789+ key : 'segment:/dashboard:children/page' ,
790+ now : 100 ,
791+ staleAt : 200 ,
792+ expiresAt : 300 ,
793+ segment : {
794+ requestKey : 'children/page' ,
795+ fetchStrategy : 1 ,
796+ isPartial : false ,
797+ payloadIndex : 0 ,
798+ } ,
799+ segmentVaryPath,
800+ payload : { kind : 'segment-payload' } ,
801+ } )
802+ await cache . writeSegment ( {
803+ buildId : 'build-a' ,
804+ key : 'segment:/expired:children/page' ,
805+ now : 100 ,
806+ staleAt : 110 ,
807+ expiresAt : 120 ,
808+ segment : {
809+ requestKey : 'children/page' ,
810+ fetchStrategy : 1 ,
811+ isPartial : false ,
812+ payloadIndex : 0 ,
813+ } ,
814+ segmentVaryPath,
815+ payload : { kind : 'expired-payload' } ,
816+ } )
817+
818+ await expect (
819+ cache . readRoutes ( { buildId : 'build-a' , now : 150 } )
820+ ) . resolves . toMatchObject ( [ { key : 'route:/dashboard' } ] )
821+ await expect (
822+ cache . readSegments ( { buildId : 'build-a' , now : 150 } )
823+ ) . resolves . toMatchObject ( [ { key : 'segment:/dashboard:children/page' } ] )
824+ expect ( storage . routeEntries . has ( 'build-a\0route:/expired' ) ) . toBe ( false )
825+ expect (
826+ storage . segmentEntries . has ( 'build-a\0segment:/expired:children/page' )
827+ ) . toBe ( false )
828+
829+ await expect (
830+ cache . readRoutes ( { buildId : 'build-b' , now : 150 } )
831+ ) . resolves . toEqual ( [ ] )
832+ await expect (
833+ cache . readSegments ( { buildId : 'build-b' , now : 150 } )
834+ ) . resolves . toEqual ( [ ] )
835+ } )
836+
711837 it ( 'ignores and deletes entries whose stored build id does not match' , async ( ) => {
712838 const storage = new MemoryOfflineNavigationCacheStorage ( )
713839 const cache = createOfflineNavigationCache ( storage )
0 commit comments