feat: add IndexedDB wrapper

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
IgorVolochay
2026-10-25 10:51:22 +03:00
co-authored by Claude Sonnet 5.5
parent 13838cbeb7
commit 3d7c0aa4fd
+83
View File
@@ -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());
}