import { Effect, UnexpectedError, UUID } from "@fabric/core"; import { Model, ModelSchemaFromModels, ModelToType, StoreQuery, StoreQueryError, WritableStateStore, } from "@fabric/domain"; import { modelToSql } from "../sqlite/model-to-sql.ts"; import { keyToParamKey, recordToSQLKeys, recordToSQLParamKeys, recordToSQLParamRecord, 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]>, ): Effect { const model = this.schema[collection]; return Effect.tryFrom( () => { this.db.runPrepared( `INSERT INTO ${model.name} (${ recordToSQLKeys( record, ) }) VALUES (${recordToSQLParamKeys(record)})`, recordToSQLParamRecord(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]>>, ): Effect { const model = this.schema[collection]; return Effect.tryFrom( () => { const params = recordToSQLParamRecord(model, { ...record, id, }); this.db.runPrepared( `UPDATE ${model.name} SET ${ recordToSQLSet( record, ) } WHERE id = ${keyToParamKey("id")}`, params, ); }, (error) => new StoreQueryError(error.message), ); } delete>( collection: T, id: UUID, ): Effect { const model = this.schema[collection]; return Effect.tryFrom( () => { this.db.runPrepared( `DELETE FROM ${model.name} WHERE id = ${keyToParamKey("id")}`, { [keyToParamKey("id")]: id, }, ); }, (error) => new StoreQueryError(error.message), ); } migrate(): Effect { return Effect.tryFrom( async () => { 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(): Effect { return Effect.tryFrom( () => this.db.close(), (e) => new UnexpectedError(e.message), ); } }