diff --git a/src/getter.js b/src/getter.js index 0e795ad..c09a3da 100644 --- a/src/getter.js +++ b/src/getter.js @@ -2,23 +2,34 @@ import { log, canUseCache } from './utils.js' var db -function openDB(config) { +function openCache(config) { if (config.cache && typeof window !== 'undefined') { - const request = window.indexedDB.open("pokeapi-js-wrapper", 3); + const request = window.indexedDB.open("pokeapi-js-wrapper", 8); return new Promise((resolve, reject) => { request.onerror = (event) => { log('IndexedDB not available') reject() } request.onupgradeneeded = (event) => { - db = event.target.result; - log('db opened and cache created') - db.createObjectStore("cache", { autoIncrement: false }); - resolve(db) + const db = event.target.result; + const transaction = event.target.transaction; + let objectStore; + + if (!db.objectStoreNames.contains('cache')) { + objectStore = db.createObjectStore("cache", { autoIncrement: false }); + log('Object store "cache" created'); + } else { + objectStore = transaction.objectStore("cache"); + } + + if (!objectStore.indexNames.contains("deploy_date_index")) { + objectStore.createIndex("deploy_date_index", "meta.deploy_date", { unique: false }); + log('Index "deploy_date_index" created'); + } } request.onsuccess = (event) => { - log('db opened') db = event.target.result; + log('db opened') resolve(db) } request.onversionchange = (event) => { @@ -44,7 +55,7 @@ function getFromDB(objectStore, url) { async function loadResource(config, url) { if (! url.includes('://')) { url = url.replace(/^\//, ''); - url = `${config.protocol}://${config.hostName}/${url}` + url = `${config.protocol}://${config.hostName}${config.versionPath}${url}` } if (canUseCache(config, db)) { const transaction = db.transaction("cache", "readonly"); @@ -66,10 +77,14 @@ async function loadUrl(config, url) { const body = await response.json() if (response.status === 200) { if (canUseCache(config, db)) { + const deploy_date = parseInt(response.headers.get('X-PokeAPI-Deploy-Date')) + body.meta = { deploy_date } const transaction = db.transaction("cache", "readwrite"); const objectStore = transaction.objectStore("cache"); const request = objectStore.add(body, url) - request.onsuccess = () => log(`object cached ${url}`); + request.onsuccess = () => { + log(`object cached ${url}`); + } request.onerror = () => { log(request.error) } @@ -79,7 +94,7 @@ async function loadUrl(config, url) { return body } -function sizeDB(config) { +function sizeCache(config) { if (canUseCache(config, db)) { return new Promise((resolve, reject) => { const transaction = db.transaction("cache", "readwrite"); @@ -93,7 +108,32 @@ function sizeDB(config) { } } -function clearDB(config) { +async function invalidateCache(config) { + if (canUseCache(config, db)) { + const meta = await loadResource({...config, cache: false}, 'meta') + const upstream_deploy_date = parseInt(meta.deploy_date) + const transaction = db.transaction("cache", "readwrite"); + const objectStore = transaction.objectStore("cache"); + const index = objectStore.index("deploy_date_index") + const range = IDBKeyRange.upperBound(upstream_deploy_date, true); + const request = index.getAllKeys(range); + + request.onsuccess = () => { + const keys = request.result; + keys.forEach(pk => { + objectStore.delete(pk); + log(`invalidated ${pk}`); + }); + return true + }; + request.onerror = () => {throw new Error(request.error); + }; + } else { + throw new Error('cache not available') + } +} + +function clearCache(config) { if (canUseCache(config, db)) { return new Promise((resolve, reject) => { const transaction = db.transaction("cache", "readwrite"); @@ -107,4 +147,4 @@ function clearDB(config) { } } -export { loadResource, openDB, sizeDB, clearDB } +export { loadResource, openCache, sizeCache, clearCache, invalidateCache } diff --git a/src/index.js b/src/index.js index 593f0f8..59c7ed5 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ import endpoints from './endpoints.json' with { type: "json" } import rootEndpoints from './rootEndpoints.json' with { type: "json" } -import { loadResource, openDB, sizeDB, clearDB } from './getter.js' +import { loadResource, openCache, sizeCache, clearCache, invalidateCache } from './getter.js' import { Config } from './config.js' export class Pokedex { @@ -18,7 +18,7 @@ export class Pokedex { // if the user has submitted a Name or an ID, return the JSON promise if (typeof input === 'number' || typeof input === 'string') { - return loadResource(this.config, `${this.config.versionPath}${endpoint[2].replace(':id', input)}`) + return loadResource(this.config, `${endpoint[2].replace(':id', input)}`) } // if the user has submitted an Array @@ -44,7 +44,7 @@ export class Pokedex { limit = config.limit } } - return loadResource(this.config, `${this.config.versionPath}${rootEndpoint[1]}?limit=${limit}&offset=${offset}`) + return loadResource(this.config, `${rootEndpoint[1]}?limit=${limit}&offset=${offset}`) } this[rootEndpoint[0]] = this[rootEndpointFullName] }) @@ -56,7 +56,7 @@ export class Pokedex { static async init(config) { config = new Config(config) - await openDB(config) + await openCache(config) return new Pokedex(config) } @@ -72,6 +72,10 @@ export class Pokedex { return clearDB(this.config) } + invalidateCache() { + return invalidateCache(this.config) + } + resource(path) { if (typeof path === 'string') { return loadResource(this.config, path) @@ -85,7 +89,7 @@ export class Pokedex { function mapResources(config, endpoint, inputs) { return inputs.map(input => { - return loadResource(config, `${config.versionPath}${endpoint[2].replace(':id', input)}`) + return loadResource(config, `${endpoint[2].replace(':id', input)}`) }) } diff --git a/test/test.html.js b/test/test.html.js index 64f2007..2a9dc22 100644 --- a/test/test.html.js +++ b/test/test.html.js @@ -42,16 +42,17 @@ describe("pokedex", function () { describe(".resource(Mixed: array) not cached", function () { it("should have property name", async function () { - const res = await customP.resource(['/api/v2/pokemon/36', 'api/v2/berry/8', 'https://pokeapi.co/api/v2/ability/9/']); + const res = await customP.resource(['pokemon/37', '/pokemon/36', '/berry/8', 'https://pokeapi.co/api/v2/ability/9/']); expect(res[0]).to.have.property('name'); expect(res[1]).to.have.property('name'); expect(res[2]).to.have.property('name'); + expect(res[3]).to.have.property('name'); }); }); describe(".resource(Path: string)", function () { it("should have property height", async function () { - const res = await defaultP.resource('/api/v2/pokemon/34'); + const res = await defaultP.resource('pokemon/34'); expect(res).to.have.property('height'); }); });