feat(database): add database service abstraction with libsql/turso backend (#3472)

This commit is contained in:
Huang Xin
2026-03-05 02:01:35 +08:00
parent bd957a4eb8
commit 5273ef75dc
26 changed files with 2738 additions and 668 deletions
@@ -0,0 +1,38 @@
import { Database, QueryResult } from 'tauri-plugin-libsql';
import { DatabaseService, DatabaseExecResult, DatabaseRow } from '@/types/database';
export class NativeDatabaseService implements DatabaseService {
private db: Database;
private constructor(db: Database) {
this.db = db;
}
static async open(path: string): Promise<NativeDatabaseService> {
const db = await Database.load(path);
return new NativeDatabaseService(db);
}
async execute(sql: string, params: unknown[] = []): Promise<DatabaseExecResult> {
const result: QueryResult = await this.db.execute(sql, params);
return {
rowsAffected: result.rowsAffected,
lastInsertId: result.lastInsertId,
};
}
async select<T extends DatabaseRow = DatabaseRow>(
sql: string,
params: unknown[] = [],
): Promise<T[]> {
return await this.db.select<T[]>(sql, params);
}
async batch(statements: string[]): Promise<void> {
await this.db.batch(statements);
}
async close(): Promise<void> {
await this.db.close();
}
}
@@ -0,0 +1,71 @@
import { DatabaseService, DatabaseExecResult, DatabaseRow } from '@/types/database';
interface TursoRunResult {
changes: number;
lastInsertRowid: number;
}
interface TursoStatement {
run(...params: unknown[]): Promise<TursoRunResult>;
all(...params: unknown[]): Promise<Record<string, unknown>[]>;
}
interface TursoDatabase {
prepare(sql: string): TursoStatement;
exec(sql: string): Promise<void>;
close(): Promise<void>;
}
/**
* DatabaseService implementation backed by @tursodatabase/database (Node.js native).
* Uses the same libsql engine and API surface as the browser-based
* @tursodatabase/database-wasm used by WebDatabaseService.
*/
export class NodeDatabaseService implements DatabaseService {
private db: TursoDatabase;
private constructor(db: TursoDatabase) {
this.db = db;
}
static async open(path: string): Promise<NodeDatabaseService> {
const mod = await import('@tursodatabase/database');
const db = (await mod.connect(path)) as unknown as TursoDatabase;
return new NodeDatabaseService(db);
}
async execute(sql: string, params: unknown[] = []): Promise<DatabaseExecResult> {
const stmt = this.db.prepare(sql);
const result = await stmt.run(...params);
return {
rowsAffected: result.changes,
lastInsertId: Number(result.lastInsertRowid),
};
}
async select<T extends DatabaseRow = DatabaseRow>(
sql: string,
params: unknown[] = [],
): Promise<T[]> {
const stmt = this.db.prepare(sql);
const rows = await stmt.all(...params);
return rows as T[];
}
async batch(statements: string[]): Promise<void> {
await this.db.exec('BEGIN');
try {
for (const sql of statements) {
await this.db.exec(sql);
}
await this.db.exec('COMMIT');
} catch (error: unknown) {
await this.db.exec('ROLLBACK');
throw error;
}
}
async close(): Promise<void> {
await this.db.close();
}
}
@@ -0,0 +1,66 @@
import { DatabaseService, DatabaseExecResult, DatabaseRow } from '@/types/database';
interface WasmRunResult {
changes: number;
lastInsertRowid: number;
}
interface WasmStatement {
run(...params: unknown[]): Promise<WasmRunResult>;
all(...params: unknown[]): Promise<Record<string, unknown>[]>;
}
interface WasmDatabase {
prepare(sql: string): WasmStatement;
exec(sql: string): Promise<void>;
close(): Promise<void>;
}
export class WebDatabaseService implements DatabaseService {
private db: WasmDatabase;
private constructor(db: WasmDatabase) {
this.db = db;
}
static async open(path: string): Promise<WebDatabaseService> {
const mod = await import('@tursodatabase/database-wasm');
const db = (await mod.connect(path)) as unknown as WasmDatabase;
return new WebDatabaseService(db);
}
async execute(sql: string, params: unknown[] = []): Promise<DatabaseExecResult> {
const stmt = this.db.prepare(sql);
const result = await stmt.run(...params);
return {
rowsAffected: result.changes,
lastInsertId: Number(result.lastInsertRowid),
};
}
async select<T extends DatabaseRow = DatabaseRow>(
sql: string,
params: unknown[] = [],
): Promise<T[]> {
const stmt = this.db.prepare(sql);
const rows = await stmt.all(...params);
return rows as T[];
}
async batch(statements: string[]): Promise<void> {
await this.db.exec('BEGIN');
try {
for (const sql of statements) {
await this.db.exec(sql);
}
await this.db.exec('COMMIT');
} catch (error: unknown) {
await this.db.exec('ROLLBACK');
throw error;
}
}
async close(): Promise<void> {
await this.db.close();
}
}