Просмотр исходного кода

fix(app): require query functions for sync queries (#25939)

Luke Parker 4 месяцев назад
Родитель
Сommit
f5c3d352a1

+ 2 - 2
packages/app/src/components/dialog-select-mcp.tsx

@@ -6,7 +6,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
 import { List } from "@opencode-ai/ui/list"
 import { Switch } from "@opencode-ai/ui/switch"
 import { useLanguage } from "@/context/language"
-import { loadMcpQuery } from "@/context/global-sync"
+import { mcpQueryKey } from "@/context/global-sync"
 
 const statusLabels = {
   connected: "mcp.status.connected",
@@ -32,7 +32,7 @@ export const DialogSelectMcp: Component = () => {
       if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
       else await sdk.client.mcp.connect({ name })
     },
-    onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
+    onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
   }))
 
   const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)

+ 7 - 1
packages/app/src/components/prompt-input.tsx

@@ -16,6 +16,7 @@ import {
 } from "@/context/prompt"
 import { useLayout } from "@/context/layout"
 import { useSDK } from "@/context/sdk"
+import { useGlobalSDK } from "@/context/global-sdk"
 import { useSync } from "@/context/sync"
 import { useComments } from "@/context/comments"
 import { Button } from "@opencode-ai/ui/button"
@@ -102,6 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
 
 export const PromptInput: Component<PromptInputProps> = (props) => {
   const sdk = useSDK()
+  const globalSDK = useGlobalSDK()
 
   const sync = useSync()
   const local = useLocal()
@@ -1253,7 +1255,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
   }
 
   const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
-    queries: [loadAgentsQuery(sdk.directory), loadProvidersQuery(null), loadProvidersQuery(sdk.directory)],
+    queries: [
+      loadAgentsQuery(sdk.directory, sdk.client),
+      loadProvidersQuery(null, globalSDK.client),
+      loadProvidersQuery(sdk.directory, sdk.client),
+    ],
   }))
 
   const agentsLoading = () => agentsQuery.isLoading

+ 2 - 2
packages/app/src/components/status-popover-body.tsx

@@ -15,7 +15,7 @@ import { useSDK } from "@/context/sdk"
 import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
 import { useSync } from "@/context/sync"
 import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
-import { loadMcpQuery } from "@/context/global-sync"
+import { mcpQueryKey } from "@/context/global-sync"
 
 const pollMs = 10_000
 
@@ -145,7 +145,7 @@ const useMcpToggleMutation = () => {
       const status = sync.data.mcp[name]
       await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
     },
-    onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
+    onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
     onError: (err) => {
       showToast({
         variant: "error",

+ 18 - 12
packages/app/src/context/global-sync.tsx

@@ -20,7 +20,6 @@ import {
   clearProviderRev,
   loadGlobalConfigQuery,
   loadPathQuery,
-  loadProjectsQuery,
   loadProvidersQuery,
 } from "./global-sync/bootstrap"
 import { createChildStoreManager } from "./global-sync/child-store"
@@ -31,7 +30,7 @@ import { trimSessions } from "./global-sync/session-trim"
 import type { ProjectMeta } from "./global-sync/types"
 import { SESSION_RECENT_LIMIT } from "./global-sync/types"
 import { formatServerError } from "@/utils/server-errors"
-import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
+import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
 import { createRefreshQueue } from "./global-sync/queue"
 import { directoryKey } from "./global-sync/utils"
 
@@ -49,19 +48,22 @@ type GlobalStore = {
   reload: undefined | "pending" | "complete"
 }
 
-export const loadSessionsQuery = (directory: string) =>
-  queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
+export const loadSessionsQueryKey = (directory: string) => [directory, "loadSessions"] as const
 
-export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) =>
+export const mcpQueryKey = (directory: string) => [directory, "mcp"] as const
+
+export const loadMcpQuery = (directory: string, sdk: OpencodeClient) =>
   queryOptions({
-    queryKey: [directory, "mcp"],
-    queryFn: sdk ? () => sdk.mcp.status().then((r) => r.data ?? {}) : skipToken,
+    queryKey: mcpQueryKey(directory),
+    queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
   })
 
-export const loadLspQuery = (directory: string, sdk?: OpencodeClient) =>
+export const lspQueryKey = (directory: string) => [directory, "lsp"] as const
+
+export const loadLspQuery = (directory: string, sdk: OpencodeClient) =>
   queryOptions({
-    queryKey: [directory, "lsp"],
-    queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken,
+    queryKey: lspQueryKey(directory),
+    queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
   })
 
 function createGlobalSync() {
@@ -76,7 +78,11 @@ function createGlobalSync() {
   const sessionMeta = new Map<string, { limit: number }>()
 
   const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
-    queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()],
+    queries: [
+      loadGlobalConfigQuery(globalSDK.client),
+      loadProvidersQuery(null, globalSDK.client),
+      loadPathQuery(null, globalSDK.client),
+    ],
   }))
 
   const [globalStore, setGlobalStore] = createStore<GlobalStore>({
@@ -233,7 +239,7 @@ function createGlobalSync() {
     const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
     const promise = queryClient
       .fetchQuery({
-        ...loadSessionsQuery(key),
+        queryKey: loadSessionsQueryKey(key),
         queryFn: () =>
           loadRootSessionsWithFallback({
             directory,

+ 30 - 75
packages/app/src/context/global-sync/bootstrap.ts

@@ -18,7 +18,7 @@ import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
 import type { State, VcsCache } from "./types"
 import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
 import { formatServerError } from "@/utils/server-errors"
-import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
+import { QueryClient, queryOptions } from "@tanstack/solid-query"
 import { loadMcpQuery } from "../global-sync"
 
 type GlobalStore = {
@@ -83,44 +83,25 @@ function showErrors(input: {
   })
 }
 
-export const loadGlobalConfigQuery = (
-  sdk?: OpencodeClient,
-  transform?: (x: Awaited<ReturnType<OpencodeClient["global"]["config"]["get"]>>) => void,
-) =>
+export const loadGlobalConfigQuery = (sdk: OpencodeClient) =>
   queryOptions({
     queryKey: ["config"],
-    queryFn: sdk
-      ? () =>
-          retry(() =>
-            sdk.global.config.get().then((x) => {
-              transform?.(x)
-              return x.data!
-            }),
-          )
-      : skipToken,
+    queryFn: () => retry(() => sdk.global.config.get().then((x) => x.data!)),
   })
 
-export const loadProjectsQuery = (
-  sdk?: OpencodeClient,
-  transform?: (x: Awaited<ReturnType<OpencodeClient["project"]["list"]>>["data"]) => void,
-) =>
+export const loadProjectsQuery = (sdk: OpencodeClient) =>
   queryOptions({
     queryKey: ["project"],
-    queryFn: sdk
-      ? () =>
-          retry(() =>
-            sdk.project
-              .list()
-              .then((x) => {
-                return (x.data ?? [])
-                  .filter((p) => !!p?.id)
-                  .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
-                  .slice()
-                  .sort((a, b) => cmp(a.id, b.id))
-              })
-              .then(transform),
-          )
-      : skipToken,
+    queryFn: () =>
+      retry(() =>
+        sdk.project.list().then((x) => {
+          return (x.data ?? [])
+            .filter((p) => !!p?.id)
+            .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
+            .slice()
+            .sort((a, b) => cmp(a.id, b.id))
+        }),
+      ),
   })
 
 export async function bootstrapGlobal(input: {
@@ -136,9 +117,9 @@ export async function bootstrapGlobal(input: {
     () => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
     () => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
     () =>
-      input.queryClient.fetchQuery(
-        loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])),
-      ),
+      input.queryClient
+        .fetchQuery(loadProjectsQuery(input.globalSDK))
+        .then((data) => input.setGlobalStore("project", data)),
   ]
   await runAll(slow)
   // showErrors({
@@ -197,46 +178,22 @@ function warmSessions(input: {
   ).then(() => undefined)
 }
 
-export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) =>
+export const loadProvidersQuery = (directory: string | null, sdk: OpencodeClient) =>
   queryOptions({
     queryKey: [directory, "providers"],
-    queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken,
+    queryFn: () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))),
   })
 
-export const loadAgentsQuery = (
-  directory: string | null,
-  sdk?: OpencodeClient,
-  transform?: (x: Awaited<ReturnType<OpencodeClient["app"]["agents"]>>) => void,
-) =>
+export const loadAgentsQuery = (directory: string | null, sdk: OpencodeClient) =>
   queryOptions({
     queryKey: [directory, "agents"],
-    queryFn: sdk
-      ? () =>
-          retry(() =>
-            sdk.app.agents().then((x) => {
-              transform?.(x)
-              return x.data!
-            }),
-          )
-      : skipToken,
+    queryFn: () => retry(() => sdk.app.agents().then((x) => normalizeAgentList(x.data))),
   })
 
-export const loadPathQuery = (
-  directory: string | null,
-  sdk?: OpencodeClient,
-  transform?: (x: Awaited<ReturnType<OpencodeClient["path"]["get"]>>) => void,
-) =>
+export const loadPathQuery = (directory: string | null, sdk: OpencodeClient) =>
   queryOptions<Path>({
     queryKey: [directory, "path"],
-    queryFn: sdk
-      ? () =>
-          retry(() =>
-            sdk.path.get().then(async (x) => {
-              transform?.(x)
-              return x.data!
-            }),
-          )
-      : skipToken,
+    queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
   })
 
 export async function bootstrapDirectory(input: {
@@ -271,9 +228,9 @@ export async function bootstrapDirectory(input: {
     const slow = [
       () => Promise.resolve(input.loadSessions(input.directory)),
       () =>
-        input.queryClient.ensureQueryData(
-          loadAgentsQuery(input.directory, input.sdk, (x) => input.setStore("agent", normalizeAgentList(x.data))),
-        ),
+        input.queryClient
+          .ensureQueryData(loadAgentsQuery(input.directory, input.sdk))
+          .then((data) => input.setStore("agent", data)),
       () =>
         retry(() => input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))),
       () => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
@@ -281,12 +238,10 @@ export async function bootstrapDirectory(input: {
         (() => retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id))),
       !seededPath &&
         (() =>
-          input.queryClient.ensureQueryData(
-            loadPathQuery(input.directory, input.sdk, (x) => {
-              const next = projectID(x.data?.directory ?? input.directory, input.global.project)
-              if (next) input.setStore("project", next)
-            }),
-          )),
+          input.queryClient.ensureQueryData(loadPathQuery(input.directory, input.sdk)).then((data) => {
+            const next = projectID(data.directory ?? input.directory, input.global.project)
+            if (next) input.setStore("project", next)
+          })),
       () =>
         retry(() =>
           input.sdk.vcs.get().then((x) => {

+ 7 - 7
packages/app/src/pages/layout/sidebar-workspace.tsx

@@ -14,12 +14,12 @@ import { Spinner } from "@opencode-ai/ui/spinner"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { type Session } from "@opencode-ai/sdk/v2/client"
 import { type LocalProject } from "@/context/layout"
-import { loadSessionsQuery, useGlobalSync } from "@/context/global-sync"
+import { loadSessionsQueryKey, useGlobalSync } from "@/context/global-sync"
 import { useLanguage } from "@/context/language"
 import { pathKey } from "@/utils/path-key"
 import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
 import { sortedRootSessions } from "./helpers"
-import { useQuery } from "@tanstack/solid-query"
+import { useIsFetching } from "@tanstack/solid-query"
 
 type InlineEditorComponent = (props: {
   id: string
@@ -320,9 +320,9 @@ export const SortableWorkspace = (props: {
   const boot = createMemo(() => open() || active())
   const count = createMemo(() => sessions()?.length ?? 0)
   const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
-  const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
+  const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.directory) }))
   const busy = createMemo(() => props.ctx.isBusy(props.directory))
-  const loading = () => query.isLoading && count() === 0
+  const loading = () => fetching() > 0 && count() === 0
   const touch = createMediaQuery("(hover: none)")
   const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
   const loadMore = async () => {
@@ -427,7 +427,7 @@ export const SortableWorkspace = (props: {
             mobile={props.mobile}
             ctx={props.ctx}
             showNew={showNew}
-            loading={() => query.isLoading && count() === 0}
+            loading={loading}
             sessions={sessions}
             hasMore={hasMore}
             loadMore={loadMore}
@@ -454,9 +454,9 @@ export const LocalWorkspace = (props: {
   const slug = createMemo(() => base64Encode(props.project.worktree))
   const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
   const count = createMemo(() => sessions()?.length ?? 0)
-  const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
+  const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.project.worktree) }))
   const hasMore = createMemo(() => workspace().store.sessionTotal > count())
-  const loading = () => query.isLoading && count() === 0
+  const loading = () => fetching() > 0 && count() === 0
   const loadMore = async () => {
     workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
     await globalSync.project.loadSessions(props.project.worktree)