import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import { Effect, Layer, Schema, Struct } from "effect"; import { toPersistenceSqlError } from "../Errors.ts"; import { ProjectionThreadMessageRepository, type ProjectionThreadMessageRepositoryShape, DeleteProjectionThreadMessagesInput, ListProjectionThreadMessagesInput, ProjectionThreadMessage, } from "../Services/ProjectionThreadMessages.ts"; const ProjectionThreadMessageDbRowSchema = ProjectionThreadMessage.mapFields( Struct.assign({ isStreaming: Schema.Number, }), ); const makeProjectionThreadMessageRepository = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const upsertProjectionThreadMessageRow = SqlSchema.void({ Request: ProjectionThreadMessage, execute: (row) => sql` INSERT INTO projection_thread_messages ( message_id, thread_id, turn_id, role, text, is_streaming, created_at, updated_at ) VALUES ( ${row.messageId}, ${row.threadId}, ${row.turnId}, ${row.role}, ${row.text}, ${row.isStreaming ? 1 : 0}, ${row.createdAt}, ${row.updatedAt} ) ON CONFLICT (message_id) DO UPDATE SET thread_id = excluded.thread_id, turn_id = excluded.turn_id, role = excluded.role, text = excluded.text, is_streaming = excluded.is_streaming, created_at = excluded.created_at, updated_at = excluded.updated_at `, }); const listProjectionThreadMessageRows = SqlSchema.findAll({ Request: ListProjectionThreadMessagesInput, Result: ProjectionThreadMessageDbRowSchema, execute: ({ threadId }) => sql` SELECT message_id AS "messageId", thread_id AS "threadId", turn_id AS "turnId", role, text, is_streaming AS "isStreaming", created_at AS "createdAt", updated_at AS "updatedAt" FROM projection_thread_messages WHERE thread_id = ${threadId} ORDER BY created_at ASC, message_id ASC `, }); const deleteProjectionThreadMessageRows = SqlSchema.void({ Request: DeleteProjectionThreadMessagesInput, execute: ({ threadId }) => sql` DELETE FROM projection_thread_messages WHERE thread_id = ${threadId} `, }); const upsert: ProjectionThreadMessageRepositoryShape["upsert"] = (row) => upsertProjectionThreadMessageRow(row).pipe( Effect.mapError(toPersistenceSqlError("ProjectionThreadMessageRepository.upsert:query")), ); const listByThreadId: ProjectionThreadMessageRepositoryShape["listByThreadId"] = (input) => listProjectionThreadMessageRows(input).pipe( Effect.mapError( toPersistenceSqlError("ProjectionThreadMessageRepository.listByThreadId:query"), ), Effect.map((rows) => rows.map((row) => ({ ...row, isStreaming: row.isStreaming === 1, })), ), ); const deleteByThreadId: ProjectionThreadMessageRepositoryShape["deleteByThreadId"] = (input) => deleteProjectionThreadMessageRows(input).pipe( Effect.mapError( toPersistenceSqlError("ProjectionThreadMessageRepository.deleteByThreadId:query"), ), ); return { upsert, listByThreadId, deleteByThreadId, } satisfies ProjectionThreadMessageRepositoryShape; }); export const ProjectionThreadMessageRepositoryLive = Layer.effect( ProjectionThreadMessageRepository, makeProjectionThreadMessageRepository, );