/** * 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, Context } 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<void, ProjectionRepositoryError>; /** * Replaces any existing pending-start placeholder rows for a thread with exactly one latest pending-start row. */ readonly replacePendingTurnStart: ( row: ProjectionPendingTurnStart, ) => Effect.Effect<void, ProjectionRepositoryError>; /** * 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<Option.Option<ProjectionPendingTurnStart>, ProjectionRepositoryError>; /** * Deletes only pending-start placeholder rows (`turnId = null`) for a thread and leaves concrete turn rows untouched. */ readonly deletePendingTurnStartByThreadId: ( input: GetProjectionPendingTurnStartInput, ) => Effect.Effect<void, ProjectionRepositoryError>; /** * Lists all projection rows for a thread, including pending placeholders, with checkpoint rows ordered before non-checkpoint rows. */ readonly listByThreadId: ( input: ListProjectionTurnsByThreadInput, ) => Effect.Effect<ReadonlyArray<ProjectionTurn>, ProjectionRepositoryError>; /** * Looks up a concrete turn row by `{threadId, turnId}` and never returns pending placeholder rows. */ readonly getByTurnId: ( input: GetProjectionTurnByTurnIdInput, ) => Effect.Effect<Option.Option<ProjectionTurnById>, 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<void, ProjectionRepositoryError>; /** * Hard-deletes all projection rows for a thread, including pending-start placeholders and checkpoint metadata rows. */ readonly deleteByThreadId: ( input: DeleteProjectionTurnsByThreadInput, ) => Effect.Effect<void, ProjectionRepositoryError>;} export class ProjectionTurnRepository extends Context.Service< ProjectionTurnRepository, ProjectionTurnRepositoryShape>()("t3/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {}
/**
* 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, Context } 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<void, ProjectionRepositoryError>;
/**
* Replaces any existing pending-start placeholder rows for a thread with exactly one latest pending-start row.
*/
readonly replacePendingTurnStart: (
row: ProjectionPendingTurnStart,
) => Effect.Effect<void, ProjectionRepositoryError>;
/**
* 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<Option.Option<ProjectionPendingTurnStart>, ProjectionRepositoryError>;
/**
* Deletes only pending-start placeholder rows (`turnId = null`) for a thread and leaves concrete turn rows untouched.
*/
readonly deletePendingTurnStartByThreadId: (
input: GetProjectionPendingTurnStartInput,
) => Effect.Effect<void, ProjectionRepositoryError>;
/**
* Lists all projection rows for a thread, including pending placeholders, with checkpoint rows ordered before non-checkpoint rows.
*/
readonly listByThreadId: (
input: ListProjectionTurnsByThreadInput,
) => Effect.Effect<ReadonlyArray<ProjectionTurn>, ProjectionRepositoryError>;
/**
* Looks up a concrete turn row by `{threadId, turnId}` and never returns pending placeholder rows.
*/
readonly getByTurnId: (
input: GetProjectionTurnByTurnIdInput,
) => Effect.Effect<Option.Option<ProjectionTurnById>, 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<void, ProjectionRepositoryError>;
/**
* Hard-deletes all projection rows for a thread, including pending-start placeholders and checkpoint metadata rows.
*/
readonly deleteByThreadId: (
input: DeleteProjectionTurnsByThreadInput,
) => Effect.Effect<void, ProjectionRepositoryError>;
}
export class ProjectionTurnRepository extends Context.Service<
ProjectionTurnRepository,
ProjectionTurnRepositoryShape
>()("t3/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {}