# v0.4.28 (2026-05-10)

## Features
- Add bun:sqlite adapter with automatic runtime detection (Bun/Node)
- Add bulk API key import (format: `name|sk-key`, one per line)
## Fixes
- Fix add API key for custom providers
This commit is contained in:
decolua
2026-05-10 08:44:14 +07:00
parent b39eb61c33
commit 530dc9cb3b
14 changed files with 282 additions and 71 deletions
+63
View File
@@ -0,0 +1,63 @@
// Bun runtime adapter — uses built-in bun:sqlite (native, fastest under Bun).
// Loaded only when process.versions.bun is present.
import { PRAGMA_SQL } from "../schema.js";
const CHECKPOINT_INTERVAL_MS = 60 * 1000;
export async function createBunSqliteAdapter(filePath) {
// Dynamic import — only resolves under Bun runtime
const { Database } = await import("bun:sqlite");
const db = new Database(filePath, { create: true });
db.exec(PRAGMA_SQL);
const stmtCache = new Map();
function prepare(sql) {
let stmt = stmtCache.get(sql);
if (!stmt) {
stmt = db.prepare(sql);
stmtCache.set(sql, stmt);
}
return stmt;
}
const checkpointTimer = setInterval(() => {
try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {}
}, CHECKPOINT_INTERVAL_MS);
if (typeof checkpointTimer.unref === "function") checkpointTimer.unref();
function gracefulClose() {
try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {}
try { stmtCache.clear(); } catch {}
try { db.close(); } catch {}
}
const onShutdown = () => gracefulClose();
process.once("beforeExit", onShutdown);
process.once("SIGINT", () => { onShutdown(); process.exit(0); });
process.once("SIGTERM", () => { onShutdown(); process.exit(0); });
return {
driver: "bun:sqlite",
run(sql, params = []) {
const r = prepare(sql).run(...params);
return { changes: Number(r.changes ?? 0), lastInsertRowid: Number(r.lastInsertRowid ?? 0) };
},
get(sql, params = []) {
return prepare(sql).get(...params);
},
all(sql, params = []) {
return prepare(sql).all(...params);
},
exec(sql) { return db.exec(sql); },
transaction(fn) {
// bun:sqlite has db.transaction() API (similar to better-sqlite3)
const tx = db.transaction(fn);
return tx();
},
checkpoint() { try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {} },
close() {
clearInterval(checkpointTimer);
gracefulClose();
},
raw: db,
};
}
+5 -4
View File
@@ -62,14 +62,15 @@ export async function createNodeSqliteAdapter(filePath) {
},
exec(sql) { return db.exec(sql); },
transaction(fn) {
// node:sqlite has no built-in transaction wrapper → manual BEGIN/COMMIT
db.exec("BEGIN");
// node:sqlite has no transaction wrapper. Use SAVEPOINT for nested support.
const sp = `sp_${Math.random().toString(36).slice(2)}`;
db.exec(`SAVEPOINT ${sp}`);
try {
const r = fn();
db.exec("COMMIT");
db.exec(`RELEASE ${sp}`);
return r;
} catch (e) {
try { db.exec("ROLLBACK"); } catch {}
try { db.exec(`ROLLBACK TO ${sp}`); db.exec(`RELEASE ${sp}`); } catch {}
throw e;
}
},
+4 -3
View File
@@ -86,14 +86,15 @@ export async function createSqlJsAdapter(filePath) {
}
function transaction(fn) {
db.exec("BEGIN");
const sp = `sp_${Math.random().toString(36).slice(2)}`;
db.exec(`SAVEPOINT ${sp}`);
try {
const result = fn();
db.exec("COMMIT");
db.exec(`RELEASE ${sp}`);
scheduleSave();
return result;
} catch (e) {
db.exec("ROLLBACK");
try { db.exec(`ROLLBACK TO ${sp}`); db.exec(`RELEASE ${sp}`); } catch {}
throw e;
}
}
+22 -4
View File
@@ -4,7 +4,21 @@ import { ensureDirs, DATA_FILE } from "./paths.js";
if (!global._dbAdapter) global._dbAdapter = { instance: null, initPromise: null, logged: false };
const state = global._dbAdapter;
async function tryBunSqlite() {
// Bun runtime only — built-in, no install needed
if (!process.versions.bun) return null;
try {
const { createBunSqliteAdapter } = await import("./adapters/bunSqliteAdapter.js");
return await createBunSqliteAdapter(DATA_FILE);
} catch (e) {
console.warn(`[DB] bun:sqlite unavailable: ${e.message}`);
return null;
}
}
async function tryBetterSqlite() {
// Skip on Bun — better-sqlite3 native bindings unsupported
if (process.versions.bun) return null;
try {
const { createBetterSqliteAdapter } = await import("./adapters/betterSqliteAdapter.js");
return createBetterSqliteAdapter(DATA_FILE);
@@ -15,7 +29,8 @@ async function tryBetterSqlite() {
}
async function tryNodeSqlite() {
// Built-in since Node 22.5.0 — no install needed.
// Built-in since Node 22.5.0 — no install needed. Skip under Bun (no node:sqlite).
if (process.versions.bun) return null;
const [maj, min] = process.versions.node.split(".").map(Number);
if (maj < 22 || (maj === 22 && min < 5)) return null;
try {
@@ -39,11 +54,14 @@ async function trySqlJs() {
async function initAdapter() {
ensureDirs();
// Order: native (fastest) → built-in (no install) → pure JS (universal)
let adapter = await tryBetterSqlite();
// Order per runtime:
// Bun: bun:sqlite → sql.js
// Node: better-sqlite3 → node:sqlite (≥22.5) → sql.js
let adapter = await tryBunSqlite();
if (!adapter) adapter = await tryBetterSqlite();
if (!adapter) adapter = await tryNodeSqlite();
if (!adapter) adapter = await trySqlJs();
if (!adapter) throw new Error("[DB] No SQLite driver available (better-sqlite3 + node:sqlite + sql.js all failed)");
if (!adapter) throw new Error("[DB] No SQLite driver available (bun/better/node/sql.js all failed)");
if (!state.logged) {
console.log(`[DB] Driver: ${adapter.driver} | file: ${DATA_FILE}`);