@@ -45,6 +45,13 @@ interface IDBCacheConfig {
4545 debug ?: boolean ;
4646 pbkdf2Iterations ?: number ;
4747 gcTime ?: number ;
48+ /**
49+ * The maximum number of chunks to store in the cache.
50+ * If set, during cleanup intervals, the cache will ensure that no more than maxChunks are stored.
51+ * Excess oldest chunks will be removed to enforce this limit.
52+ * Defaults to undefined, meaning no limit.
53+ */
54+ maxChunks ?: number ;
4855}
4956
5057export interface AsyncStorage {
@@ -73,6 +80,7 @@ export class IDBCache implements AsyncStorage {
7380 private pbkdf2Iterations : number ;
7481 private cacheBuster : string ;
7582 private debug : boolean ;
83+ private maxChunks ?: number ;
7684
7785 constructor ( config : IDBCacheConfig ) {
7886 const {
@@ -84,16 +92,18 @@ export class IDBCache implements AsyncStorage {
8492 chunkSize = DEFAULT_CHUNK_SIZE ,
8593 cleanupInterval = CLEANUP_INTERVAL ,
8694 pbkdf2Iterations = DEFAULT_PBKDF2_ITERATIONS ,
95+ maxChunks,
8796 } = config ;
8897
8998 this . storeName = "cache" ;
9099 this . cacheKey = cacheKey ;
100+ this . cacheBuster = cacheBuster ;
91101 this . debug = debug ;
92102 this . gcTime = gcTime ;
93103 this . chunkSize = chunkSize ;
94104 this . cleanupInterval = cleanupInterval ;
95105 this . pbkdf2Iterations = pbkdf2Iterations ;
96- this . cacheBuster = cacheBuster ;
106+ this . maxChunks = maxChunks ;
97107 this . pendingRequests = new Map ( ) ;
98108
99109 if ( ! window . indexedDB )
@@ -108,14 +118,17 @@ export class IDBCache implements AsyncStorage {
108118 DB_VERSION
109119 ) ;
110120
111- this . cleanupIntervalId = window . setInterval (
112- this . cleanupExpiredItems . bind ( this ) ,
113- this . cleanupInterval
114- ) ;
121+ this . cleanupIntervalId = window . setInterval ( async ( ) => {
122+ try {
123+ await this . cleanupCache ( ) ; // Call the consolidated cleanupCache
124+ } catch ( error ) {
125+ console . error ( "Error during cleanup:" , error ) ;
126+ }
127+ } , this . cleanupInterval ) ;
115128
116129 this . initWorker ( cacheKey , cacheBuster )
117130 . then ( ( ) => {
118- this . cleanupExpiredItems ( ) . catch ( ( error ) =>
131+ this . cleanupCache ( ) . catch ( ( error ) =>
119132 console . error ( "Initial cleanup failed:" , error )
120133 ) ;
121134 this . flushBustedCacheItems ( ) . catch ( ( error ) =>
@@ -232,42 +245,76 @@ export class IDBCache implements AsyncStorage {
232245 }
233246
234247 /**
235- * Cleans up expired items from the IndexedDB store based on their timestamps.
248+ * Cleans up the cache by removing expired items and enforcing the maxChunks limit.
249+ * This method consolidates the functionality of cleanupExpiredItems and cleanupExcessChunks.
236250 * @throws {DatabaseError } If there is an issue accessing the database.
237251 */
238- private async cleanupExpiredItems ( ) {
252+ private async cleanupCache ( ) : Promise < void > {
239253 try {
240254 const db = await this . dbReadyPromise ;
241255 const transaction = db . transaction ( this . storeName , "readwrite" ) ;
242256 const store = transaction . store ;
243- const index = store . index ( "byTimestamp" ) ;
257+ const timestampIndex = store . index ( "byTimestamp" ) ;
258+ const cacheBusterIndex = store . index ( "byCacheBuster" ) ;
244259 const now = Date . now ( ) ;
245260
246- let cursor = await index . openCursor ( ) ;
247-
261+ // 1. Remove expired items
262+ let cursor = await timestampIndex . openCursor ( ) ;
248263 while ( cursor ) {
249264 const { timestamp } = cursor . value ;
250265 if ( timestamp <= now ) {
251266 const age = now - timestamp ;
252267 if ( this . debug ) {
253268 console . debug (
254- `Deleting item with timestamp ${ timestamp } . It is ${ age } ms older than the expiration.`
269+ `Deleting expired item with timestamp ${ timestamp } . It is ${ age } ms older than the expiration.`
255270 ) ;
256271 }
257272 await cursor . delete ( ) ;
258273 } else {
259- break ;
274+ break ; // Since the index is ordered, no need to check further
260275 }
261276 cursor = await cursor . continue ( ) ;
262277 }
263278
279+ // 2. Enforce maxChunks limit
280+ if ( this . maxChunks !== undefined ) {
281+ const totalChunks = await store . count ( ) ;
282+ if ( totalChunks > this . maxChunks ) {
283+ const excess = totalChunks - this . maxChunks ;
284+ if ( this . debug ) {
285+ console . debug (
286+ `Total chunks (${ totalChunks } ) exceed maxChunks (${ this . maxChunks } ). Deleting ${ excess } oldest chunks.`
287+ ) ;
288+ }
289+
290+ let excessDeleted = 0 ;
291+ let excessCursor = await timestampIndex . openCursor ( null , "next" ) ; // Ascending order (oldest first)
292+
293+ while ( excessCursor && excessDeleted < excess ) {
294+ await excessCursor . delete ( ) ;
295+ excessDeleted ++ ;
296+ excessCursor = await excessCursor . continue ( ) ;
297+ }
298+
299+ if ( this . debug ) {
300+ console . debug (
301+ `Deleted ${ excessDeleted } oldest chunks to enforce maxChunks.`
302+ ) ;
303+ }
304+ } else if ( this . debug ) {
305+ console . debug (
306+ `Total chunks (${ totalChunks } ) within maxChunks (${ this . maxChunks } ). No excess cleanup needed.`
307+ ) ;
308+ }
309+ }
310+
264311 await transaction . done ;
265312 } catch ( error ) {
266- console . error ( "Error during cleanupExpiredItems :" , error ) ;
313+ console . error ( "Error during cleanupCache :" , error ) ;
267314 if ( error instanceof DatabaseError ) {
268315 throw error ;
269316 }
270- throw new DatabaseError ( "Failed to clean up expired items ." ) ;
317+ throw new DatabaseError ( "Failed to clean up the cache ." ) ;
271318 }
272319 }
273320
0 commit comments