11import type { H3Event } from "h3" ;
22
33export default eventHandler ( async ( event ) => {
4+ setResponseHeader ( event , "Transfer-Encoding" , "chunked" ) ;
5+ setResponseHeader ( event , "Cache-Control" , "no-cache" ) ;
6+ setResponseHeader ( event , "Content-Type" , "text/plain" ) ;
7+
48 const rmStaleKeyHeader = getHeader ( event , "sb-rm-stale-key" ) ;
9+ const signal = toWebRequest ( event ) . signal ;
510 const { rmStaleKey } = useRuntimeConfig ( event ) ;
11+
612 if ( rmStaleKeyHeader !== rmStaleKey ) {
713 throw createError ( {
814 status : 403 ,
915 } ) ;
1016 }
11- return {
12- ok : true ,
13- removed : [
14- ...( await Promise . all ( [
15- iterateAndDelete ( event , {
16- prefix : usePackagesBucket . base ,
17- limit : 100 ,
18- } ) ,
19- iterateAndDelete ( event , {
20- prefix : useTemplatesBucket . base ,
21- limit : 100 ,
22- } ) ,
23- ] ) . then ( ( results ) => results . flat ( ) ) ) ,
24- ] ,
25- } ;
17+
18+ const { readable, writable } = new TransformStream ( )
19+
20+ event . waitUntil (
21+ ( async ( ) => {
22+ // const writer = writable.getWriter()
23+ // console.log('here')
24+ // await writer.ready
25+ // await writer.write(new TextEncoder().encode("start\n"))
26+ // writer.releaseLock()
27+
28+ await iterateAndDelete ( event , writable , signal , {
29+ prefix : usePackagesBucket . base ,
30+ limit : 100 ,
31+ } )
32+ await iterateAndDelete ( event , writable , signal , {
33+ prefix : useTemplatesBucket . base ,
34+ limit : 100 ,
35+ } )
36+ await writable . close ( )
37+ } ) ( )
38+ )
39+
40+ return readable
2641} ) ;
2742
28- async function iterateAndDelete ( event : H3Event , opts : R2ListOptions ) {
43+ async function iterateAndDelete ( event : H3Event , writable : WritableStream , signal : AbortSignal , opts : R2ListOptions ) {
44+ const writer = writable . getWriter ( )
45+ await writer . ready
2946 const binding = useBinding ( event ) ;
3047
3148 let truncated = true ;
@@ -34,34 +51,58 @@ async function iterateAndDelete(event: H3Event, opts: R2ListOptions) {
3451 const downloadedAtBucket = useDownloadedAtBucket ( event ) ;
3552 const today = Date . parse ( new Date ( ) . toString ( ) ) ;
3653
37- const removed : string [ ] = [ ] ;
38- while ( truncated ) {
54+ while ( truncated && ! signal . aborted ) {
3955 // TODO: Avoid using context.cloudflare and migrate to unstorage, but it does not have truncated for now
4056 const next = await binding . list ( {
4157 ...opts ,
4258 cursor,
4359 } ) ;
4460 for ( const object of next . objects ) {
61+ if ( signal . aborted ) {
62+ break ;
63+ }
4564 const uploaded = Date . parse ( object . uploaded . toString ( ) ) ;
4665 // remove the object anyway if it's 6 months old already
47- if ( ( today - uploaded ) / ( 1000 * 3600 * 24 * 30 * 6 ) >= 1 ) {
48- removed . push ( object . key ) ;
49- event . context . cloudflare . context . waitUntil ( binding . delete ( object . key ) ) ;
50- event . context . cloudflare . context . waitUntil (
51- downloadedAtBucket . removeItem ( object . key ) ,
52- ) ;
66+ // Use calendar-accurate 6 months check
67+ const uploadedDate = new Date ( uploaded ) ;
68+ const sixMonthsAgo = new Date ( today ) ;
69+ sixMonthsAgo . setMonth ( sixMonthsAgo . getMonth ( ) - 6 ) ;
70+ if ( uploadedDate <= sixMonthsAgo ) {
71+ const downloadedAt = ( await downloadedAtBucket . getItem ( object . key ) ) ! ;
72+ await writer . write ( new TextEncoder ( ) . encode ( JSON . stringify ( {
73+ key : object . key ,
74+ uploaded : new Date ( object . uploaded ) ,
75+ downloadedAt : downloadedAt ? new Date ( downloadedAt ) : null ,
76+ } ) + "\n" ) )
77+ // event.context.cloudflare.context.waitUntil(binding.delete(object.key));
78+ // event.context.cloudflare.context.waitUntil(
79+ // downloadedAtBucket.removeItem(object.key),
80+ // );
81+ }
82+ const downloadedAt = ( await downloadedAtBucket . getItem ( object . key ) ) ;
83+
84+ if ( ! downloadedAt ) {
85+ continue ;
5386 }
54- const downloadedAt = ( await downloadedAtBucket . getItem ( object . key ) ) ! ;
5587 // if it has not been downloaded in the last month and it's at least 1 month old
88+ // Calendar-accurate 1 month checks
89+ const downloadedAtDate = new Date ( downloadedAt ) ;
90+ const oneMonthAgo = new Date ( today ) ;
91+ oneMonthAgo . setMonth ( oneMonthAgo . getMonth ( ) - 1 ) ;
92+ const uploadedDate2 = new Date ( uploaded ) ; // uploaded already parsed above
5693 if (
57- ! ( ( today - downloadedAt ) / ( 1000 * 3600 * 24 * 30 ) < 1 ) &&
58- ( today - uploaded ) / ( 1000 * 3600 * 24 * 30 ) >= 1
94+ downloadedAtDate <= oneMonthAgo &&
95+ uploadedDate2 <= oneMonthAgo
5996 ) {
60- removed . push ( object . key ) ;
61- event . context . cloudflare . context . waitUntil ( binding . delete ( object . key ) ) ;
62- event . context . cloudflare . context . waitUntil (
63- downloadedAtBucket . removeItem ( object . key ) ,
64- ) ;
97+ await writer . write ( new TextEncoder ( ) . encode ( JSON . stringify ( {
98+ key : object . key ,
99+ uploaded : new Date ( object . uploaded ) ,
100+ downloadedAt : new Date ( downloadedAt ) ,
101+ } ) + "\n" ) )
102+ // event.context.cloudflare.context.waitUntil(binding.delete(object.key));
103+ // event.context.cloudflare.context.waitUntil(
104+ // downloadedAtBucket.removeItem(object.key),
105+ // );
65106 }
66107 }
67108
@@ -70,5 +111,5 @@ async function iterateAndDelete(event: H3Event, opts: R2ListOptions) {
70111 cursor = next . cursor ;
71112 }
72113 }
73- return removed ;
114+ writer . releaseLock ( )
74115}
0 commit comments