/** * ProjectionTurnRepository - Projection repository interface for unified turn state. * * Owns persistence operations for pending starts, running/completed turn lifecycle, * and checkpoint metadata in a single projection table. * * @module ProjectionTurnRepository */ import { CheckpointRef, IsoDateTime, MessageId, NonNegativeInt, OrchestrationProposedPlanId, OrchestrationCheckpointFile, OrchestrationCheckpointStatus, ThreadId, TurnId, } from "@t3tools/contracts"; import { Option, Schema, ServiceMap } from "effect"; import type { Effect } from "effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; export const ProjectionTurnState = Schema.Literals([ "pending", "running", "interrupted", "completed", "error", ]); export type ProjectionTurnState = typeof ProjectionTurnState.Type; export const ProjectionTurn = Schema.Struct({ threadId: ThreadId, turnId: Schema.NullOr(TurnId), pendingMessageId: Schema.NullOr(MessageId), sourceProposedPlanThreadId: Schema.NullOr(ThreadId), sourceProposedPlanId: Schema.NullOr(OrchestrationProposedPlanId), assistantMessageId: Schema.NullOr(MessageId), state: ProjectionTurnState, requestedAt: IsoDateTime, startedAt: Schema.NullOr(IsoDateTime), completedAt: Schema.NullOr(IsoDateTime), checkpointTurnCount: Schema.NullOr(NonNegativeInt), checkpointRef: Schema.NullOr(CheckpointRef), checkpointStatus: Schema.NullOr(OrchestrationCheckpointStatus), checkpointFiles: Schema.Array(OrchestrationCheckpointFile), }); export type ProjectionTurn = typeof ProjectionTurn.Type; export const ProjectionTurnById = Schema.Struct({ threadId: ThreadId, turnId: TurnId, pendingMessageId: Schema.NullOr(MessageId), sourceProposedPlanThreadId: Schema.NullOr(ThreadId), sourceProposedPlanId: Schema.NullOr(OrchestrationProposedPlanId), assistantMessageId: Schema.NullOr(MessageId), state: ProjectionTurnState, requestedAt: IsoDateTime, startedAt: Schema.NullOr(IsoDateTime), completedAt: Schema.NullOr(IsoDateTime), checkpointTurnCount: Schema.NullOr(NonNegativeInt), checkpointRef: Schema.NullOr(CheckpointRef), checkpointStatus: Schema.NullOr(OrchestrationCheckpointStatus), checkpointFiles: Schema.Array(OrchestrationCheckpointFile), }); export type ProjectionTurnById = typeof ProjectionTurnById.Type; export const ProjectionPendingTurnStart = Schema.Struct({ threadId: ThreadId, messageId: MessageId, sourceProposedPlanThreadId: Schema.NullOr(ThreadId), sourceProposedPlanId: Schema.NullOr(OrchestrationProposedPlanId), requestedAt: IsoDateTime, }); export type ProjectionPendingTurnStart = typeof ProjectionPendingTurnStart.Type; export const ListProjectionTurnsByThreadInput = Schema.Struct({ threadId: ThreadId, }); export type ListProjectionTurnsByThreadInput = typeof ListProjectionTurnsByThreadInput.Type; export const GetProjectionTurnByTurnIdInput = Schema.Struct({ threadId: ThreadId, turnId: TurnId, }); export type GetProjectionTurnByTurnIdInput = typeof GetProjectionTurnByTurnIdInput.Type; export const GetProjectionPendingTurnStartInput = Schema.Struct({ threadId: ThreadId, }); export type GetProjectionPendingTurnStartInput = typeof GetProjectionPendingTurnStartInput.Type; export const DeleteProjectionTurnsByThreadInput = Schema.Struct({ threadId: ThreadId, }); export type DeleteProjectionTurnsByThreadInput = typeof DeleteProjectionTurnsByThreadInput.Type; export const ClearCheckpointTurnConflictInput = Schema.Struct({ threadId: ThreadId, turnId: TurnId, checkpointTurnCount: NonNegativeInt, }); export type ClearCheckpointTurnConflictInput = typeof ClearCheckpointTurnConflictInput.Type; export interface ProjectionTurnRepositoryShape { /** * Inserts or updates the canonical row for a concrete `{threadId, turnId}` turn lifecycle state. */ readonly upsertByTurnId: ( row: ProjectionTurnById, ) => Effect.Effect; /** * Replaces any existing pending-start placeholder rows for a thread with exactly one latest pending-start row. */ readonly replacePendingTurnStart: ( row: ProjectionPendingTurnStart, ) => Effect.Effect; /** * Returns the newest pending-start placeholder for a thread; this is expected to be at most one row after replacement writes. */ readonly getPendingTurnStartByThreadId: ( input: GetProjectionPendingTurnStartInput, ) => Effect.Effect, ProjectionRepositoryError>; /** * Deletes only pending-start placeholder rows (`turnId = null`) for a thread and leaves concrete turn rows untouched. */ readonly deletePendingTurnStartByThreadId: ( input: GetProjectionPendingTurnStartInput, ) => Effect.Effect; /** * Lists all projection rows for a thread, including pending placeholders, with checkpoint rows ordered before non-checkpoint rows. */ readonly listByThreadId: ( input: ListProjectionTurnsByThreadInput, ) => Effect.Effect, ProjectionRepositoryError>; /** * Looks up a concrete turn row by `{threadId, turnId}` and never returns pending placeholder rows. */ readonly getByTurnId: ( input: GetProjectionTurnByTurnIdInput, ) => Effect.Effect, ProjectionRepositoryError>; /** * Clears checkpoint fields on conflicting rows that reuse the same checkpoint turn count in a thread, excluding the provided turn. */ readonly clearCheckpointTurnConflict: ( input: ClearCheckpointTurnConflictInput, ) => Effect.Effect; /** * Hard-deletes all projection rows for a thread, including pending-start placeholders and checkpoint metadata rows. */ readonly deleteByThreadId: ( input: DeleteProjectionTurnsByThreadInput, ) => Effect.Effect; } export class ProjectionTurnRepository extends ServiceMap.Service< ProjectionTurnRepository, ProjectionTurnRepositoryShape >()("t3/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {}