206 lines · 6.5 KB · TypeScript
RawDownload
import type { SourceControlDiscoveryResult } from "@t3tools/contracts";import { Atom, type AtomRegistry } from "effect/unstable/reactivity"; /* --- Types ---------------------------------------------------------- */ export interface SourceControlDiscoveryState {  readonly data: SourceControlDiscoveryResult | null;  readonly error: string | null;  readonly isPending: boolean;} export interface SourceControlDiscoveryTarget {  readonly key: string | null;} export interface SourceControlDiscoveryClient {  readonly discoverSourceControl: () => Promise<SourceControlDiscoveryResult>;} /* --- Constants ------------------------------------------------------ */ export const EMPTY_SOURCE_CONTROL_DISCOVERY_STATE = Object.freeze<SourceControlDiscoveryState>({  data: null,  error: null,  isPending: false,}); const INITIAL_SOURCE_CONTROL_DISCOVERY_STATE = Object.freeze<SourceControlDiscoveryState>({  data: null,  error: null,  isPending: true,}); /* --- Atoms ---------------------------------------------------------- */ const knownSourceControlDiscoveryKeys = new Set<string>(); export const sourceControlDiscoveryStateAtom = Atom.family((key: string) => {  knownSourceControlDiscoveryKeys.add(key);  return Atom.make(INITIAL_SOURCE_CONTROL_DISCOVERY_STATE).pipe(    Atom.keepAlive,    Atom.withLabel(`source-control-discovery:${key}`),  );}); export const EMPTY_SOURCE_CONTROL_DISCOVERY_ATOM = Atom.make(  EMPTY_SOURCE_CONTROL_DISCOVERY_STATE,).pipe(Atom.keepAlive, Atom.withLabel("source-control-discovery:null")); /* --- Helpers -------------------------------------------------------- */ export function getSourceControlDiscoveryTargetKey(  target: SourceControlDiscoveryTarget,): string | null {  const key = target.key?.trim();  return key && key.length > 0 ? key : null;} /* --- Refresh manager ------------------------------------------------ */ export interface SourceControlDiscoveryManagerConfig {  /**   * Get the atom registry used to read/write source-control discovery snapshots.   */  readonly getRegistry: () => AtomRegistry.AtomRegistry;  /**   * Resolve the runtime client for a discovery target key.   *   * Web currently uses a single `"primary"` target, but keeping this keyed   * lets mobile or future multi-environment clients provide separate discovery   * clients without changing the state primitive.   */  readonly getClient: (key: string) => SourceControlDiscoveryClient | null;} export function createSourceControlDiscoveryManager(config: SourceControlDiscoveryManagerConfig) {  const refreshInFlight = new Map<string, Promise<SourceControlDiscoveryResult | null>>();   /* -- Atom helpers -------------------------------------------------- */   function setState(targetKey: string, nextState: SourceControlDiscoveryState): void {    config.getRegistry().set(sourceControlDiscoveryStateAtom(targetKey), nextState);  }   function markPending(targetKey: string): void {    const current = config.getRegistry().get(sourceControlDiscoveryStateAtom(targetKey));    const next: SourceControlDiscoveryState =      current.data === null        ? INITIAL_SOURCE_CONTROL_DISCOVERY_STATE        : {            data: current.data,            error: null,            isPending: true,          };     if (      current.data === next.data &&      current.error === next.error &&      current.isPending === next.isPending    ) {      return;    }     setState(targetKey, next);  }   function setData(targetKey: string, data: SourceControlDiscoveryResult): void {    setState(targetKey, {      data,      error: null,      isPending: false,    });  }   function setError(targetKey: string, error: unknown): void {    const current = config.getRegistry().get(sourceControlDiscoveryStateAtom(targetKey));    setState(targetKey, {      data: current.data,      error: error instanceof Error ? error.message : "Failed to discover source control tools.",      isPending: false,    });  }   /* -- Public API ---------------------------------------------------- */   /**   * Trigger a one-shot source-control discovery RPC for a target.   *   * Calls are deduplicated while a refresh for the same target key is in   * flight. On failure, the previous successful snapshot is kept in `data`   * and the error message is stored separately so UI can keep rendering stale   * discovery results while showing the failure.   *   * @param target The logical runtime target to refresh.   * @param client Optional pre-resolved client, useful in tests.   */  function refresh(    target: SourceControlDiscoveryTarget,    client?: SourceControlDiscoveryClient,  ): Promise<SourceControlDiscoveryResult | null> {    const targetKey = getSourceControlDiscoveryTargetKey(target);    if (targetKey === null) {      return Promise.resolve(null);    }     const existing = refreshInFlight.get(targetKey);    if (existing) {      return existing;    }     const resolvedClient = client ?? config.getClient(targetKey);    if (!resolvedClient) {      const error = new Error("Source control discovery client is unavailable.");      setError(targetKey, error);      return Promise.resolve(getSnapshot(target).data);    }     markPending(targetKey);    const promise = resolvedClient.discoverSourceControl().then(      (result) => {        setData(targetKey, result);        return result;      },      (error: unknown) => {        setError(targetKey, error);        return getSnapshot(target).data;      },    );    const tracked = promise.finally(() => refreshInFlight.delete(targetKey));    refreshInFlight.set(targetKey, tracked);    return tracked;  }   /**   * Read the current atom snapshot for `target`.   *   * Invalid targets return the inert empty state rather than creating a new   * family atom entry.   */  function getSnapshot(target: SourceControlDiscoveryTarget): SourceControlDiscoveryState {    const targetKey = getSourceControlDiscoveryTargetKey(target);    if (targetKey === null) {      return EMPTY_SOURCE_CONTROL_DISCOVERY_STATE;    }     return config.getRegistry().get(sourceControlDiscoveryStateAtom(targetKey));  }   /**   * Clear in-flight refresh tracking and reset every known discovery atom.   * Primarily used by tests and runtime teardown.   */  function reset(): void {    refreshInFlight.clear();    for (const key of knownSourceControlDiscoveryKeys) {      setState(key, INITIAL_SOURCE_CONTROL_DISCOVERY_STATE);    }    knownSourceControlDiscoveryKeys.clear();  }   return {    refresh,    getSnapshot,    reset,  };}