diff --git a/spec/indexeddb_spec.js b/spec/indexeddb_spec.js index 7470b59..0a83b99 100644 --- a/spec/indexeddb_spec.js +++ b/spec/indexeddb_spec.js @@ -3,7 +3,7 @@ import { openDB, deleteDB } from "../scripts/idb/index.js" const MAX_TIMESTAMP_DIFFERENCE = 3 -describe("sanity check", () => { +xdescribe("sanity check", () => { it("works", () => { expect(true).toBeTrue() }) @@ -38,6 +38,13 @@ describe("SlopDB", () => { describe("version 2", () => { let slopdb + let mock_clock + + beforeAll(() => { + const j_clock = jasmine.clock() + mock_clock = j_clock.install() + mock_clock.mockDate(new Date(2020, 1, 1)) + }) beforeEach(async () => { slopdb = new SlopDB(2) @@ -60,17 +67,20 @@ describe("SlopDB", () => { const slop_url = new URL("https://sloppy-slop.com/sloparticle") - await cache.store(slop_url.toString()) + const new_item = await cache.store(slop_url.toString()) const store_time = Date.now() + + mock_clock.tick(1000) const cached_item = await cache.get(slop_url.toString()) expect(cached_item.url).toEqual(slop_url.toString()) - expect(cached_item.check_timestamp).toBeLessThanOrEqual(store_time) - expect(cached_item.check_timestamp).toBeGreaterThan(store_time - MAX_TIMESTAMP_DIFFERENCE) + expect(new_item.check_timestamp).toBeCloseTo(store_time) + expect(new_item.check_timestamp).toBeGreaterThan(store_time - MAX_TIMESTAMP_DIFFERENCE) }) it("updates a cached url's timestamp when it is accessed", async () => { - const mock_clock = clock.install() + + const cache = slopdb.get_check_cache() const slop_url = new URL("https://sloppy-slop.com/sloparticle") @@ -79,8 +89,30 @@ describe("SlopDB", () => { mock_clock.tick(1000 * 30) - const cached_item = cache.get(slop_url.toString()) - expect(cached_item.check_timestamp).toEqual(store_time + 30) + const cached_item = await cache.get(slop_url.toString()) + expect(cached_item.check_timestamp).toBeGreaterThan(store_time + 10) + }) + + it("evicts the least recently accessed URL when an item is added to a full cache", async () => { + const cache = slopdb.get_check_cache() + cache.cache_capacity = 2 + + const slop_url = new URL("https://sloppy-slop.com/sloparticle") + await cache.store(slop_url.toString()) + + mock_clock.tick(1000) + + const slop_url2 = new URL("https://sloppy-slop.com/sloparticle2") + await cache.store(slop_url2.toString()) + + mock_clock.tick(1000) + + const slop_url3 = new URL("https://sloppy-slop.com/sloparticle3") + await cache.store(slop_url3.toString()) + + const get_slop1 = await cache.get(slop_url.toString()) + expect(get_slop1).toBeUndefined() + expect(cache.size).toEqual(2) }) }) diff --git a/src/indexed-db.ts b/src/indexed-db.ts index 283d642..ea75547 100644 --- a/src/indexed-db.ts +++ b/src/indexed-db.ts @@ -1,4 +1,4 @@ -import { openDB, IDBPDatabase } from "idb/index.js" +import { openDB, IDBPDatabase, unwrap } from "idb/index.js" export class IDBCursorValueIterator { cursor: IDBCursorWithValue @@ -37,11 +37,13 @@ export class IDBCursorValueIterator { export class CheckCache { slopdb: SlopDB cache_capacity: number + size: number static cache_objectstore_name = "checkcache" constructor(slopdb: SlopDB, max_entries: number) { this.slopdb = slopdb this.cache_capacity = max_entries + this.size = 0 } cache_item_factory(url: string) { @@ -53,43 +55,31 @@ export class CheckCache { async store(url: string) { const cache_store = this.slopdb.db.transaction(CheckCache.cache_objectstore_name, "readwrite").objectStore(CheckCache.cache_objectstore_name) - await cache_store.add(this.cache_item_factory(url)) + const cache_item = this.cache_item_factory(url) + cache_store.add(cache_item) + this.size++ + if(this.size > this.cache_capacity) { + this.evict_lru() + } + return cache_item } async get(url: string) { const cache_store = this.slopdb.db.transaction(CheckCache.cache_objectstore_name, "readwrite").objectStore(CheckCache.cache_objectstore_name) - return cache_store.get(url) + const cache_item = await cache_store.get(url) + if(cache_item) { + cache_item.check_timestamp = Date.now() + await cache_store.put(cache_item) + } + return cache_item } - // async evict_least_recently_checked(count: number) { - // const transaction = this.slopdb.start_transaction(CheckCache.cache_objectstore_name, "readwrite") - // const cache_objectstore = transaction.objectStore(CheckCache.cache_objectstore_name) - - // const cursor_result_promise = new Promise>((resolve, reject) => { - // const cache_cursor_request = cache_objectstore.openCursor() - - // cache_cursor_request.onerror = (error) => { - // reject(error) - // } - - // cache_cursor_request.onsuccess = (event) => { - // const cursor = cache_cursor_request.result - // resolve(new IDBCursorValueIterator(cursor)) - // } - // }) - - // const cursor = await cursor_result_promise - - // const key_array = Array.from(cursor) - // key_array.sort((a, b) => { - // const a_datetime = a.check_timestamp - // const b_datetime = b.check_timestamp - - // return a_datetime.getTime - b_datetime.getTime - // }) - - - // } + async evict_lru() { + const timestamp_index = this.slopdb.db.transaction(CheckCache.cache_objectstore_name, "readwrite").store.index("timestamp") + const timestamp_cursor = await timestamp_index.openCursor() + await timestamp_cursor.delete() + this.size-- + } } export class SlopDB { @@ -103,7 +93,8 @@ export class SlopDB { db.createObjectStore("slop", { keyPath: "domain" }) break case 2: - db.createObjectStore("checkcache", { keyPath: "url" }) + const store = db.createObjectStore("checkcache", { keyPath: "url" }) + store.createIndex("timestamp", "check_timestamp") break } }