Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API methods for cache storage #7

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ await get("Hello"); // "World!"

## Overview

The main purpose of **Storage** is to provide a set of adapters that normalize across various client side storage mechanisms (`localStorage` / `sessionStorage`, IndexedDB, cookies, and OPFS) with a consistent key-value API (`get()`, `set()`, etc).
The main purpose of **Storage** is to provide a set of adapters that normalize across various client side storage mechanisms (`localStorage` / `sessionStorage`, IndexedDB, cookies, OPFS, and cache) with a consistent key-value API (`get()`, `set()`, etc).

## Client Side Storage Adapters

Expand All @@ -44,6 +44,8 @@ The main purpose of **Storage** is to provide a set of adapters that normalize a

**Warning:** Web workers in some cases require modified security settings (for the site/app) -- for example, [a Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), specifically [the `worker-src` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src).

* `cache`: [CacheStorage](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage) and [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache)

Each of these client-side storage mechanisms has its own pros/cons, so choice should be made carefully.

However, IndexedDB (`idb` adapter) is the most robust and flexible option, and should generally be considered the best default.
Expand Down Expand Up @@ -124,6 +126,7 @@ If you are not using a bundler (Astro, Vite, Webpack, etc) for your web applicat
"storage/cookie": "/path/to/js-assets/storage/adapter.cookie.mjs",
"storage/opfs": "/path/to/js-assets/storage/adapter.opfs.mjs",
"storage/opfs-worker": "/path/to/js-assets/storage/adapter.opfs-worker.mjs",
"storage/cache": "/path/to/js-assets/storage/adapter.cache.mjs",

"idb-keyval": "/path/to/js-assets/storage/external/idb-keyval.js"
}
Expand Down
107 changes: 107 additions & 0 deletions src/adapter.cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { safeJSONParse, } from "./util.js";
import {
setMany,
getMany,
removeMany,
} from "./many.js";


// ***********************

get.many = (...args) => getMany(get, ...args);
set.many = (...args) => setMany(set, ...args);
remove.many = (...args) => removeMany(remove, ...args);


// ***********************

var storageType = "cache";
const CACHE_NAME_PREFIX = "cache-kvstore-";
export {
storageType,
has,
get,
set,
remove,
keys,
entries,
};
var publicAPI = {
storageType,
has,
get,
set,
remove,
keys,
entries,
};
export default publicAPI;

// ***********************

async function has(name) {
return await caches.has(`${CACHE_NAME_PREFIX}${name}`);
}

async function get(name) {
const request = new Request(name);
const response = await caches.match(request);
return safeJSONParse(response !== undefined ? await response.json() : null);
}

async function set(name, value) {
try {
const cache = await caches.open(`${CACHE_NAME_PREFIX}${name}`);;
const request = new Request(name);
const response = new Response(JSON.stringify(value));
await cache.put(request, response);

if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(({ usage, quota }) => {
if (usage >= quota) {
throw new DOMException("Browser storage is full","QuotaExceededError");
}
});
}
return true;
}
catch (err) {
throw err;
}
}

async function remove(name) {
return await caches.delete(`${CACHE_NAME_PREFIX}${name}`);
}

async function keys() {
const cacheList = await caches.keys();
const storeKeys = [];
for (const cacheName of cacheList) {
const cache = await caches.open(cacheName);
const requests = await cache.keys();
const cacheKeys = requests.map(request => request.url.split('/').pop());
storeKeys.push(...cacheKeys);
}
return storeKeys;
}

async function entries() {
const cacheList = await caches.keys();
const storeEntries = [];
for (const cacheName of cacheList) {
const cache = await caches.open(cacheName);
const requests = await cache.keys();
for (const request of requests) {
const response = await cache.match(request);
if (response) {
const value = safeJSONParse(await response.json());
storeEntries.push([
request.url.split('/').pop(),
value
]);
}
}
}
return storeEntries;
}
2 changes: 2 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ <h3><a href="https://github.com/byojs/storage">Github</a></h3>
"storage/src/cookie": "./src/adapter.cookie.js",
"storage/src/opfs": "./src/adapter.opfs.js",
"storage/src/opfs-worker": "./src/adapter.opfs-worker.js",
"storage/src/cache": "./src/adapter.cache.js",

"storage/dist/idb": "./dist/adapter.idb.mjs",
"storage/dist/local-storage": "./dist/adapter.local-storage.mjs",
"storage/dist/session-storage": "./dist/adapter.session-storage.mjs",
"storage/dist/cookie": "./dist/adapter.cookie.mjs",
"storage/dist/opfs": "./dist/adapter.opfs.mjs",
"storage/dist/opfs-worker": "./dist/adapter.opfs-worker.mjs",
"storage/dist/cache": "./dist/adapter.cache.mjs",

"idb-keyval": "./dist/external/idb-keyval.js"
}
Expand Down
22 changes: 21 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SSStore from "storage/src/session-storage";
import CookieStore from "storage/src/cookie";
import OPFSStore from "storage/src/opfs";
import OPFSWorkerStore from "storage/src/opfs-worker";
import CacheStore from "storage/src/cache";


// ***********************
Expand All @@ -18,6 +19,7 @@ const storageTypes = {
"cookie": [ "Cookies", CookieStore, ],
"opfs": [ "Origin Private FS", OPFSStore, ],
"opfs-worker": [ "OPFS-Worker", OPFSWorkerStore, ],
"cache": [ "Cache", CacheStore, ],
};

var runTestsBtn;
Expand Down Expand Up @@ -162,12 +164,30 @@ async function runTests() {
[ "opfs-worker", "get.many (2)", [ "world", { ofLife: 42, }, ], ],
[ "opfs-worker", "remove.many (2)", true],
[ "opfs-worker", "{cleared} (2)", [ false, false, ], ],
[ "cache", "has(1)", false ],
[ "cache", "get(1)", null ],
[ "cache", "set(1)", true ],
[ "cache", "has(2)", true ],
[ "cache", "get(2)", "world" ],
[ "cache", "set(2)", true ],
[ "cache", "keys(1)", [ "hello", "meaning", ], ],
[ "cache", "entries", [ [ "hello", "world", ], [ "meaning", { ofLife: 42, }, ], ], ],
[ "cache", "remove", true ],
[ "cache", "keys(2)", [ "meaning", ], ],
[ "cache", "{cleared} (1)", [ false, false, ], ],
[ "cache", "set.many (1)", true],
[ "cache", "get.many (1)", [ "world", { ofLife: 42, }, ], ],
[ "cache", "remove.many (1)", true],
[ "cache", "set.many (2)", true],
[ "cache", "get.many (2)", [ "world", { ofLife: 42, }, ], ],
[ "cache", "remove.many (2)", true],
[ "cache", "{cleared} (2)", [ false, false, ], ],
];
var testResults = [];

testResultsEl.innerHTML = "Storage tests running...<br>";

var stores = [ IDBStore, LSStore, SSStore, CookieStore, OPFSStore, OPFSWorkerStore, ];
var stores = [ IDBStore, LSStore, SSStore, CookieStore, OPFSStore, OPFSWorkerStore, CacheStore, ];
for (let store of stores) {
testResults.push([ storageTypes[store.storageType][0], "has(1)", await store.has("hello"), ]);
testResults.push([ storageTypes[store.storageType][0], "get(1)", await store.get("hello"), ]);
Expand Down