@rocksky/cli
Version:
Command-line interface for Rocksky – scrobble tracks, view stats, and manage your listening history
174 lines (152 loc) • 4.15 kB
text/typescript
import Database from "better-sqlite3";
import { Kysely, SqliteDialect } from "kysely";
import { defineDriver } from "unstorage";
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 Database(location, { fileMustExist: false });
// Enable WAL mode
sqlite.pragma("journal_mode = WAL");
_db = new Kysely<TableSchema>({
dialect: new SqliteDialect({
database: 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({
value,
updated_at: now,
}),
)
.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;
}
return {
birthtime: new Date(result.created_at),
mtime: new Date(result.updated_at),
};
},
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();
},
};
},
);