summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context/global-sync/bootstrap.ts
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-04-28 12:24:30 +0800
committerGitHub <[email protected]>2026-04-28 04:24:30 +0000
commitc8d9f7aa892ab46b9d6aa839d414451e8cd6b02a (patch)
treeb14b4355ea358fcc635df8d89162037ab470f505 /packages/app/src/context/global-sync/bootstrap.ts
parentcd7ec93cdfad5f35582c4b993eba5675da2ccd65 (diff)
downloadopencode-c8d9f7aa892ab46b9d6aa839d414451e8cd6b02a.tar.gz
opencode-c8d9f7aa892ab46b9d6aa839d414451e8cd6b02a.zip
refactor(app): load sync state through TanStack Query (#23792)
Diffstat (limited to 'packages/app/src/context/global-sync/bootstrap.ts')
-rw-r--r--packages/app/src/context/global-sync/bootstrap.ts194
1 files changed, 94 insertions, 100 deletions
diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts
index a83030fad..451531835 100644
--- a/packages/app/src/context/global-sync/bootstrap.ts
+++ b/packages/app/src/context/global-sync/bootstrap.ts
@@ -19,6 +19,7 @@ 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 { loadMcpQuery } from "../global-sync"
type GlobalStore = {
ready: boolean
@@ -66,6 +67,62 @@ function runAll(list: Array<() => Promise<unknown>>) {
return Promise.allSettled(list.map((item) => item()))
}
+function showErrors(input: {
+ errors: unknown[]
+ title: string
+ translate: (key: string, vars?: Record<string, string | number>) => string
+ formatMoreCount: (count: number) => string
+}) {
+ if (input.errors.length === 0) return
+ const message = formatServerError(input.errors[0], input.translate)
+ const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : ""
+ showToast({
+ variant: "error",
+ title: input.title,
+ description: message + more,
+ })
+}
+
+export const loadGlobalConfigQuery = (
+ sdk?: OpencodeClient,
+ transform?: (x: Awaited<ReturnType<OpencodeClient["global"]["config"]["get"]>>) => void,
+) =>
+ queryOptions({
+ queryKey: ["config"],
+ queryFn: sdk
+ ? () =>
+ retry(() =>
+ sdk.global.config.get().then((x) => {
+ transform?.(x)
+ return x.data!
+ }),
+ )
+ : skipToken,
+ })
+
+export const loadProjectsQuery = (
+ sdk?: OpencodeClient,
+ transform?: (x: Awaited<ReturnType<OpencodeClient["project"]["list"]>>["data"]) => void,
+) =>
+ 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,
+ })
+
export async function bootstrapGlobal(input: {
globalSDK: OpencodeClient
requestFailedTitle: string
@@ -74,53 +131,15 @@ export async function bootstrapGlobal(input: {
setGlobalStore: SetStoreFunction<GlobalStore>
queryClient: QueryClient
}) {
- const fast = [
- () =>
- retry(() =>
- input.globalSDK.global.config.get().then((x) => {
- input.setGlobalStore("config", reconcile(x.data!, { merge: false }))
- }),
- ),
- ]
-
const slow = [
+ () => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.globalSDK)),
+ () => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
+ () => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
() =>
- input.queryClient.fetchQuery({
- ...loadProvidersQuery(null),
- queryFn: () =>
- retry(() =>
- input.globalSDK.provider.list().then((x) => {
- input.setGlobalStore("provider", normalizeProviderList(x.data!))
- return null
- }),
- ),
- }),
- () =>
- retry(() =>
- input.globalSDK.path.get().then((x) => {
- input.setGlobalStore("path", x.data!)
- }),
- ),
- () =>
- retry(() =>
- input.globalSDK.project.list().then((x) => {
- const projects = (x.data ?? [])
- .filter((p) => !!p?.id)
- .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
- .slice()
- .sort((a, b) => cmp(a.id, b.id))
- input.setGlobalStore("project", projects)
- }),
+ input.queryClient.fetchQuery(
+ loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])),
),
]
- await runAll(fast)
- // showErrors({
- // errors: errors(await runAll(fast)),
- // title: input.requestFailedTitle,
- // translate: input.translate,
- // formatMoreCount: input.formatMoreCount,
- // })
- await waitForPaint()
await runAll(slow)
// showErrors({
// errors: errors(),
@@ -128,7 +147,6 @@ export async function bootstrapGlobal(input: {
// translate: input.translate,
// formatMoreCount: input.formatMoreCount,
// })
- input.setGlobalStore("ready", true)
}
function groupBySession<T extends { id: string; sessionID: string }>(input: T[]) {
@@ -179,26 +197,28 @@ function warmSessions(input: {
).then(() => undefined)
}
-export const loadProvidersQuery = (directory: string | null) =>
- queryOptions<null>({ queryKey: [directory, "providers"], queryFn: skipToken })
+export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) =>
+ queryOptions({
+ queryKey: [directory, "providers"],
+ queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken,
+ })
export const loadAgentsQuery = (
directory: string | null,
sdk?: OpencodeClient,
transform?: (x: Awaited<ReturnType<OpencodeClient["app"]["agents"]>>) => void,
) =>
- queryOptions<null>({
+ queryOptions({
queryKey: [directory, "agents"],
- queryFn:
- sdk && transform
- ? () =>
- retry(() =>
- sdk.app
- .agents()
- .then(transform)
- .then(() => null),
- )
- : skipToken,
+ queryFn: sdk
+ ? () =>
+ retry(() =>
+ sdk.app.agents().then((x) => {
+ transform?.(x)
+ return x.data!
+ }),
+ )
+ : skipToken,
})
export const loadPathQuery = (
@@ -208,16 +228,15 @@ export const loadPathQuery = (
) =>
queryOptions<Path>({
queryKey: [directory, "path"],
- queryFn:
- sdk && transform
- ? () =>
- retry(() =>
- sdk.path.get().then(async (x) => {
- transform(x)
- return x.data!
- }),
- )
- : skipToken,
+ queryFn: sdk
+ ? () =>
+ retry(() =>
+ sdk.path.get().then(async (x) => {
+ transform?.(x)
+ return x.data!
+ }),
+ )
+ : skipToken,
})
export async function bootstrapDirectory(input: {
@@ -247,13 +266,6 @@ export async function bootstrapDirectory(input: {
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
input.setStore("config", reconcile(input.global.config, { merge: false }))
}
- if (loading || input.store.provider.all.length === 0) {
- input.setStore("provider_ready", false)
- }
- input.setStore("mcp_ready", false)
- input.setStore("mcp", {})
- input.setStore("lsp_ready", false)
- input.setStore("lsp", [])
if (loading) input.setStore("status", "partial")
const rev = (providerRev.get(input.directory) ?? 0) + 1
@@ -340,33 +352,15 @@ export async function bootstrapDirectory(input: {
}),
),
() => Promise.resolve(input.loadSessions(input.directory)),
+ () => input.queryClient.fetchQuery(loadMcpQuery(input.directory, input.sdk)),
() =>
- retry(() =>
- input.sdk.mcp.status().then((x) => {
- input.setStore("mcp", x.data!)
- input.setStore("mcp_ready", true)
- }),
- ),
- () =>
- input.queryClient.ensureQueryData({
- ...loadProvidersQuery(input.directory),
- queryFn: () =>
- retry(() => input.sdk.provider.list())
- .then((x) => {
- if (providerRev.get(input.directory) !== rev) return
- input.setStore("provider", normalizeProviderList(x.data!))
- input.setStore("provider_ready", true)
- })
- .catch((err) => {
- if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err)
- const project = getFilename(input.directory)
- showToast({
- variant: "error",
- title: input.translate("toast.project.reloadFailed.title", { project }),
- description: formatServerError(err, input.translate),
- })
- })
- .then(() => null),
+ input.queryClient.fetchQuery(loadProvidersQuery(input.directory, input.sdk)).catch((err) => {
+ const project = getFilename(input.directory)
+ showToast({
+ variant: "error",
+ title: input.translate("toast.project.reloadFailed.title", { project }),
+ description: formatServerError(err, input.translate),
+ })
}),
].filter(Boolean) as (() => Promise<any>)[]