@@ -2,11 +2,12 @@ import {
22 S3Client ,
33 GetObjectCommand ,
44 PutObjectCommand ,
5+ DeleteObjectsCommand ,
56 PutObjectCommandInput ,
67 ListObjectsV2Command ,
78} from "@aws-sdk/client-s3" ;
89import path from "node:path" ;
9- import { error , awsLogger } from "./logger.js" ;
10+ import { awsLogger , debug , error } from "./logger.js" ;
1011import { loadBuildId } from "./util.js" ;
1112
1213interface CachedFetchValue {
@@ -75,7 +76,14 @@ interface CacheHandlerValue {
7576 value : IncrementalCacheValue | null ;
7677}
7778
78- type Extension = "json" | "html" | "rsc" | "body" | "meta" | "fetch" ;
79+ type Extension =
80+ | "json"
81+ | "html"
82+ | "rsc"
83+ | "body"
84+ | "meta"
85+ | "fetch"
86+ | "redirect" ;
7987
8088// Expected environment variables
8189const { CACHE_BUCKET_NAME , CACHE_BUCKET_KEY_PREFIX , CACHE_BUCKET_REGION } =
@@ -100,6 +108,7 @@ export default class S3Cache {
100108 }
101109
102110 async getFetchCache ( key : string ) {
111+ debug ( "get fetch cache" , { key } ) ;
103112 try {
104113 const { Body, LastModified } = await this . getS3Object ( key , "fetch" ) ;
105114 return {
@@ -113,10 +122,12 @@ export default class S3Cache {
113122 }
114123
115124 async getIncrementalCache ( key : string ) : Promise < CacheHandlerValue | null > {
116- const { Contents } = await this . listS3Objects ( key ) ;
117- const keys = ( Contents ?? [ ] ) . map ( ( { Key } ) => Key ) ;
125+ const keys = await this . listS3Object ( key ) ;
126+ if ( keys . length === 0 ) return null ;
127+ debug ( "keys" , keys ) ;
118128
119129 if ( keys . includes ( this . buildS3Key ( key , "body" ) ) ) {
130+ debug ( "get body cache " , { key } ) ;
120131 try {
121132 const [ { Body, LastModified } , { Body : MetaBody } ] = await Promise . all ( [
122133 this . getS3Object ( key , "body" ) ,
@@ -143,6 +154,7 @@ export default class S3Cache {
143154 if ( keys . includes ( this . buildS3Key ( key , "html" ) ) ) {
144155 const isJson = keys . includes ( this . buildS3Key ( key , "json" ) ) ;
145156 const isRsc = keys . includes ( this . buildS3Key ( key , "rsc" ) ) ;
157+ debug ( "get html cache " , { key, isJson, isRsc } ) ;
146158 if ( ! isJson && ! isRsc ) return null ;
147159
148160 try {
@@ -166,10 +178,27 @@ export default class S3Cache {
166178 }
167179 return null ;
168180 }
181+
182+ // Check for redirect last. This way if a page has been regenerated
183+ // after having been redirected, we'll get the page data
184+ if ( keys . includes ( this . buildS3Key ( key , "redirect" ) ) ) {
185+ debug ( "get redirect cache" , { key } ) ;
186+ try {
187+ const { Body, LastModified } = await this . getS3Object ( key , "redirect" ) ;
188+ return {
189+ lastModified : LastModified ?. getTime ( ) ,
190+ value : JSON . parse ( ( await Body ?. transformToString ( ) ) ?? "{}" ) ,
191+ } ;
192+ } catch ( e ) {
193+ error ( "Failed to get redirect cache" , e ) ;
194+ }
195+ return null ;
196+ }
197+
169198 return null ;
170199 }
171200
172- async set ( key : string , data ?: IncrementalCacheValue ) : Promise < void > {
201+ async set ( key : string , data ?: IncrementalCacheValue | null ) : Promise < void > {
173202 if ( data ?. kind === "ROUTE" ) {
174203 const { body, status, headers } = data ;
175204 await Promise . all ( [
@@ -189,6 +218,12 @@ export default class S3Cache {
189218 ] ) ;
190219 } else if ( data ?. kind === "FETCH" ) {
191220 await this . putS3Object ( key , "fetch" , JSON . stringify ( data ) ) ;
221+ } else if ( data ?. kind === "REDIRECT" ) {
222+ // delete potential page data if we're redirecting
223+ await this . deleteS3Objects ( key ) ;
224+ await this . putS3Object ( key , "redirect" , JSON . stringify ( data ) ) ;
225+ } else if ( data === null || data === undefined ) {
226+ await this . deleteS3Objects ( key ) ;
192227 }
193228 }
194229
@@ -205,13 +240,16 @@ export default class S3Cache {
205240 return path . posix . join ( CACHE_BUCKET_KEY_PREFIX ?? "" , this . buildId , key ) ;
206241 }
207242
208- private listS3Objects ( key : string ) {
209- return this . client . send (
243+ private async listS3Object ( key : string ) {
244+ const { Contents } = await this . client . send (
210245 new ListObjectsV2Command ( {
211246 Bucket : CACHE_BUCKET_NAME ,
212- Prefix : this . buildS3KeyPrefix ( key ) ,
247+ // add a point to the key so that it only matches the key and
248+ // not other keys starting with the same string
249+ Prefix : `${ this . buildS3KeyPrefix ( key ) } .` ,
213250 } )
214251 ) ;
252+ return ( Contents ?? [ ] ) . map ( ( { Key } ) => Key ) ;
215253 }
216254
217255 private getS3Object ( key : string , extension : Extension ) {
@@ -236,4 +274,24 @@ export default class S3Cache {
236274 } )
237275 ) ;
238276 }
277+
278+ private async deleteS3Objects ( key : string ) {
279+ try {
280+ const regex = new RegExp ( `\.(json|rsc|html|body|meta|fetch|redirect)$` ) ;
281+ const s3Keys = ( await this . listS3Object ( key ) ) . filter (
282+ ( key ) => key && regex . test ( key )
283+ ) ;
284+
285+ await this . client . send (
286+ new DeleteObjectsCommand ( {
287+ Bucket : CACHE_BUCKET_NAME ,
288+ Delete : {
289+ Objects : s3Keys . map ( ( Key ) => ( { Key } ) ) ,
290+ } ,
291+ } )
292+ ) ;
293+ } catch ( e ) {
294+ error ( "Failed to delete cache" , e ) ;
295+ }
296+ }
239297}
0 commit comments