Skip to content

Commit 4139e99

Browse files
committed
support maxChunks config
1 parent d1cfc66 commit 4139e99

File tree

2 files changed

+63
-15
lines changed

2 files changed

+63
-15
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const cache = new IDBCache({
3232
cacheBuster: 'unique-cache-buster', // Doubles as salt
3333
// dbName?: string;
3434
// chunkSize?: number;
35+
// maxChunks?: number
3536
// cleanupInterval?: number;
3637
// pbkdf2Iterations?: number;
3738
// gcTime?: number;

packages/idb-cache/src/index.ts

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5057
export 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

Comments
 (0)