import { useAtomRefresh, useAtomValue } from "@effect/atom-react";import type { EnvironmentId, ProjectListEntriesResult, ProjectReadFileResult,} from "@t3tools/contracts";import * as Cause from "effect/Cause";import * as Option from "effect/Option";import { AsyncResult, Atom } from "effect/unstable/reactivity";import { useCallback } from "react"; import { appAtomRegistry } from "~/rpc/atomRegistry";import { projectEnvironment } from "~/state/projects";import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime"; const EMPTY_PROJECT_FILE_PATH = "";const EMPTY_PROJECT_FILE_QUERY_ATOM = Atom.make( AsyncResult.initial<ProjectReadFileResult, never>(false),).pipe(Atom.withLabel("project-file-query:empty"));function optimisticFileAtom(environmentId: EnvironmentId, cwd: string, relativePath: string) { return projectEnvironment.optimisticFile({ environmentId, cwd, relativePath });} interface ProjectQueryState<A> { readonly data: A | null; readonly error: string | null; readonly isPending: boolean; readonly refresh: () => void;} export function getProjectEntriesQueryAtom(environmentId: EnvironmentId, cwd: string) { return projectEnvironment.listEntries({ environmentId, input: { cwd } });} export function getProjectFileQueryAtom( environmentId: EnvironmentId, cwd: string, relativePath: string | null,) { return projectEnvironment.readFile({ environmentId, input: { cwd, relativePath: relativePath ?? EMPTY_PROJECT_FILE_PATH }, });} export function setProjectFileQueryData( environmentId: EnvironmentId, cwd: string, relativePath: string, contents: string,): void { appAtomRegistry.set(optimisticFileAtom(environmentId, cwd, relativePath), { confirmedAgainst: undefined, data: { relativePath, contents, byteLength: new TextEncoder().encode(contents).byteLength, truncated: false, }, });} export function getOptimisticProjectFileQueryData( environmentId: EnvironmentId, cwd: string, relativePath: string,): ProjectReadFileResult | null { return appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath))?.data ?? null;} export function confirmProjectFileQueryData( environmentId: EnvironmentId, cwd: string, relativePath: string, contents: string,): boolean { const atom = optimisticFileAtom(environmentId, cwd, relativePath); const optimisticFile = appAtomRegistry.get(atom); if (optimisticFile?.data.contents !== contents) return false; const queryAtom = getProjectFileQueryAtom(environmentId, cwd, relativePath); const confirmed = { ...optimisticFile, confirmedAgainst: appAtomRegistry.get(queryAtom), }; appAtomRegistry.set(atom, confirmed); appAtomRegistry.refresh(queryAtom); void executeAtomQuery(appAtomRegistry, queryAtom, { reportDefect: false, reportFailure: false, }).then((result) => { if (result._tag === "Success" && appAtomRegistry.get(atom) === confirmed) { appAtomRegistry.set(atom, null); } }); return true;} export function resolveProjectFileQueryData( environmentId: EnvironmentId, cwd: string, relativePath: string | null, data: ProjectReadFileResult | null,): ProjectReadFileResult | null { if (relativePath === null) return data; return appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath))?.data ?? data;} export function clearProjectFileQueryData( environmentId: EnvironmentId, cwd: string, relativePath: string,): void { appAtomRegistry.set(optimisticFileAtom(environmentId, cwd, relativePath), null);} function errorMessage<A>(result: AsyncResult.AsyncResult<A, unknown>): string | null { if (result._tag !== "Failure") return null; const cause = Cause.squash(result.cause); return cause instanceof Error ? cause.message : "Workspace query failed.";} export function useProjectEntriesQuery( environmentId: EnvironmentId, cwd: string,): ProjectQueryState<ProjectListEntriesResult> { const atom = getProjectEntriesQueryAtom(environmentId, cwd); const result = useAtomValue(atom); const refreshAtom = useAtomRefresh(atom); const refresh = useCallback(() => refreshAtom(), [refreshAtom]); return { data: Option.getOrNull(AsyncResult.value(result)), error: errorMessage(result), isPending: result.waiting, refresh, };} export function useProjectFileQuery( environmentId: EnvironmentId, cwd: string, relativePath: string | null, enabled = true,): ProjectQueryState<ProjectReadFileResult> { const atom = enabled ? getProjectFileQueryAtom(environmentId, cwd, relativePath) : EMPTY_PROJECT_FILE_QUERY_ATOM; const result = useAtomValue(atom); const refreshAtom = useAtomRefresh(atom); const refresh = useCallback(() => refreshAtom(), [refreshAtom]); const data = Option.getOrNull(AsyncResult.value(result)); const optimisticResult = useAtomValue( optimisticFileAtom(environmentId, cwd, relativePath ?? EMPTY_PROJECT_FILE_PATH), ); const optimisticFile = relativePath === null ? null : optimisticResult; return { data: optimisticFile?.data ?? data, error: errorMessage(result), isPending: result.waiting, refresh, };}
import { useAtomRefresh, useAtomValue } from "@effect/atom-react";
import type {
EnvironmentId,
ProjectListEntriesResult,
ProjectReadFileResult,
} from "@t3tools/contracts";
import * as Cause from "effect/Cause";
import * as Option from "effect/Option";
import { AsyncResult, Atom } from "effect/unstable/reactivity";
import { useCallback } from "react";
import { appAtomRegistry } from "~/rpc/atomRegistry";
import { projectEnvironment } from "~/state/projects";
import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime";
const EMPTY_PROJECT_FILE_PATH = "";
const EMPTY_PROJECT_FILE_QUERY_ATOM = Atom.make(
AsyncResult.initial<ProjectReadFileResult, never>(false),
).pipe(Atom.withLabel("project-file-query:empty"));
function optimisticFileAtom(environmentId: EnvironmentId, cwd: string, relativePath: string) {
return projectEnvironment.optimisticFile({ environmentId, cwd, relativePath });
}
interface ProjectQueryState<A> {
readonly data: A | null;
readonly error: string | null;
readonly isPending: boolean;
readonly refresh: () => void;
}
export function getProjectEntriesQueryAtom(environmentId: EnvironmentId, cwd: string) {
return projectEnvironment.listEntries({ environmentId, input: { cwd } });
}
export function getProjectFileQueryAtom(
environmentId: EnvironmentId,
cwd: string,
relativePath: string | null,
) {
return projectEnvironment.readFile({
environmentId,
input: { cwd, relativePath: relativePath ?? EMPTY_PROJECT_FILE_PATH },
});
}
export function setProjectFileQueryData(
environmentId: EnvironmentId,
cwd: string,
relativePath: string,
contents: string,
): void {
appAtomRegistry.set(optimisticFileAtom(environmentId, cwd, relativePath), {
confirmedAgainst: undefined,
data: {
relativePath,
contents,
byteLength: new TextEncoder().encode(contents).byteLength,
truncated: false,
},
});
}
export function getOptimisticProjectFileQueryData(
environmentId: EnvironmentId,
cwd: string,
relativePath: string,
): ProjectReadFileResult | null {
return appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath))?.data ?? null;
}
export function confirmProjectFileQueryData(
environmentId: EnvironmentId,
cwd: string,
relativePath: string,
contents: string,
): boolean {
const atom = optimisticFileAtom(environmentId, cwd, relativePath);
const optimisticFile = appAtomRegistry.get(atom);
if (optimisticFile?.data.contents !== contents) return false;
const queryAtom = getProjectFileQueryAtom(environmentId, cwd, relativePath);
const confirmed = {
...optimisticFile,
confirmedAgainst: appAtomRegistry.get(queryAtom),
};
appAtomRegistry.set(atom, confirmed);
appAtomRegistry.refresh(queryAtom);
void executeAtomQuery(appAtomRegistry, queryAtom, {
reportDefect: false,
reportFailure: false,
}).then((result) => {
if (result._tag === "Success" && appAtomRegistry.get(atom) === confirmed) {
appAtomRegistry.set(atom, null);
}
});
return true;
}
export function resolveProjectFileQueryData(
environmentId: EnvironmentId,
cwd: string,
relativePath: string | null,
data: ProjectReadFileResult | null,
): ProjectReadFileResult | null {
if (relativePath === null) return data;
return appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath))?.data ?? data;
}
export function clearProjectFileQueryData(
environmentId: EnvironmentId,
cwd: string,
relativePath: string,
): void {
appAtomRegistry.set(optimisticFileAtom(environmentId, cwd, relativePath), null);
}
function errorMessage<A>(result: AsyncResult.AsyncResult<A, unknown>): string | null {
if (result._tag !== "Failure") return null;
const cause = Cause.squash(result.cause);
return cause instanceof Error ? cause.message : "Workspace query failed.";
}
export function useProjectEntriesQuery(
environmentId: EnvironmentId,
cwd: string,
): ProjectQueryState<ProjectListEntriesResult> {
const atom = getProjectEntriesQueryAtom(environmentId, cwd);
const result = useAtomValue(atom);
const refreshAtom = useAtomRefresh(atom);
const refresh = useCallback(() => refreshAtom(), [refreshAtom]);
return {
data: Option.getOrNull(AsyncResult.value(result)),
error: errorMessage(result),
isPending: result.waiting,
refresh,
};
}
export function useProjectFileQuery(
environmentId: EnvironmentId,
cwd: string,
relativePath: string | null,
enabled = true,
): ProjectQueryState<ProjectReadFileResult> {
const atom = enabled
? getProjectFileQueryAtom(environmentId, cwd, relativePath)
: EMPTY_PROJECT_FILE_QUERY_ATOM;
const result = useAtomValue(atom);
const refreshAtom = useAtomRefresh(atom);
const refresh = useCallback(() => refreshAtom(), [refreshAtom]);
const data = Option.getOrNull(AsyncResult.value(result));
const optimisticResult = useAtomValue(
optimisticFileAtom(environmentId, cwd, relativePath ?? EMPTY_PROJECT_FILE_PATH),
);
const optimisticFile = relativePath === null ? null : optimisticResult;
return {
data: optimisticFile?.data ?? data,
error: errorMessage(result),
isPending: result.waiting,
refresh,
};
}