Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/nperez0111/bookhive. Track your books, share your shelves, see what others are reading bookhive.buzz
atproto bluesky books bookshelf goodreads management-system
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176import { wrapBunSqliteForKysely } from "./bun-sqlite-kysely.js";import { defineDriver } from "unstorage";import { Kysely, SqliteDialect } from "kysely";import { Database as DatabaseSync } from "bun:sqlite";
interface TableSchema { [k: string]: { id: string; value: string; created_at: string; updated_at: string; };}
export type KvDb = Kysely<TableSchema>;
const DRIVER_NAME = "sqlite";
export default defineDriver< { location?: string; table: string; getDb?: () => KvDb; }, KvDb>( ({ location, table = "kv", getDb = (): KvDb => { let _db: KvDb | null = null;
return (() => { if (_db) { return _db; }
if (!location) { throw new Error("SQLite location is required"); }
const sqlite = new DatabaseSync(location); sqlite.exec("PRAGMA journal_mode = WAL");
_db = new Kysely<TableSchema>({ dialect: new SqliteDialect({ database: wrapBunSqliteForKysely(sqlite), }), });
// Create table if not exists _db.schema .createTable(table) .ifNotExists() .addColumn("id", "text", (col) => col.primaryKey()) .addColumn("value", "text", (col) => col.notNull()) .addColumn("created_at", "text", (col) => col.notNull()) .addColumn("updated_at", "text", (col) => col.notNull()) .execute();
return _db; })(); }, }) => { return { name: DRIVER_NAME, options: { location, table }, getInstance: getDb,
async hasItem(key) { const result = await getDb() .selectFrom(table) .select(["id"]) .where("id", "=", key) .executeTakeFirst(); return !!result; },
async getItem(key) { const result = await getDb() .selectFrom(table) .select(["value"]) .where("id", "=", key) .executeTakeFirst(); return result?.value ?? null; },
async setItem(key: string, value: string) { const now = new Date().toISOString(); await getDb() .insertInto(table) .values({ id: key, value, created_at: now, updated_at: now, }) .onConflict((oc) => oc.column("id").doUpdateSet((c) => ({ value: c.ref("excluded.value"), updated_at: c.ref("excluded.updated_at"), })), ) .execute(); },
async setItems(items) { const now = new Date().toISOString();
await getDb() .transaction() .execute(async (trx) => { await Promise.all( items.map(({ key, value }) => { return trx .insertInto(table) .values({ id: key, value, created_at: now, updated_at: now, }) .onConflict((oc) => oc.column("id").doUpdateSet({ value, updated_at: now, }), ) .execute(); }), ); }); },
async removeItem(key: string) { await getDb().deleteFrom(table).where("id", "=", key).execute(); },
async getMeta(key: string) { const result = await getDb() .selectFrom(table) .select(["created_at", "updated_at"]) .where("id", "=", key) .executeTakeFirst(); if (!result) { return null; } const mtime = new Date(result.updated_at); return { birthtime: new Date(result.created_at), mtime, /** Used by readThroughCache for TTL; setItem updates updated_at. */ timestamp: mtime.getTime(), }; },
async getKeys(base = "") { const results = await getDb() .selectFrom(table) .select(["id"]) .where("id", "like", `${base}%`) .execute(); return results.map((r) => r.id); },
async clear() { await getDb().deleteFrom(table).execute(); },
async dispose() { await getDb().destroy(); }, }; },);