import { Database, RestBindParameters, Statement } from "@db/sqlite"; import { err, getMessageFromError, ok, Result } from "@shared/utils/result.ts"; import { QueryExecutionError } from "@src/errors.ts"; import { fromNullableVal, none, Option, some } from "@shared/utils/option.ts"; export class DatabaseClient { constructor(private readonly db: Database) {} private safeExecute(fn: () => T): Result { try { return ok(fn()); } catch (e) { const message = getMessageFromError(e); return err(new QueryExecutionError(message)); } } exec( sql: string, ...params: RestBindParameters ): Result { return this.safeExecute(() => this.db.exec(sql, params)); } first( sql: string, ...params: RestBindParameters ): Result, QueryExecutionError> { return this.safeExecute(() => fromNullableVal(this.db.prepare(sql).get(params)), ); } all( sql: string, ...params: RestBindParameters ): Result, QueryExecutionError> { return this.safeExecute(() => this.db.prepare(sql).all(params)).map( (results) => (results.length > 0 ? some(results) : none), ); } prepareFetch< T extends object, P extends RestBindParameters = RestBindParameters, >(sql: string): PreparedStatement { const stmt = this.db.prepare(sql); const get = ( ...params: P ): Result>, QueryExecutionError> => this.safeExecute(() => fromNullableVal(stmt.get(params))); const all = ( ...params: P ): Result>, QueryExecutionError> => this.safeExecute(() => stmt.all(params)).map((result) => result.length > 0 ? some(result) : none, ); return { get, all }; } prepareExec

( sql: string, ): (...params: P) => Result { const stmt = this.db.prepare(sql); return (...params: P): Result => { return this.safeExecute(() => stmt.run(params)); }; } } interface PreparedStatement { get(...params: RestBindParameters): Result, QueryExecutionError>; all( ...params: RestBindParameters ): Result, QueryExecutionError>; }