@@ -11,47 +11,55 @@ import { getLogger, getRequestContext } from './request-context.cjs'
11
11
12
12
const purgeCacheUserAgent = `${ nextRuntimePkgName } @${ nextRuntimePkgVersion } `
13
13
14
- /**
15
- * Get timestamp of the last revalidation for a tag
16
- */
17
- async function getTagRevalidatedAt (
14
+ async function getTagManifest (
18
15
tag : string ,
19
16
cacheStore : MemoizedKeyValueStoreBackedByRegionalBlobStore ,
20
- ) : Promise < number | null > {
17
+ ) : Promise < TagManifest | null > {
21
18
const tagManifest = await cacheStore . get < TagManifest > ( tag , 'tagManifest.get' )
22
19
if ( ! tagManifest ) {
23
20
return null
24
21
}
25
- return tagManifest . revalidatedAt
22
+ return tagManifest
26
23
}
27
24
28
25
/**
29
26
* Get the most recent revalidation timestamp for a list of tags
30
27
*/
31
- export async function getMostRecentTagRevalidationTimestamp ( tags : string [ ] ) {
28
+ export async function getMostRecentTagExpirationTimestamp ( tags : string [ ] ) {
32
29
if ( tags . length === 0 ) {
33
30
return 0
34
31
}
35
32
36
33
const cacheStore = getMemoizedKeyValueStoreBackedByRegionalBlobStore ( { consistency : 'strong' } )
37
34
38
- const timestampsOrNulls = await Promise . all (
39
- tags . map ( ( tag ) => getTagRevalidatedAt ( tag , cacheStore ) ) ,
40
- )
35
+ const timestampsOrNulls = await Promise . all ( tags . map ( ( tag ) => getTagManifest ( tag , cacheStore ) ) )
41
36
42
- const timestamps = timestampsOrNulls . filter ( ( timestamp ) => timestamp !== null )
43
- if ( timestamps . length === 0 ) {
37
+ const expirationTimestamps = timestampsOrNulls
38
+ . filter ( ( timestamp ) => timestamp !== null )
39
+ . map ( ( manifest ) => manifest . expiredAt )
40
+ if ( expirationTimestamps . length === 0 ) {
44
41
return 0
45
42
}
46
- return Math . max ( ...timestamps )
43
+ return Math . max ( ...expirationTimestamps )
47
44
}
48
45
46
+ export type TagStaleOrExpired =
47
+ // FRESH
48
+ | { stale : false ; expired : false }
49
+ // STALE
50
+ | { stale : true ; expired : false ; expireAt : number }
51
+ // EXPIRED (should be treated similarly to MISS)
52
+ | { stale : true ; expired : true }
53
+
49
54
/**
50
- * Check if any of the tags were invalidated since the given timestamp
55
+ * Check if any of the tags expired since the given timestamp
51
56
*/
52
- export function isAnyTagStale ( tags : string [ ] , timestamp : number ) : Promise < boolean > {
57
+ export function isAnyTagStaleOrExpired (
58
+ tags : string [ ] ,
59
+ timestamp : number ,
60
+ ) : Promise < TagStaleOrExpired > {
53
61
if ( tags . length === 0 || ! timestamp ) {
54
- return Promise . resolve ( false )
62
+ return Promise . resolve ( { stale : false , expired : false } )
55
63
}
56
64
57
65
const cacheStore = getMemoizedKeyValueStoreBackedByRegionalBlobStore ( { consistency : 'strong' } )
@@ -60,37 +68,74 @@ export function isAnyTagStale(tags: string[], timestamp: number): Promise<boolea
60
68
// but we will only do actual blob read once withing a single request due to cacheStore
61
69
// memoization.
62
70
// Additionally, we will resolve the promise as soon as we find first
63
- // stale tag, so that we don't wait for all of them to resolve (but keep all
71
+ // expired tag, so that we don't wait for all of them to resolve (but keep all
64
72
// running in case future `CacheHandler.get` calls would be able to use results).
65
- // "Worst case" scenario is none of tag was invalidated in which case we need to wait
66
- // for all blob store checks to finish before we can be certain that no tag is stale .
67
- return new Promise < boolean > ( ( resolve , reject ) => {
68
- const tagManifestPromises : Promise < boolean > [ ] = [ ]
73
+ // "Worst case" scenario is none of tag was expired in which case we need to wait
74
+ // for all blob store checks to finish before we can be certain that no tag is expired .
75
+ return new Promise < TagStaleOrExpired > ( ( resolve , reject ) => {
76
+ const tagManifestPromises : Promise < TagStaleOrExpired > [ ] = [ ]
69
77
70
78
for ( const tag of tags ) {
71
- const lastRevalidationTimestampPromise = getTagRevalidatedAt ( tag , cacheStore )
79
+ const tagManifestPromise = getTagManifest ( tag , cacheStore )
72
80
73
81
tagManifestPromises . push (
74
- lastRevalidationTimestampPromise . then ( ( lastRevalidationTimestamp ) => {
75
- if ( ! lastRevalidationTimestamp ) {
82
+ tagManifestPromise . then ( ( tagManifest ) => {
83
+ if ( ! tagManifest ) {
76
84
// tag was never revalidated
77
- return false
85
+ return { stale : false , expired : false }
86
+ }
87
+ const stale = tagManifest . staleAt >= timestamp
88
+ const expired = tagManifest . expiredAt >= timestamp && tagManifest . expiredAt <= Date . now ( )
89
+
90
+ if ( expired && stale ) {
91
+ const expiredResult : TagStaleOrExpired = {
92
+ stale,
93
+ expired,
94
+ }
95
+ // resolve outer promise immediately if any of the tags is expired
96
+ resolve ( expiredResult )
97
+ return expiredResult
78
98
}
79
- const isStale = lastRevalidationTimestamp >= timestamp
80
- if ( isStale ) {
81
- // resolve outer promise immediately if any of the tags is stale
82
- resolve ( true )
83
- return true
99
+
100
+ if ( stale ) {
101
+ const staleResult : TagStaleOrExpired = {
102
+ stale,
103
+ expired,
104
+ expireAt : tagManifest . expiredAt ,
105
+ }
106
+ return staleResult
84
107
}
85
- return false
108
+ return { stale : false , expired : false }
86
109
} ) ,
87
110
)
88
111
}
89
112
90
- // make sure we resolve promise after all blobs are checked (if we didn't resolve as stale yet)
113
+ // make sure we resolve promise after all blobs are checked (if we didn't resolve as expired yet)
91
114
Promise . all ( tagManifestPromises )
92
- . then ( ( tagManifestAreStale ) => {
93
- resolve ( tagManifestAreStale . some ( ( tagIsStale ) => tagIsStale ) )
115
+ . then ( ( tagManifestsAreStaleOrExpired ) => {
116
+ let result : TagStaleOrExpired = { stale : false , expired : false }
117
+
118
+ for ( const tagResult of tagManifestsAreStaleOrExpired ) {
119
+ if ( tagResult . expired ) {
120
+ // if any of the tags is expired, the whole thing is expired
121
+ result = tagResult
122
+ break
123
+ }
124
+
125
+ if ( tagResult . stale ) {
126
+ result = {
127
+ stale : true ,
128
+ expired : false ,
129
+ expireAt :
130
+ // make sure to use expireAt that is lowest of all tags
131
+ result . stale && ! result . expired && typeof result . expireAt === 'number'
132
+ ? Math . min ( result . expireAt , tagResult . expireAt )
133
+ : tagResult . expireAt ,
134
+ }
135
+ }
136
+ }
137
+
138
+ resolve ( result )
94
139
} )
95
140
. catch ( reject )
96
141
} )
@@ -122,15 +167,21 @@ export function purgeEdgeCache(tagOrTags: string | string[]): Promise<void> {
122
167
} )
123
168
}
124
169
125
- async function doRevalidateTagAndPurgeEdgeCache ( tags : string [ ] ) : Promise < void > {
126
- getLogger ( ) . withFields ( { tags } ) . debug ( 'doRevalidateTagAndPurgeEdgeCache' )
170
+ async function doRevalidateTagAndPurgeEdgeCache (
171
+ tags : string [ ] ,
172
+ durations ?: { expire ?: number } ,
173
+ ) : Promise < void > {
174
+ getLogger ( ) . withFields ( { tags, durations } ) . debug ( 'doRevalidateTagAndPurgeEdgeCache' )
127
175
128
176
if ( tags . length === 0 ) {
129
177
return
130
178
}
131
179
180
+ const now = Date . now ( )
181
+
132
182
const tagManifest : TagManifest = {
133
- revalidatedAt : Date . now ( ) ,
183
+ staleAt : now ,
184
+ expiredAt : now + ( durations ?. expire ? durations . expire * 1000 : 0 ) ,
134
185
}
135
186
136
187
const cacheStore = getMemoizedKeyValueStoreBackedByRegionalBlobStore ( { consistency : 'strong' } )
@@ -148,10 +199,13 @@ async function doRevalidateTagAndPurgeEdgeCache(tags: string[]): Promise<void> {
148
199
await purgeEdgeCache ( tags )
149
200
}
150
201
151
- export function markTagsAsStaleAndPurgeEdgeCache ( tagOrTags : string | string [ ] ) {
202
+ export function markTagsAsStaleAndPurgeEdgeCache (
203
+ tagOrTags : string | string [ ] ,
204
+ durations ?: { expire ?: number } ,
205
+ ) {
152
206
const tags = getCacheTagsFromTagOrTags ( tagOrTags )
153
207
154
- const revalidateTagPromise = doRevalidateTagAndPurgeEdgeCache ( tags )
208
+ const revalidateTagPromise = doRevalidateTagAndPurgeEdgeCache ( tags , durations )
155
209
156
210
const requestContext = getRequestContext ( )
157
211
if ( requestContext ) {
0 commit comments