@@ -104,7 +104,12 @@ export default class S3Cache {
104
104
// fetchCache is for next 13.5 and above, kindHint is for next 14 and above and boolean is for earlier versions
105
105
options ?:
106
106
| boolean
107
- | { fetchCache ?: boolean ; kindHint ?: "app" | "pages" | "fetch" } ,
107
+ | {
108
+ fetchCache ?: boolean ;
109
+ kindHint ?: "app" | "pages" | "fetch" ;
110
+ tags ?: string [ ] ;
111
+ softTags ?: string [ ] ;
112
+ } ,
108
113
) {
109
114
if ( globalThis . disableIncrementalCache ) {
110
115
return null ;
@@ -115,13 +120,16 @@ export default class S3Cache {
115
120
? options . kindHint === "fetch"
116
121
: options . fetchCache
117
122
: options ;
123
+
124
+ const softTags = typeof options === "object" ? options . softTags : [ ] ;
125
+ const tags = typeof options === "object" ? options . tags : [ ] ;
118
126
return isFetchCache
119
- ? this . getFetchCache ( key )
127
+ ? this . getFetchCache ( key , softTags , tags )
120
128
: this . getIncrementalCache ( key ) ;
121
129
}
122
130
123
- async getFetchCache ( key : string ) {
124
- debug ( "get fetch cache" , { key } ) ;
131
+ async getFetchCache ( key : string , softTags ?: string [ ] , tags ?: string [ ] ) {
132
+ debug ( "get fetch cache" , { key, softTags , tags } ) ;
125
133
try {
126
134
const { value, lastModified } = await globalThis . incrementalCache . get (
127
135
key ,
@@ -139,6 +147,31 @@ export default class S3Cache {
139
147
140
148
if ( value === undefined ) return null ;
141
149
150
+ // For cases where we don't have tags, we need to ensure that we insert at least an entry
151
+ // for this specific paths, otherwise we might not be able to invalidate it
152
+ if ( ( tags ?? [ ] ) . length === 0 ) {
153
+ // First we check if we have any tags for the given key
154
+ const storedTags = await globalThis . tagCache . getByPath ( key ) ;
155
+ if ( storedTags . length === 0 ) {
156
+ // Then we need to find the path for the given key
157
+ const path = softTags ?. find (
158
+ ( tag ) =>
159
+ tag . startsWith ( "_N_T_/" ) &&
160
+ ! tag . endsWith ( "layout" ) &&
161
+ ! tag . endsWith ( "page" ) ,
162
+ ) ;
163
+ if ( path ) {
164
+ // And write the path with the tag
165
+ await globalThis . tagCache . writeTags ( [
166
+ {
167
+ path : key ,
168
+ tag : path ,
169
+ } ,
170
+ ] ) ;
171
+ }
172
+ }
173
+ }
174
+
142
175
return {
143
176
lastModified : _lastModified ,
144
177
value : value ,
@@ -317,22 +350,45 @@ export default class S3Cache {
317
350
}
318
351
}
319
352
320
- public async revalidateTag ( tag : string ) {
353
+ public async revalidateTag ( tags : string | string [ ] ) {
321
354
if ( globalThis . disableDynamoDBCache || globalThis . disableIncrementalCache ) {
322
355
return ;
323
356
}
324
357
try {
325
- debug ( "revalidateTag" , tag ) ;
326
- // Find all keys with the given tag
327
- const paths = await globalThis . tagCache . getByTag ( tag ) ;
328
- debug ( "Items" , paths ) ;
329
- // Update all keys with the given tag with revalidatedAt set to now
330
- await globalThis . tagCache . writeTags (
331
- paths ?. map ( ( path ) => ( {
332
- path : path ,
333
- tag : tag ,
334
- } ) ) ?? [ ] ,
335
- ) ;
358
+ const _tags = Array . isArray ( tags ) ? tags : [ tags ] ;
359
+ for ( const tag of _tags ) {
360
+ debug ( "revalidateTag" , tag ) ;
361
+ // Find all keys with the given tag
362
+ const paths = await globalThis . tagCache . getByTag ( tag ) ;
363
+ debug ( "Items" , paths ) ;
364
+ const toInsert = paths . map ( ( path ) => ( {
365
+ path,
366
+ tag,
367
+ } ) ) ;
368
+
369
+ // If the tag is a soft tag, we should also revalidate the hard tags
370
+ if ( tag . startsWith ( "_N_T_/" ) ) {
371
+ for ( const path of paths ) {
372
+ // We need to find all hard tags for a given path
373
+ const _tags = await globalThis . tagCache . getByPath ( path ) ;
374
+ const hardTags = _tags . filter ( ( t ) => ! t . startsWith ( "_N_T_/" ) ) ;
375
+ // For every hard tag, we need to find all paths and revalidate them
376
+ for ( const hardTag of hardTags ) {
377
+ const _paths = await globalThis . tagCache . getByTag ( hardTag ) ;
378
+ debug ( { hardTag, _paths } ) ;
379
+ toInsert . push (
380
+ ..._paths . map ( ( path ) => ( {
381
+ path,
382
+ tag : hardTag ,
383
+ } ) ) ,
384
+ ) ;
385
+ }
386
+ }
387
+ }
388
+
389
+ // Update all keys with the given tag with revalidatedAt set to now
390
+ await globalThis . tagCache . writeTags ( toInsert ) ;
391
+ }
336
392
} catch ( e ) {
337
393
error ( "Failed to revalidate tag" , e ) ;
338
394
}
0 commit comments