287 lines · 7.6 KB · TypeScript
RawDownload
import { useAtomValue } from "@effect/atom-react";import {  type EnvironmentId,  type GitManagerServiceError,  type VcsStatusResult,} from "@t3tools/contracts";import { Cause } from "effect";import { Atom } from "effect/unstable/reactivity";import { useEffect } from "react"; import { appAtomRegistry } from "../rpc/atomRegistry";import {  readEnvironmentConnection,  subscribeEnvironmentConnections,} from "../environments/runtime";import type { WsRpcClient } from "~/rpc/wsRpcClient"; interface GitStatusState {  readonly data: VcsStatusResult | null;  readonly error: GitManagerServiceError | null;  readonly cause: Cause.Cause<GitManagerServiceError> | null;  readonly isPending: boolean;} type GitStatusClient = Pick<WsRpcClient["vcs"], "onStatus" | "refreshStatus">;interface ResolvedGitStatusClient {  readonly clientIdentity: string;  readonly client: GitStatusClient;} interface WatchedGitStatus {  refCount: number;  unsubscribe: () => void;} interface GitStatusTarget {  readonly environmentId: EnvironmentId | null;  readonly cwd: string | null;} const EMPTY_GIT_STATUS_STATE = Object.freeze<GitStatusState>({  data: null,  error: null,  cause: null,  isPending: false,});const INITIAL_GIT_STATUS_STATE = Object.freeze<GitStatusState>({  ...EMPTY_GIT_STATUS_STATE,  isPending: true,});const EMPTY_GIT_STATUS_ATOM = Atom.make(EMPTY_GIT_STATUS_STATE).pipe(  Atom.keepAlive,  Atom.withLabel("git-status:null"),); const NOOP: () => void = () => undefined;const watchedGitStatuses = new Map<string, WatchedGitStatus>();const knownGitStatusKeys = new Set<string>();const gitStatusRefreshInFlight = new Map<string, Promise<VcsStatusResult>>();const gitStatusLastRefreshAtByKey = new Map<string, number>(); const GIT_STATUS_REFRESH_DEBOUNCE_MS = 1_000; const gitStatusStateAtom = Atom.family((key: string) => {  knownGitStatusKeys.add(key);  return Atom.make(INITIAL_GIT_STATUS_STATE).pipe(    Atom.keepAlive,    Atom.withLabel(`git-status:${key}`),  );}); function getGitStatusTargetKey(target: GitStatusTarget): string | null {  if (target.environmentId === null || target.cwd === null) {    return null;  }   return `${target.environmentId}:${target.cwd}`;} function readResolvedGitStatusClient(target: GitStatusTarget): ResolvedGitStatusClient | null {  if (target.environmentId === null) {    return null;  }  const connection = readEnvironmentConnection(target.environmentId);  return connection    ? { clientIdentity: connection.environmentId, client: connection.client.vcs }    : null;} export function getGitStatusSnapshot(target: GitStatusTarget): GitStatusState {  const targetKey = getGitStatusTargetKey(target);  if (targetKey === null) {    return EMPTY_GIT_STATUS_STATE;  }   return appAtomRegistry.get(gitStatusStateAtom(targetKey));} export function watchGitStatus(target: GitStatusTarget, client?: GitStatusClient): () => void {  const targetKey = getGitStatusTargetKey(target);  if (targetKey === null) {    return NOOP;  }   const watched = watchedGitStatuses.get(targetKey);  if (watched) {    watched.refCount += 1;    return () => unwatchGitStatus(targetKey);  }   watchedGitStatuses.set(targetKey, {    refCount: 1,    unsubscribe: subscribeToGitStatusTarget(targetKey, target, client),  });   return () => unwatchGitStatus(targetKey);} export function refreshGitStatus(  target: GitStatusTarget,  client?: GitStatusClient,): Promise<VcsStatusResult | null> {  const targetKey = getGitStatusTargetKey(target);  if (targetKey === null || target.cwd === null) {    return Promise.resolve(null);  }   const resolvedClient = client ?? readResolvedGitStatusClient(target)?.client;  if (!resolvedClient) {    return Promise.resolve(getGitStatusSnapshot(target).data);  }   const currentInFlight = gitStatusRefreshInFlight.get(targetKey);  if (currentInFlight) {    return currentInFlight;  }   const lastRequestedAt = gitStatusLastRefreshAtByKey.get(targetKey) ?? 0;  if (Date.now() - lastRequestedAt < GIT_STATUS_REFRESH_DEBOUNCE_MS) {    return Promise.resolve(getGitStatusSnapshot(target).data);  }   gitStatusLastRefreshAtByKey.set(targetKey, Date.now());  const refreshPromise = resolvedClient.refreshStatus({ cwd: target.cwd }).finally(() => {    gitStatusRefreshInFlight.delete(targetKey);  });  gitStatusRefreshInFlight.set(targetKey, refreshPromise);  return refreshPromise;} export function resetGitStatusStateForTests(): void {  for (const watched of watchedGitStatuses.values()) {    watched.unsubscribe();  }  watchedGitStatuses.clear();  gitStatusRefreshInFlight.clear();  gitStatusLastRefreshAtByKey.clear();   for (const key of knownGitStatusKeys) {    appAtomRegistry.set(gitStatusStateAtom(key), INITIAL_GIT_STATUS_STATE);  }  knownGitStatusKeys.clear();} export function useGitStatus(target: GitStatusTarget): GitStatusState {  const targetKey = getGitStatusTargetKey(target);  useEffect(    () => watchGitStatus({ environmentId: target.environmentId, cwd: target.cwd }),    [target.environmentId, target.cwd],  );   const state = useAtomValue(    targetKey !== null ? gitStatusStateAtom(targetKey) : EMPTY_GIT_STATUS_ATOM,  );  return targetKey === null ? EMPTY_GIT_STATUS_STATE : state;} function unwatchGitStatus(targetKey: string): void {  const watched = watchedGitStatuses.get(targetKey);  if (!watched) {    return;  }   watched.refCount -= 1;  if (watched.refCount > 0) {    return;  }   watched.unsubscribe();  watchedGitStatuses.delete(targetKey);} function subscribeToGitStatusTarget(  targetKey: string,  target: GitStatusTarget,  providedClient?: GitStatusClient,): () => void {  if (target.cwd === null) {    return NOOP;  }   const cwd = target.cwd;  let currentClientIdentity: string | null = null;  let currentUnsubscribe = NOOP;   const syncClientSubscription = () => {    const resolved = providedClient      ? {          clientIdentity: `provided:${targetKey}`,          client: providedClient,        }      : readResolvedGitStatusClient(target);     if (!resolved) {      if (currentClientIdentity !== null) {        currentUnsubscribe();        currentUnsubscribe = NOOP;        currentClientIdentity = null;      }      markGitStatusPending(targetKey);      return;    }     if (currentClientIdentity === resolved.clientIdentity) {      return;    }     currentUnsubscribe();    currentClientIdentity = resolved.clientIdentity;    currentUnsubscribe = subscribeToGitStatus(targetKey, cwd, resolved.client);  };   const unsubscribeRegistry = providedClient    ? NOOP    : subscribeEnvironmentConnections(syncClientSubscription);  syncClientSubscription();   return () => {    unsubscribeRegistry();    currentUnsubscribe();  };} function subscribeToGitStatus(targetKey: string, cwd: string, client: GitStatusClient): () => void {  markGitStatusPending(targetKey);  return client.onStatus(    { cwd },    (status: VcsStatusResult) => {      appAtomRegistry.set(gitStatusStateAtom(targetKey), {        data: status,        error: null,        cause: null,        isPending: false,      });    },    {      onResubscribe: () => {        markGitStatusPending(targetKey);      },    },  );} function markGitStatusPending(targetKey: string): void {  const atom = gitStatusStateAtom(targetKey);  const current = appAtomRegistry.get(atom);  const next =    current.data === null      ? INITIAL_GIT_STATUS_STATE      : {          ...current,          error: null,          cause: null,          isPending: true,        };   if (    current.data === next.data &&    current.error === next.error &&    current.cause === next.cause &&    current.isPending === next.isPending  ) {    return;  }   appAtomRegistry.set(atom, next);}