import { AsyncResult, UnexpectedError, UUID } from "@fabric/core"; import { type AggregateModel, ModelSchemaFromModels, ModelToType, StoreQuery, StoreQueryError, WritableStateStore, } from "@fabric/domain"; import { modelToSql } from "../sqlite/model-to-sql.ts"; import { keyToParam, recordToSQLKeyParams, recordToSQLKeys, recordToSQLParams, recordToSQLSet, } from "../sqlite/record-utils.ts"; import { SQLiteDatabase } from "../sqlite/sqlite-database.ts"; import { QueryBuilder } from "./query-builder.ts"; export class SQLiteStateStore implements WritableStateStore { private schema: ModelSchemaFromModels; private db: SQLiteDatabase; constructor(private readonly dbPath: string, models: TModel[]) { this.schema = models.reduce((acc, model: TModel) => { return { ...acc, [model.name]: model, }; }, {} as ModelSchemaFromModels); this.db = new SQLiteDatabase(dbPath); } insertInto>( collection: T, record: ModelToType[T]>, ): AsyncResult { const model = this.schema[collection]; return AsyncResult.tryFrom( () => { this.db.runPrepared( `INSERT INTO ${model.name} (${ recordToSQLKeys( record, ) }) VALUES (${recordToSQLKeyParams(record)})`, recordToSQLParams(model, record), ); }, (error) => new StoreQueryError(error.message), ); } from>( collection: T, ): StoreQuery[T]>> { return new QueryBuilder(this.db, this.schema, { from: collection, }) as StoreQuery[T]>>; } update>( collection: T, id: UUID, record: Partial[T]>>, ): AsyncResult { const model = this.schema[collection]; return AsyncResult.tryFrom( () => { const params = recordToSQLParams(model, { ...record, id, }); this.db.runPrepared( `UPDATE ${model.name} SET ${ recordToSQLSet( record, ) } WHERE id = ${keyToParam("id")}`, params, ); }, (error) => new StoreQueryError(error.message), ); } delete>( collection: T, id: UUID, ): AsyncResult { const model = this.schema[collection]; return AsyncResult.tryFrom( () => { this.db.runPrepared( `DELETE FROM ${model.name} WHERE id = ${keyToParam("id")}`, { $id: id }, ); }, (error) => new StoreQueryError(error.message), ); } migrate(): AsyncResult { return AsyncResult.tryFrom( async () => { await this.db.init(); await this.db.withTransaction(() => { for (const modelKey in this.schema) { const model = this.schema[modelKey as keyof ModelSchemaFromModels]; this.db.runPrepared(modelToSql(model)); } }); }, (error) => new StoreQueryError(error.message), ); } close(): AsyncResult { return AsyncResult.from(() => this.db.close()); } }