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; } /* --- Constants ------------------------------------------------------ */ export const EMPTY_SOURCE_CONTROL_DISCOVERY_STATE = Object.freeze({ data: null, error: null, isPending: false, }); const INITIAL_SOURCE_CONTROL_DISCOVERY_STATE = Object.freeze({ data: null, error: null, isPending: true, }); /* --- Atoms ---------------------------------------------------------- */ const knownSourceControlDiscoveryKeys = new Set(); 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>(); /* -- 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 { 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, }; }