import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import { Effect, Layer } from "effect"; import { toPersistenceSqlError } from "../Errors.ts"; import { DeleteProjectionThreadInput, GetProjectionThreadInput, ListProjectionThreadsByProjectInput, ProjectionThread, ProjectionThreadRepository, type ProjectionThreadRepositoryShape, } from "../Services/ProjectionThreads.ts"; const makeProjectionThreadRepository = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const upsertProjectionThreadRow = SqlSchema.void({ Request: ProjectionThread, execute: (row) => sql` INSERT INTO projection_threads ( thread_id, project_id, title, model, runtime_mode, interaction_mode, branch, worktree_path, latest_turn_id, created_at, updated_at, deleted_at ) VALUES ( ${row.threadId}, ${row.projectId}, ${row.title}, ${row.model}, ${row.runtimeMode}, ${row.interactionMode}, ${row.branch}, ${row.worktreePath}, ${row.latestTurnId}, ${row.createdAt}, ${row.updatedAt}, ${row.deletedAt} ) ON CONFLICT (thread_id) DO UPDATE SET project_id = excluded.project_id, title = excluded.title, model = excluded.model, runtime_mode = excluded.runtime_mode, interaction_mode = excluded.interaction_mode, branch = excluded.branch, worktree_path = excluded.worktree_path, latest_turn_id = excluded.latest_turn_id, created_at = excluded.created_at, updated_at = excluded.updated_at, deleted_at = excluded.deleted_at `, }); const getProjectionThreadRow = SqlSchema.findOneOption({ Request: GetProjectionThreadInput, Result: ProjectionThread, execute: ({ threadId }) => sql` SELECT thread_id AS "threadId", project_id AS "projectId", title, model, runtime_mode AS "runtimeMode", interaction_mode AS "interactionMode", branch, worktree_path AS "worktreePath", latest_turn_id AS "latestTurnId", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_threads WHERE thread_id = ${threadId} `, }); const listProjectionThreadRows = SqlSchema.findAll({ Request: ListProjectionThreadsByProjectInput, Result: ProjectionThread, execute: ({ projectId }) => sql` SELECT thread_id AS "threadId", project_id AS "projectId", title, model, runtime_mode AS "runtimeMode", interaction_mode AS "interactionMode", branch, worktree_path AS "worktreePath", latest_turn_id AS "latestTurnId", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_threads WHERE project_id = ${projectId} ORDER BY created_at ASC, thread_id ASC `, }); const deleteProjectionThreadRow = SqlSchema.void({ Request: DeleteProjectionThreadInput, execute: ({ threadId }) => sql` DELETE FROM projection_threads WHERE thread_id = ${threadId} `, }); const upsert: ProjectionThreadRepositoryShape["upsert"] = (row) => upsertProjectionThreadRow(row).pipe( Effect.mapError(toPersistenceSqlError("ProjectionThreadRepository.upsert:query")), ); const getById: ProjectionThreadRepositoryShape["getById"] = (input) => getProjectionThreadRow(input).pipe( Effect.mapError(toPersistenceSqlError("ProjectionThreadRepository.getById:query")), ); const listByProjectId: ProjectionThreadRepositoryShape["listByProjectId"] = (input) => listProjectionThreadRows(input).pipe( Effect.mapError(toPersistenceSqlError("ProjectionThreadRepository.listByProjectId:query")), ); const deleteById: ProjectionThreadRepositoryShape["deleteById"] = (input) => deleteProjectionThreadRow(input).pipe( Effect.mapError(toPersistenceSqlError("ProjectionThreadRepository.deleteById:query")), ); return { upsert, getById, listByProjectId, deleteById, } satisfies ProjectionThreadRepositoryShape; }); export const ProjectionThreadRepositoryLive = Layer.effect( ProjectionThreadRepository, makeProjectionThreadRepository, );