import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as Struct from "effect/Struct"; import { ModelSelection, ProjectIconOverride, ProjectScript } from "@t3tools/contracts"; import { toPersistenceSqlError } from "../Errors.ts"; import { DeleteProjectionProjectInput, GetProjectionProjectInput, ProjectionProject, ProjectionProjectRepository, type ProjectionProjectRepositoryShape, } from "../Services/ProjectionProjects.ts"; const ProjectionProjectDbRow = ProjectionProject.mapFields( Struct.assign({ defaultModelSelection: Schema.NullOr(Schema.fromJsonString(ModelSelection)), autoPull: Schema.Number, projectIcon: Schema.NullOr(Schema.fromJsonString(ProjectIconOverride)), scripts: Schema.fromJsonString(Schema.Array(ProjectScript)), }), ); type ProjectionProjectDbRow = typeof ProjectionProjectDbRow.Type; const makeProjectionProjectRepository = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const upsertProjectionProjectRow = SqlSchema.void({ Request: ProjectionProject, execute: (row) => sql` INSERT INTO projection_projects ( project_id, title, workspace_root, default_model_selection_json, default_thread_env_mode, auto_pull, favicon_path, project_icon_json, scripts_json, created_at, updated_at, deleted_at ) VALUES ( ${row.projectId}, ${row.title}, ${row.workspaceRoot}, ${row.defaultModelSelection !== null ? JSON.stringify(row.defaultModelSelection) : null}, ${row.defaultThreadEnvMode}, ${row.autoPull ? 1 : 0}, ${row.faviconPath ?? null}, ${row.projectIcon ? JSON.stringify(row.projectIcon) : null}, ${JSON.stringify(row.scripts)}, ${row.createdAt}, ${row.updatedAt}, ${row.deletedAt} ) ON CONFLICT (project_id) DO UPDATE SET title = excluded.title, workspace_root = excluded.workspace_root, default_model_selection_json = excluded.default_model_selection_json, default_thread_env_mode = excluded.default_thread_env_mode, auto_pull = excluded.auto_pull, favicon_path = excluded.favicon_path, project_icon_json = excluded.project_icon_json, scripts_json = excluded.scripts_json, created_at = excluded.created_at, updated_at = excluded.updated_at, deleted_at = excluded.deleted_at `, }); const getProjectionProjectRow = SqlSchema.findOneOption({ Request: GetProjectionProjectInput, Result: ProjectionProjectDbRow, execute: ({ projectId }) => sql` SELECT project_id AS "projectId", title, workspace_root AS "workspaceRoot", default_model_selection_json AS "defaultModelSelection", default_thread_env_mode AS "defaultThreadEnvMode", auto_pull AS "autoPull", favicon_path AS "faviconPath", project_icon_json AS "projectIcon", scripts_json AS "scripts", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_projects WHERE project_id = ${projectId} `, }); const listProjectionProjectRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionProjectDbRow, execute: () => sql` SELECT project_id AS "projectId", title, workspace_root AS "workspaceRoot", default_model_selection_json AS "defaultModelSelection", default_thread_env_mode AS "defaultThreadEnvMode", auto_pull AS "autoPull", favicon_path AS "faviconPath", project_icon_json AS "projectIcon", scripts_json AS "scripts", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_projects ORDER BY created_at ASC, project_id ASC `, }); const deleteProjectionProjectRow = SqlSchema.void({ Request: DeleteProjectionProjectInput, execute: ({ projectId }) => sql` DELETE FROM projection_projects WHERE project_id = ${projectId} `, }); const upsert: ProjectionProjectRepositoryShape["upsert"] = (row) => upsertProjectionProjectRow(row).pipe( Effect.mapError(toPersistenceSqlError("ProjectionProjectRepository.upsert:query")), ); const getById: ProjectionProjectRepositoryShape["getById"] = (input) => getProjectionProjectRow(input).pipe( Effect.map(Option.map((row) => ({ ...row, autoPull: row.autoPull === 1 }))), Effect.mapError(toPersistenceSqlError("ProjectionProjectRepository.getById:query")), ); const listAll: ProjectionProjectRepositoryShape["listAll"] = () => listProjectionProjectRows().pipe( Effect.map((rows) => rows.map((row) => ({ ...row, autoPull: row.autoPull === 1 }))), Effect.mapError(toPersistenceSqlError("ProjectionProjectRepository.listAll:query")), ); const deleteById: ProjectionProjectRepositoryShape["deleteById"] = (input) => deleteProjectionProjectRow(input).pipe( Effect.mapError(toPersistenceSqlError("ProjectionProjectRepository.deleteById:query")), ); return { upsert, getById, listAll, deleteById, } satisfies ProjectionProjectRepositoryShape; }); export const ProjectionProjectRepositoryLive = Layer.effect( ProjectionProjectRepository, makeProjectionProjectRepository, );