import { useAtomValue } from "@effect/atom-react"; import { EnvironmentId, type OrchestrationShellSnapshot } from "@t3tools/contracts"; import { AsyncResult, Atom } from "effect/unstable/reactivity"; import { useCallback, useMemo } from "react"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import { readEnvironmentApi } from "../environmentApi"; import { appAtomRegistry } from "../rpc/atomRegistry"; const ARCHIVED_THREADS_STALE_TIME_MS = 5_000; const ARCHIVED_THREADS_IDLE_TTL_MS = 5 * 60_000; const ARCHIVED_THREADS_ENVIRONMENT_KEY_SEPARATOR = "\u001f"; export type ArchivedSnapshotEntry = { readonly environmentId: EnvironmentId; readonly snapshot: OrchestrationShellSnapshot; }; const knownArchivedThreadEnvironmentKeys = new Set(); function makeArchivedThreadsEnvironmentKey(environmentIds: ReadonlyArray): string { return environmentIds.toSorted().join(ARCHIVED_THREADS_ENVIRONMENT_KEY_SEPARATOR); } function parseArchivedThreadsEnvironmentKey(key: string): ReadonlyArray { if (key.length === 0) { return []; } return key .split(ARCHIVED_THREADS_ENVIRONMENT_KEY_SEPARATOR) .map((environmentId) => EnvironmentId.make(environmentId)); } const archivedThreadSnapshotsAtom = Atom.family((environmentKey: string) => { knownArchivedThreadEnvironmentKeys.add(environmentKey); return Atom.make( Effect.promise(async (): Promise> => { const environmentIds = parseArchivedThreadsEnvironmentKey(environmentKey); const snapshots = await Promise.all( environmentIds.map(async (environmentId) => { const api = readEnvironmentApi(environmentId); if (!api) { return null; } return { environmentId, snapshot: await api.orchestration.getArchivedShellSnapshot(), }; }), ); return snapshots.filter((snapshot) => snapshot !== null); }), ).pipe( Atom.swr({ staleTime: ARCHIVED_THREADS_STALE_TIME_MS, revalidateOnMount: true, }), Atom.setIdleTTL(ARCHIVED_THREADS_IDLE_TTL_MS), Atom.withLabel(`archived-thread-snapshots:${environmentKey}`), ); }); function readArchivedThreadsError( result: AsyncResult.AsyncResult, unknown>, ): string | null { if (result._tag !== "Failure") { return null; } const error = Cause.squash(result.cause); return error instanceof Error ? error.message : "Failed to load archived threads."; } export function refreshArchivedThreadsForEnvironment(environmentId: EnvironmentId): void { for (const key of knownArchivedThreadEnvironmentKeys) { if (parseArchivedThreadsEnvironmentKey(key).includes(environmentId)) { appAtomRegistry.refresh(archivedThreadSnapshotsAtom(key)); } } } export function useArchivedThreadSnapshots(environmentIds: ReadonlyArray): { readonly snapshots: ReadonlyArray; readonly error: string | null; readonly isLoading: boolean; readonly refresh: () => void; } { const environmentKey = useMemo( () => makeArchivedThreadsEnvironmentKey(environmentIds), [environmentIds], ); const atom = archivedThreadSnapshotsAtom(environmentKey); const result = useAtomValue(atom); const snapshots = Option.getOrElse(AsyncResult.value(result), () => []); const refresh = useCallback(() => { appAtomRegistry.refresh(atom); }, [atom]); return { snapshots, error: readArchivedThreadsError(result), isLoading: result.waiting, refresh, }; }