apps/server/src/checkpointing/CheckpointStore.ts
import { type CheckpointRef } from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import type { CheckpointStoreError } from "./Errors.ts";
import type { VcsCheckpointOps } from "../vcs/VcsDriver.ts";
import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
export interface CaptureCheckpointInput {
readonly cwd: string;
readonly checkpointRef: CheckpointRef;
}
export interface RestoreCheckpointInput {
readonly cwd: string;
readonly checkpointRef: CheckpointRef;
readonly fallbackToHead?: boolean;
}
export interface DiffCheckpointsInput {
readonly cwd: string;
readonly fromCheckpointRef: CheckpointRef;
readonly toCheckpointRef: CheckpointRef;
readonly fallbackFromToHead?: boolean;
readonly ignoreWhitespace: boolean;
readonly format?: "patch" | "numstat";
/** Maximum bytes collected from the diff command. */
readonly maxOutputBytes?: number;
/** Limits the diff to these exact paths. An empty list yields an empty diff. */
readonly filePaths?: ReadonlyArray<string>;
}
export interface ListAuthoredPathsInput {
readonly cwd: string;
readonly fromCheckpointRef: CheckpointRef;
readonly toCheckpointRef: CheckpointRef;
}
export interface DeleteCheckpointRefsInput {
readonly cwd: string;
readonly checkpointRefs: ReadonlyArray<CheckpointRef>;
}
export class CheckpointStore extends Context.Service<
CheckpointStore,
{
readonly isGitRepository: (cwd: string) => Effect.Effect<boolean, CheckpointStoreError>;
readonly captureCheckpoint: (
input: CaptureCheckpointInput,
) => Effect.Effect<void, CheckpointStoreError>;
readonly hasCheckpointRef: (
input: Omit<RestoreCheckpointInput, "fallbackToHead">,
) => Effect.Effect<boolean, CheckpointStoreError>;
readonly restoreCheckpoint: (
input: RestoreCheckpointInput,
) => Effect.Effect<boolean, CheckpointStoreError>;
readonly diffCheckpoints: (
input: DiffCheckpointsInput,
) => Effect.Effect<string, CheckpointStoreError>;
/** Null means HEAD is unchanged or unrecorded; all changed paths then belong to the turn. */
readonly listAuthoredPaths: (
input: ListAuthoredPathsInput,
) => Effect.Effect<ReadonlySet<string> | null, CheckpointStoreError>;
readonly deleteCheckpointRefs: (
input: DeleteCheckpointRefsInput,
) => Effect.Effect<void, CheckpointStoreError>;
}
>()("t3/checkpointing/CheckpointStore") {}
/** @public */
export const make = Effect.gen(function* () {
const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry;
const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* (
cwd: string,
) {
const handle = yield* vcsRegistry.resolve({ cwd });
return handle.driver.checkpoints satisfies VcsCheckpointOps;
});
const isGitRepository: CheckpointStore["Service"]["isGitRepository"] = (cwd) =>
vcsRegistry
.detect({ cwd, requestedKind: "git" })
.pipe(Effect.map((repository) => repository !== null));
const captureCheckpoint: CheckpointStore["Service"]["captureCheckpoint"] = Effect.fn(
"captureCheckpoint",
)(function* (input) {
const checkpoints = yield* resolveCheckpoints(input.cwd);
return yield* checkpoints.captureCheckpoint(input);
});
const hasCheckpointRef: CheckpointStore["Service"]["hasCheckpointRef"] = Effect.fn(
"hasCheckpointRef",
)(function* (input) {
const checkpoints = yield* resolveCheckpoints(input.cwd);
return yield* checkpoints.hasCheckpointRef(input);
});
const restoreCheckpoint: CheckpointStore["Service"]["restoreCheckpoint"] = Effect.fn(
"restoreCheckpoint",
)(function* (input) {
const checkpoints = yield* resolveCheckpoints(input.cwd);
return yield* checkpoints.restoreCheckpoint(input);
});
const diffCheckpoints: CheckpointStore["Service"]["diffCheckpoints"] = Effect.fn(
"diffCheckpoints",
)(function* (input) {
const checkpoints = yield* resolveCheckpoints(input.cwd);
return yield* checkpoints.diffCheckpoints(input);
});
const listAuthoredPaths: CheckpointStore["Service"]["listAuthoredPaths"] = Effect.fn(
"listAuthoredPaths",
)(function* (input) {
const checkpoints = yield* resolveCheckpoints(input.cwd);
return yield* checkpoints.listAuthoredPaths(input);
});
const deleteCheckpointRefs: CheckpointStore["Service"]["deleteCheckpointRefs"] = Effect.fn(
"deleteCheckpointRefs",
)(function* (input) {
const checkpoints = yield* resolveCheckpoints(input.cwd);
return yield* checkpoints.deleteCheckpointRefs(input);
});
return CheckpointStore.of({
isGitRepository,
captureCheckpoint,
hasCheckpointRef,
restoreCheckpoint,
diffCheckpoints,
listAuthoredPaths,
deleteCheckpointRefs,
});
});
export const layer = Layer.effect(CheckpointStore, make);