1+ import type {
2+ CacheHandlerValue ,
3+ IncrementalCacheContext ,
4+ IncrementalCacheValue ,
5+ } from "types/cache" ;
6+ import { getTagsFromValue , hasBeenRevalidated } from "utils/cache" ;
17import { isBinaryContentType } from "../utils/binary" ;
28import { debug , error , warn } from "./logger" ;
39
4- interface CachedFetchValue {
5- kind : "FETCH" ;
6- data : {
7- headers : { [ k : string ] : string } ;
8- body : string ;
9- url : string ;
10- status ?: number ;
11- tags ?: string [ ] ;
12- } ;
13- revalidate : number ;
14- }
15-
16- interface CachedRedirectValue {
17- kind : "REDIRECT" ;
18- props : Object ;
19- }
20-
21- interface CachedRouteValue {
22- kind : "ROUTE" | "APP_ROUTE" ;
23- // this needs to be a RenderResult so since renderResponse
24- // expects that type instead of a string
25- body : Buffer ;
26- status : number ;
27- headers : Record < string , undefined | string | string [ ] > ;
28- }
29-
30- interface CachedImageValue {
31- kind : "IMAGE" ;
32- etag : string ;
33- buffer : Buffer ;
34- extension : string ;
35- isMiss ?: boolean ;
36- isStale ?: boolean ;
37- }
38-
39- interface IncrementalCachedPageValue {
40- kind : "PAGE" | "PAGES" ;
41- // this needs to be a string since the cache expects to store
42- // the string value
43- html : string ;
44- pageData : Object ;
45- status ?: number ;
46- headers ?: Record < string , undefined | string > ;
47- }
48-
49- interface IncrementalCachedAppPageValue {
50- kind : "APP_PAGE" ;
51- // this needs to be a string since the cache expects to store
52- // the string value
53- html : string ;
54- rscData : Buffer ;
55- headers ?: Record < string , undefined | string | string [ ] > ;
56- postponed ?: string ;
57- status ?: number ;
58- }
59-
60- type IncrementalCacheValue =
61- | CachedRedirectValue
62- | IncrementalCachedPageValue
63- | IncrementalCachedAppPageValue
64- | CachedImageValue
65- | CachedFetchValue
66- | CachedRouteValue ;
67-
68- type IncrementalCacheContext = {
69- revalidate ?: number | false | undefined ;
70- fetchCache ?: boolean | undefined ;
71- fetchUrl ?: string | undefined ;
72- fetchIdx ?: number | undefined ;
73- tags ?: string [ ] | undefined ;
74- } ;
75-
76- interface CacheHandlerValue {
77- lastModified ?: number ;
78- age ?: number ;
79- cacheState ?: string ;
80- value : IncrementalCacheValue | null ;
81- }
82-
8310function isFetchCache (
8411 options ?:
8512 | boolean
@@ -134,14 +61,15 @@ export default class Cache {
13461
13562 if ( cachedEntry ?. value === undefined ) return null ;
13663
137- const _lastModified = await globalThis . tagCache . getLastModified (
64+ const _tags = [ ...( tags ?? [ ] ) , ...( softTags ?? [ ] ) ] ;
65+ const _lastModified = cachedEntry . lastModified ?? Date . now ( ) ;
66+ const _hasBeenRevalidated = await hasBeenRevalidated (
13867 key ,
139- cachedEntry ?. lastModified ,
68+ _tags ,
69+ cachedEntry ,
14070 ) ;
141- if ( _lastModified === - 1 ) {
142- // If some tags are stale we need to force revalidation
143- return null ;
144- }
71+
72+ if ( _hasBeenRevalidated ) return null ;
14573
14674 // For cases where we don't have tags, we need to ensure that the soft tags are not being revalidated
14775 // We only need to check for the path as it should already contain all the tags
@@ -154,11 +82,12 @@ export default class Cache {
15482 ! tag . endsWith ( "page" ) ,
15583 ) ;
15684 if ( path ) {
157- const pathLastModified = await globalThis . tagCache . getLastModified (
85+ const hasPathBeenUpdated = await hasBeenRevalidated (
15886 path . replace ( "_N_T_/" , "" ) ,
159- cachedEntry . lastModified ,
87+ [ ] ,
88+ cachedEntry ,
16089 ) ;
161- if ( pathLastModified === - 1 ) {
90+ if ( hasPathBeenUpdated ) {
16291 // In case the path has been revalidated, we don't want to use the fetch cache
16392 return null ;
16493 }
@@ -184,20 +113,23 @@ export default class Cache {
184113 return null ;
185114 }
186115
187- const meta = cachedEntry . value . meta ;
188- const _lastModified = await globalThis . tagCache . getLastModified (
116+ const cacheData = cachedEntry . value ;
117+
118+ const meta = cacheData . meta ;
119+ const tags = getTagsFromValue ( cacheData ) ;
120+ const _lastModified = cachedEntry . lastModified ?? Date . now ( ) ;
121+ const _hasBeenRevalidated = await hasBeenRevalidated (
189122 key ,
190- cachedEntry ?. lastModified ,
123+ tags ,
124+ cachedEntry ,
191125 ) ;
192- if ( _lastModified === - 1 ) {
193- // If some tags are stale we need to force revalidation
194- return null ;
195- }
196- const cacheData = cachedEntry ?. value ;
126+ if ( _hasBeenRevalidated ) return null ;
127+
197128 const store = globalThis . __openNextAls . getStore ( ) ;
198129 if ( store ) {
199130 store . lastModified = _lastModified ;
200131 }
132+
201133 if ( cacheData ?. type === "route" ) {
202134 return {
203135 lastModified : _lastModified ,
@@ -363,32 +295,8 @@ export default class Cache {
363295 break ;
364296 }
365297 }
366- // Write derivedTags to dynamodb
367- // If we use an in house version of getDerivedTags in build we should use it here instead of next's one
368- const derivedTags : string [ ] =
369- data ?. kind === "FETCH"
370- ? ( ctx ?. tags ?? data ?. data ?. tags ?? [ ] ) // before version 14 next.js used data?.data?.tags so we keep it for backward compatibility
371- : data ?. kind === "PAGE"
372- ? ( data . headers ?. [ "x-next-cache-tags" ] ?. split ( "," ) ?? [ ] )
373- : [ ] ;
374- debug ( "derivedTags" , derivedTags ) ;
375- // Get all tags stored in dynamodb for the given key
376- // If any of the derived tags are not stored in dynamodb for the given key, write them
377- const storedTags = await globalThis . tagCache . getByPath ( key ) ;
378- const tagsToWrite = derivedTags . filter (
379- ( tag ) => ! storedTags . includes ( tag ) ,
380- ) ;
381- if ( tagsToWrite . length > 0 ) {
382- await globalThis . tagCache . writeTags (
383- tagsToWrite . map ( ( tag ) => ( {
384- path : key ,
385- tag : tag ,
386- // In case the tags are not there we just need to create them
387- // but we don't want them to return from `getLastModified` as they are not stale
388- revalidatedAt : 1 ,
389- } ) ) ,
390- ) ;
391- }
298+
299+ await this . updateTagsOnSet ( key , data , ctx ) ;
392300 debug ( "Finished setting cache" ) ;
393301 } catch ( e ) {
394302 error ( "Failed to set cache" , e ) ;
@@ -405,6 +313,29 @@ export default class Cache {
405313 }
406314 try {
407315 const _tags = Array . isArray ( tags ) ? tags : [ tags ] ;
316+ if ( globalThis . tagCache . mode === "nextMode" ) {
317+ const paths = ( await globalThis . tagCache . getPathsByTags ?.( _tags ) ) ?? [ ] ;
318+
319+ await globalThis . tagCache . writeTags ( _tags ) ;
320+ if ( paths . length > 0 ) {
321+ // TODO: we should introduce a new method in cdnInvalidationHandler to invalidate paths by tags for cdn that supports it
322+ // It also means that we'll need to provide the tags used in every request to the wrapper or converter.
323+ await globalThis . cdnInvalidationHandler . invalidatePaths (
324+ paths . map ( ( path ) => ( {
325+ initialPath : path ,
326+ rawPath : path ,
327+ resolvedRoutes : [
328+ {
329+ route : path ,
330+ // TODO: ideally here we should check if it's an app router page or route
331+ type : "app" ,
332+ } ,
333+ ] ,
334+ } ) ) ,
335+ ) ;
336+ }
337+ return ;
338+ }
408339 for ( const tag of _tags ) {
409340 debug ( "revalidateTag" , tag ) ;
410341 // Find all keys with the given tag
@@ -468,4 +399,46 @@ export default class Cache {
468399 error ( "Failed to revalidate tag" , e ) ;
469400 }
470401 }
402+
403+ // TODO: We should delete/update tags in this method
404+ // This will require an update to the tag cache interface
405+ private async updateTagsOnSet (
406+ key : string ,
407+ data ?: IncrementalCacheValue ,
408+ ctx ?: IncrementalCacheContext ,
409+ ) {
410+ if (
411+ globalThis . openNextConfig . dangerous ?. disableTagCache ||
412+ globalThis . tagCache . mode === "nextMode" ||
413+ // Here it means it's a delete
414+ ! data
415+ ) {
416+ return ;
417+ }
418+ // Write derivedTags to the tag cache
419+ // If we use an in house version of getDerivedTags in build we should use it here instead of next's one
420+ const derivedTags : string [ ] =
421+ data ?. kind === "FETCH"
422+ ? ( ctx ?. tags ?? data ?. data ?. tags ?? [ ] ) // before version 14 next.js used data?.data?.tags so we keep it for backward compatibility
423+ : data ?. kind === "PAGE"
424+ ? ( data . headers ?. [ "x-next-cache-tags" ] ?. split ( "," ) ?? [ ] )
425+ : [ ] ;
426+ debug ( "derivedTags" , derivedTags ) ;
427+
428+ // Get all tags stored in dynamodb for the given key
429+ // If any of the derived tags are not stored in dynamodb for the given key, write them
430+ const storedTags = await globalThis . tagCache . getByPath ( key ) ;
431+ const tagsToWrite = derivedTags . filter ( ( tag ) => ! storedTags . includes ( tag ) ) ;
432+ if ( tagsToWrite . length > 0 ) {
433+ await globalThis . tagCache . writeTags (
434+ tagsToWrite . map ( ( tag ) => ( {
435+ path : key ,
436+ tag : tag ,
437+ // In case the tags are not there we just need to create them
438+ // but we don't want them to return from `getLastModified` as they are not stale
439+ revalidatedAt : 1 ,
440+ } ) ) ,
441+ ) ;
442+ }
443+ }
471444}
0 commit comments