1
+ import type {
2
+ CacheHandlerValue ,
3
+ IncrementalCacheContext ,
4
+ IncrementalCacheValue ,
5
+ } from "types/cache" ;
6
+ import { getTagsFromValue , hasBeenRevalidated } from "utils/cache" ;
1
7
import { isBinaryContentType } from "../utils/binary" ;
2
8
import { debug , error , warn } from "./logger" ;
3
9
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
-
83
10
function isFetchCache (
84
11
options ?:
85
12
| boolean
@@ -134,14 +61,15 @@ export default class Cache {
134
61
135
62
if ( cachedEntry ?. value === undefined ) return null ;
136
63
137
- const _lastModified = await globalThis . tagCache . getLastModified (
64
+ const _tags = [ ...( tags ?? [ ] ) , ...( softTags ?? [ ] ) ] ;
65
+ const _lastModified = cachedEntry . lastModified ?? Date . now ( ) ;
66
+ const _hasBeenRevalidated = await hasBeenRevalidated (
138
67
key ,
139
- cachedEntry ?. lastModified ,
68
+ _tags ,
69
+ cachedEntry ,
140
70
) ;
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 ;
145
73
146
74
// For cases where we don't have tags, we need to ensure that the soft tags are not being revalidated
147
75
// We only need to check for the path as it should already contain all the tags
@@ -154,11 +82,12 @@ export default class Cache {
154
82
! tag . endsWith ( "page" ) ,
155
83
) ;
156
84
if ( path ) {
157
- const pathLastModified = await globalThis . tagCache . getLastModified (
85
+ const hasPathBeenUpdated = await hasBeenRevalidated (
158
86
path . replace ( "_N_T_/" , "" ) ,
159
- cachedEntry . lastModified ,
87
+ [ ] ,
88
+ cachedEntry ,
160
89
) ;
161
- if ( pathLastModified === - 1 ) {
90
+ if ( hasPathBeenUpdated ) {
162
91
// In case the path has been revalidated, we don't want to use the fetch cache
163
92
return null ;
164
93
}
@@ -184,20 +113,23 @@ export default class Cache {
184
113
return null ;
185
114
}
186
115
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 (
189
122
key ,
190
- cachedEntry ?. lastModified ,
123
+ tags ,
124
+ cachedEntry ,
191
125
) ;
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
+
197
128
const store = globalThis . __openNextAls . getStore ( ) ;
198
129
if ( store ) {
199
130
store . lastModified = _lastModified ;
200
131
}
132
+
201
133
if ( cacheData ?. type === "route" ) {
202
134
return {
203
135
lastModified : _lastModified ,
@@ -363,32 +295,8 @@ export default class Cache {
363
295
break ;
364
296
}
365
297
}
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 ) ;
392
300
debug ( "Finished setting cache" ) ;
393
301
} catch ( e ) {
394
302
error ( "Failed to set cache" , e ) ;
@@ -405,6 +313,29 @@ export default class Cache {
405
313
}
406
314
try {
407
315
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
+ }
408
339
for ( const tag of _tags ) {
409
340
debug ( "revalidateTag" , tag ) ;
410
341
// Find all keys with the given tag
@@ -468,4 +399,46 @@ export default class Cache {
468
399
error ( "Failed to revalidate tag" , e ) ;
469
400
}
470
401
}
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
+ }
471
444
}
0 commit comments