import { type GitActionProgressEvent, type GitStackedAction, type ThreadId, } from "@t3tools/contracts"; import { infiniteQueryOptions, mutationOptions, queryOptions, type QueryClient, } from "@tanstack/react-query"; import { ensureNativeApi } from "../nativeApi"; import { getWsRpcClient } from "../wsRpcClient"; const GIT_BRANCHES_STALE_TIME_MS = 15_000; const GIT_BRANCHES_REFETCH_INTERVAL_MS = 60_000; const GIT_BRANCHES_PAGE_SIZE = 100; export const gitQueryKeys = { all: ["git"] as const, branches: (cwd: string | null) => ["git", "branches", cwd] as const, branchSearch: (cwd: string | null, query: string) => ["git", "branches", cwd, "search", query] as const, }; export const gitMutationKeys = { init: (cwd: string | null) => ["git", "mutation", "init", cwd] as const, checkout: (cwd: string | null) => ["git", "mutation", "checkout", cwd] as const, runStackedAction: (cwd: string | null) => ["git", "mutation", "run-stacked-action", cwd] as const, pull: (cwd: string | null) => ["git", "mutation", "pull", cwd] as const, preparePullRequestThread: (cwd: string | null) => ["git", "mutation", "prepare-pull-request-thread", cwd] as const, }; export function invalidateGitQueries(queryClient: QueryClient, input?: { cwd?: string | null }) { const cwd = input?.cwd ?? null; if (cwd !== null) { return queryClient.invalidateQueries({ queryKey: gitQueryKeys.branches(cwd) }); } return queryClient.invalidateQueries({ queryKey: gitQueryKeys.all }); } function invalidateGitBranchQueries(queryClient: QueryClient, cwd: string | null) { if (cwd === null) { return Promise.resolve(); } return queryClient.invalidateQueries({ queryKey: gitQueryKeys.branches(cwd) }); } export function gitBranchSearchInfiniteQueryOptions(input: { cwd: string | null; query: string; enabled?: boolean; }) { const normalizedQuery = input.query.trim(); return infiniteQueryOptions({ queryKey: gitQueryKeys.branchSearch(input.cwd, normalizedQuery), initialPageParam: 0, queryFn: async ({ pageParam }) => { const api = ensureNativeApi(); if (!input.cwd) throw new Error("Git branches are unavailable."); return api.git.listBranches({ cwd: input.cwd, ...(normalizedQuery.length > 0 ? { query: normalizedQuery } : {}), cursor: pageParam, limit: GIT_BRANCHES_PAGE_SIZE, }); }, getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, enabled: input.cwd !== null && (input.enabled ?? true), staleTime: GIT_BRANCHES_STALE_TIME_MS, refetchOnWindowFocus: true, refetchOnReconnect: true, refetchInterval: GIT_BRANCHES_REFETCH_INTERVAL_MS, }); } export function gitResolvePullRequestQueryOptions(input: { cwd: string | null; reference: string | null; }) { return queryOptions({ queryKey: ["git", "pull-request", input.cwd, input.reference] as const, queryFn: async () => { const api = ensureNativeApi(); if (!input.cwd || !input.reference) { throw new Error("Pull request lookup is unavailable."); } return api.git.resolvePullRequest({ cwd: input.cwd, reference: input.reference }); }, enabled: input.cwd !== null && input.reference !== null, staleTime: 30_000, refetchOnWindowFocus: false, refetchOnReconnect: false, }); } export function gitInitMutationOptions(input: { cwd: string | null; queryClient: QueryClient }) { return mutationOptions({ mutationKey: gitMutationKeys.init(input.cwd), mutationFn: async () => { const api = ensureNativeApi(); if (!input.cwd) throw new Error("Git init is unavailable."); return api.git.init({ cwd: input.cwd }); }, onSettled: async () => { await invalidateGitBranchQueries(input.queryClient, input.cwd); }, }); } export function gitCheckoutMutationOptions(input: { cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationKey: gitMutationKeys.checkout(input.cwd), mutationFn: async (branch: string) => { const api = ensureNativeApi(); if (!input.cwd) throw new Error("Git checkout is unavailable."); return api.git.checkout({ cwd: input.cwd, branch }); }, onSettled: async () => { await invalidateGitBranchQueries(input.queryClient, input.cwd); }, }); } export function gitRunStackedActionMutationOptions(input: { cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationKey: gitMutationKeys.runStackedAction(input.cwd), mutationFn: async ({ actionId, action, commitMessage, featureBranch, filePaths, onProgress, }: { actionId: string; action: GitStackedAction; commitMessage?: string; featureBranch?: boolean; filePaths?: string[]; onProgress?: (event: GitActionProgressEvent) => void; }) => { if (!input.cwd) throw new Error("Git action is unavailable."); return getWsRpcClient().git.runStackedAction( { action, actionId, cwd: input.cwd, ...(commitMessage ? { commitMessage } : {}), ...(featureBranch ? { featureBranch: true } : {}), ...(filePaths && filePaths.length > 0 ? { filePaths } : {}), }, ...(onProgress ? [{ onProgress }] : []), ); }, onSuccess: async () => { await invalidateGitBranchQueries(input.queryClient, input.cwd); }, }); } export function gitPullMutationOptions(input: { cwd: string | null; queryClient: QueryClient }) { return mutationOptions({ mutationKey: gitMutationKeys.pull(input.cwd), mutationFn: async () => { const api = ensureNativeApi(); if (!input.cwd) throw new Error("Git pull is unavailable."); return api.git.pull({ cwd: input.cwd }); }, onSuccess: async () => { await invalidateGitBranchQueries(input.queryClient, input.cwd); }, }); } export function gitCreateWorktreeMutationOptions(input: { queryClient: QueryClient }) { return mutationOptions({ mutationKey: ["git", "mutation", "create-worktree"] as const, mutationFn: ( args: Parameters["git"]["createWorktree"]>[0], ) => ensureNativeApi().git.createWorktree(args), onSuccess: async () => { await invalidateGitQueries(input.queryClient); }, }); } export function gitRemoveWorktreeMutationOptions(input: { queryClient: QueryClient }) { return mutationOptions({ mutationKey: ["git", "mutation", "remove-worktree"] as const, mutationFn: ( args: Parameters["git"]["removeWorktree"]>[0], ) => ensureNativeApi().git.removeWorktree(args), onSuccess: async () => { await invalidateGitQueries(input.queryClient); }, }); } export function gitPreparePullRequestThreadMutationOptions(input: { cwd: string | null; queryClient: QueryClient; }) { return mutationOptions({ mutationKey: gitMutationKeys.preparePullRequestThread(input.cwd), mutationFn: async (args: { reference: string; mode: "local" | "worktree"; threadId?: ThreadId; }) => { const api = ensureNativeApi(); if (!input.cwd) throw new Error("Pull request thread preparation is unavailable."); return api.git.preparePullRequestThread({ cwd: input.cwd, reference: args.reference, mode: args.mode, ...(args.threadId ? { threadId: args.threadId } : {}), }); }, onSuccess: async () => { await invalidateGitBranchQueries(input.queryClient, input.cwd); }, }); }