feat: add IndexedDB wrapper
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
13838cbeb7
commit
3d7c0aa4fd
@@ -0,0 +1,83 @@
|
|||||||
|
const DB_NAME = 'outlier-saver';
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
|
||||||
|
let dbPromise = null;
|
||||||
|
|
||||||
|
export function openDb() {
|
||||||
|
dbPromise ??= new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
request.onupgradeneeded = () => {
|
||||||
|
const db = request.result;
|
||||||
|
db.createObjectStore('outliers', { keyPath: 'videoId' }).createIndex('updatedAt', 'updatedAt');
|
||||||
|
db.createObjectStore('channels', { keyPath: 'channelId' });
|
||||||
|
db.createObjectStore('seen', { keyPath: 'videoId' });
|
||||||
|
};
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
}).catch((err) => {
|
||||||
|
dbPromise = null;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
return dbPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function promisify(request) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMany(storeName, keys) {
|
||||||
|
const db = await openDb();
|
||||||
|
const store = db.transaction(storeName, 'readonly').objectStore(storeName);
|
||||||
|
const records = await Promise.all(keys.map((key) => promisify(store.get(key))));
|
||||||
|
return new Map(records.filter(Boolean).map((record) => [record[store.keyPath], record]));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function putMany(storeName, records) {
|
||||||
|
if (records.length === 0) return;
|
||||||
|
const db = await openDb();
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(storeName, 'readwrite');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
for (const record of records) store.put(record);
|
||||||
|
tx.oncomplete = () => resolve();
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSeen = (videoIds) => getMany('seen', videoIds);
|
||||||
|
export const putSeen = (records) => putMany('seen', records);
|
||||||
|
export const getChannels = (channelIds) => getMany('channels', channelIds);
|
||||||
|
export const putChannels = (records) => putMany('channels', records);
|
||||||
|
export const getOutliers = (videoIds) => getMany('outliers', videoIds);
|
||||||
|
export const putOutliers = (records) => putMany('outliers', records);
|
||||||
|
|
||||||
|
export async function listOutliers(limit = Infinity) {
|
||||||
|
const db = await openDb();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const results = [];
|
||||||
|
const request = db
|
||||||
|
.transaction('outliers', 'readonly')
|
||||||
|
.objectStore('outliers')
|
||||||
|
.index('updatedAt')
|
||||||
|
.openCursor(null, 'prev');
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const cursor = request.result;
|
||||||
|
if (!cursor || results.length >= limit) {
|
||||||
|
resolve(results);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
results.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function countOutliers() {
|
||||||
|
const db = await openDb();
|
||||||
|
return promisify(db.transaction('outliers', 'readonly').objectStore('outliers').count());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user