1import { useAtomValue } from "@effect/atom-react";
2import { parseScopedProjectKey, scopedProjectKey } from "@t3tools/client-runtime/environment";
3import type { EnvironmentId, ProjectCloneSnapshot, ScopedProjectRef } from "@t3tools/contracts";
4import * as Option from "effect/Option";
5import { AsyncResult, Atom } from "effect/reactivity";
6
7import { environmentServerConfigsAtom } from "./server";
8import { sourceControlEnvironment } from "./sourceControl";
9
10const EMPTY_CLONES: ReadonlyArray<ProjectCloneSnapshot> = [];
11const EMPTY_CLONE_ATOM = Atom.make<ProjectCloneSnapshot | null>(null).pipe(
12 Atom.withLabel("web-project-clone:empty"),
13);
14
15/**
16 * Latest clone list an environment has streamed; empty until the subscription
17 * delivers, and never subscribed on servers that predate clone tracking.
18 */
19export const environmentProjectClonesAtom = Atom.family((environmentId: EnvironmentId) =>
20 Atom.make((get): ReadonlyArray<ProjectCloneSnapshot> => {
21 const supported =
22 get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities
23 .projectCloneTracking === true;
24 if (!supported) return EMPTY_CLONES;
25 const result = get(sourceControlEnvironment.projectClones({ environmentId, input: {} }));
26 return Option.getOrElse(AsyncResult.value(result), () => EMPTY_CLONES);
27 }).pipe(Atom.withLabel(`web-project-clones:${environmentId}`)),
28);
29
30const projectCloneAtom = Atom.family((key: string) => {
31 const ref = parseScopedProjectKey(key);
32 return Atom.make((get): ProjectCloneSnapshot | null => {
33 if (ref === null) return null;
34 const clones = get(environmentProjectClonesAtom(ref.environmentId));
35 return clones.find((clone) => clone.projectId === ref.projectId) ?? null;
36 }).pipe(Atom.withLabel(`web-project-clone:${key}`));
37});
38
39/**
40 * The tracked clone for a project, or null once it finished (or never
41 * existed). Subscribing here opens the environment's clone stream, which is
42 * cheap: the server sends an empty list and stays quiet until a clone starts.
43 */
44export function useProjectClone(ref: ScopedProjectRef | null): ProjectCloneSnapshot | null {
45 return useAtomValue(ref === null ? EMPTY_CLONE_ATOM : projectCloneAtom(scopedProjectKey(ref)));
46}
47
48export function useEnvironmentProjectClones(
49 environmentId: EnvironmentId,
50): ReadonlyArray<ProjectCloneSnapshot> {
51 return useAtomValue(environmentProjectClonesAtom(environmentId));
52}