Skip to content
Source
Sign inSign up
Open History to see the last commit

CheckpointStore.ts

150 lines5.0 KB

1import { type CheckpointRef } from "@t3tools/contracts";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5​
6import type { CheckpointStoreError } from "./Errors.ts";
7import type { VcsCheckpointOps } from "../vcs/VcsDriver.ts";
8import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
9​
10export interface CaptureCheckpointInput {
11 readonly cwd: string;
12 readonly checkpointRef: CheckpointRef;
14​
15export interface RestoreCheckpointInput {
16 readonly cwd: string;
17 readonly checkpointRef: CheckpointRef;
18 readonly fallbackToHead?: boolean;
20​
21export interface DiffCheckpointsInput {
22 readonly cwd: string;
23 readonly fromCheckpointRef: CheckpointRef;
24 readonly toCheckpointRef: CheckpointRef;
25 readonly fallbackFromToHead?: boolean;
26 readonly ignoreWhitespace: boolean;
27 readonly format?: "patch" | "numstat";
28 /** Maximum bytes collected from the diff command. */
29 readonly maxOutputBytes?: number;
30 /** Limits the diff to these exact paths. An empty list yields an empty diff. */
31 readonly filePaths?: ReadonlyArray<string>;
33​
34export interface ListAuthoredPathsInput {
35 readonly cwd: string;
36 readonly fromCheckpointRef: CheckpointRef;
37 readonly toCheckpointRef: CheckpointRef;
39​
40export interface DeleteCheckpointRefsInput {
41 readonly cwd: string;
42 readonly checkpointRefs: ReadonlyArray<CheckpointRef>;
44​
45export class CheckpointStore extends Context.Service<
46 CheckpointStore,
47 {
48 readonly isGitRepository: (cwd: string) => Effect.Effect<boolean, CheckpointStoreError>;
49​
50 readonly captureCheckpoint: (
51 input: CaptureCheckpointInput,
52 ) => Effect.Effect<void, CheckpointStoreError>;
53​
54 readonly hasCheckpointRef: (
55 input: Omit<RestoreCheckpointInput, "fallbackToHead">,
56 ) => Effect.Effect<boolean, CheckpointStoreError>;
57​
58 readonly restoreCheckpoint: (
59 input: RestoreCheckpointInput,
60 ) => Effect.Effect<boolean, CheckpointStoreError>;
61​
62 readonly diffCheckpoints: (
63 input: DiffCheckpointsInput,
64 ) => Effect.Effect<string, CheckpointStoreError>;
65​
66 /** Null means HEAD is unchanged or unrecorded; all changed paths then belong to the turn. */
67 readonly listAuthoredPaths: (
68 input: ListAuthoredPathsInput,
69 ) => Effect.Effect<ReadonlySet<string> | null, CheckpointStoreError>;
70​
71 readonly deleteCheckpointRefs: (
72 input: DeleteCheckpointRefsInput,
73 ) => Effect.Effect<void, CheckpointStoreError>;
74 }
75>()("t3/checkpointing/CheckpointStore") {}
76​
77/** @public */
78export const make = Effect.gen(function* () {
79 const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry;
80​
81 const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* (
82 cwd: string,
83 ) {
84 const handle = yield* vcsRegistry.resolve({ cwd });
85 return handle.driver.checkpoints satisfies VcsCheckpointOps;
86 });
87​
88 // The registry reads an unreadable repository as none; ask Git again so that fails here.
89 const isGitRepository: CheckpointStore["Service"]["isGitRepository"] = Effect.fn(
90 "CheckpointStore.isGitRepository",
91 )(function* (cwd) {
92 if ((yield* vcsRegistry.detect({ cwd, requestedKind: "git" })) !== null) return true;
93 const git = yield* vcsRegistry.get("git");
94 return (yield* git.detectRepository(cwd)) !== null;
95 });
96​
97 const captureCheckpoint: CheckpointStore["Service"]["captureCheckpoint"] = Effect.fn(
98 "captureCheckpoint",
99 )(function* (input) {
100 const checkpoints = yield* resolveCheckpoints(input.cwd);
101 return yield* checkpoints.captureCheckpoint(input);
102 });
103​
104 const hasCheckpointRef: CheckpointStore["Service"]["hasCheckpointRef"] = Effect.fn(
105 "hasCheckpointRef",
106 )(function* (input) {
107 const checkpoints = yield* resolveCheckpoints(input.cwd);
108 return yield* checkpoints.hasCheckpointRef(input);
109 });
110​
111 const restoreCheckpoint: CheckpointStore["Service"]["restoreCheckpoint"] = Effect.fn(
112 "restoreCheckpoint",
113 )(function* (input) {
114 const checkpoints = yield* resolveCheckpoints(input.cwd);
115 return yield* checkpoints.restoreCheckpoint(input);
116 });
117​
118 const diffCheckpoints: CheckpointStore["Service"]["diffCheckpoints"] = Effect.fn(
119 "diffCheckpoints",
120 )(function* (input) {
121 const checkpoints = yield* resolveCheckpoints(input.cwd);
122 return yield* checkpoints.diffCheckpoints(input);
123 });
124​
125 const listAuthoredPaths: CheckpointStore["Service"]["listAuthoredPaths"] = Effect.fn(
126 "listAuthoredPaths",
127 )(function* (input) {
128 const checkpoints = yield* resolveCheckpoints(input.cwd);
129 return yield* checkpoints.listAuthoredPaths(input);
130 });
131​
132 const deleteCheckpointRefs: CheckpointStore["Service"]["deleteCheckpointRefs"] = Effect.fn(
133 "deleteCheckpointRefs",
134 )(function* (input) {
135 const checkpoints = yield* resolveCheckpoints(input.cwd);
136 return yield* checkpoints.deleteCheckpointRefs(input);
137 });
138​
139 return CheckpointStore.of({
140 isGitRepository,
141 captureCheckpoint,
142 hasCheckpointRef,
143 restoreCheckpoint,
144 diffCheckpoints,
145 listAuthoredPaths,
146 deleteCheckpointRefs,
147 });
148});
149​
150export const layer = Layer.effect(CheckpointStore, make);