refactor(app): extract refcount utility and clean up server sdk context (#29155)

This commit is contained in:
Brendan Allan
2026-05-25 10:00:00 +08:00
committed by GitHub
parent 03bb53c389
commit 9495ecd536
5 changed files with 264 additions and 278 deletions
+5 -3
View File
@@ -8,10 +8,11 @@ import {
getSessionPrefetchPromise,
setSessionPrefetch,
} from "./global-sync/session-prefetch"
import { useServerSync } from "./server-sync"
import { createServerSyncContext, useServerSync } from "./server-sync"
import type { Message, OpencodeClient, Part } from "@opencode-ai/sdk/v2/client"
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
import { diffs as list, message as clean } from "@/utils/diffs"
import { useServerSDK } from "./server-sdk"
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
@@ -164,8 +165,9 @@ function setOptimisticRemove(setStore: (...args: unknown[]) => void, input: Opti
})
}
export const createDirSyncContext = (client: OpencodeClient, directory: string) => {
const serverSync = useServerSync()
export const createDirSyncContext = (directory: string, serverSync: ReturnType<typeof createServerSyncContext>) => {
const serverSDK = useServerSDK()
const client = serverSDK.createClient({ directory, throwOnError: true })
type Child = ReturnType<(typeof serverSync)["child"]>
type Setter = Child[1]
+1 -1
View File
@@ -6,6 +6,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
init: (props: { directory: string }) => {
const serverSDK = useServerSDK()
return serverSDK.createDirSyncContext(props.directory)
return serverSDK.createDirSdkContext(props.directory)
},
})
+23 -42
View File
@@ -6,23 +6,20 @@ import { batch, onCleanup, onMount } from "solid-js"
import { createSdkForServer } from "@/utils/server"
import { useLanguage } from "./language"
import { usePlatform } from "./platform"
import { useServer } from "./server"
import { ServerConnection, useServer } from "./server"
import { createRefCountMap } from "@/utils/refcount"
const isAbortError = (error: unknown) =>
error !== null && typeof error === "object" && "name" in error && error.name === "AbortError"
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
name: "GlobalSDK",
init: () => {
const language = useLanguage()
const server = useServer()
function createServerSdkContext(server: ServerConnection.Any) {
const platform = usePlatform()
const abort = new AbortController()
const eventFetch = (() => {
if (!platform.fetch || !server.current) return
if (!platform.fetch || !server) return
try {
const url = new URL(server.current.http.url)
const url = new URL(server.http.url)
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1"
if (url.protocol === "http:" && !loopback) return platform.fetch
} catch {
@@ -30,13 +27,10 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
}
})()
const currentServer = server.current
if (!currentServer) throw new Error(language.t("error.serverSDK.noServerAvailable"))
const eventSdk = createSdkForServer({
signal: abort.signal,
fetch: eventFetch,
server: currentServer.http,
server: server.http,
})
const emitter = createGlobalEmitter<{
[key: string]: Event
@@ -142,7 +136,7 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
if (streamErrorLogged) return
streamErrorLogged = true
console.error("[global-sdk] event stream error", {
url: currentServer.http.url,
url: server.http.url,
fetch: eventFetch ? "platform" : "webview",
error,
})
@@ -184,7 +178,7 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
if (!aborted(error) && !streamErrorLogged) {
streamErrorLogged = true
console.error("[global-sdk] event stream failed", {
url: currentServer.http.url,
url: server.http.url,
fetch: eventFetch ? "platform" : "webview",
error,
})
@@ -227,16 +221,13 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
})
const sdk = createSdkForServer({
server: server.current.http,
server: server.http,
fetch: platform.fetch,
throwOnError: true,
})
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSdkContext>>()
const dirSdkContextRefCounts = new Map<string, number>()
return {
url: currentServer.http.url,
url: server.http.url,
client: sdk,
event: {
on: emitter.on.bind(emitter),
@@ -244,34 +235,26 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
start,
},
createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
const s = server.current
if (!s) throw new Error(language.t("error.serverSDK.serverNotAvailable"))
return createSdkForServer({
server: s.http,
server: server.http,
fetch: platform.fetch,
...opts,
})
},
createDirSyncContext: (directory: string) => {
onCleanup(() => {
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) - 1)
if (dirSdkContextRefCounts.get(directory) === 0) {
dirSyncContexts.delete(directory)
dirSdkContextRefCounts.delete(directory)
}
})
const cached = dirSyncContexts.get(directory)
if (cached) {
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) + 1)
return cached
}
const ctx = createDirSdkContext(directory)
dirSyncContexts.set(directory, ctx)
dirSdkContextRefCounts.set(directory, 1)
return ctx
},
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
name: "ServerSDK",
init: () => {
const language = useLanguage()
const server = useServer()
if (!server.current) throw new Error(language.t("error.serverSDK.noServerAvailable"))
const sdk = createServerSdkContext(server.current)
return {
...sdk,
createDirSdkContext: createRefCountMap((dir) => createDirSdkContext(dir, sdk)),
}
},
})
@@ -280,9 +263,7 @@ type SDKEventMap = {
[key in Event["type"]]: Extract<Event, { type: key }>
}
function createDirSdkContext(directory: string) {
const serverSDK = useServerSDK()
function createDirSdkContext(directory: string, serverSDK: ReturnType<typeof createServerSdkContext>) {
const client = serverSDK.createClient({
directory,
throwOnError: true,
+13 -36
View File
@@ -29,7 +29,8 @@ import { createRefreshQueue } from "./global-sync/queue"
import { directoryKey } from "./global-sync/utils"
import { PathKey } from "@/utils/path-key"
import { createDirSyncContext } from "./directory-sync"
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
import { createSimpleContext, NormalizedProviderListResponse } from "@opencode-ai/ui/context"
import { createRefCountMap } from "@/utils/refcount"
type GlobalStore = {
ready: boolean
@@ -72,7 +73,7 @@ function makeQueryOptionsApi(serverSDK: () => OpencodeClient, sdkFor: (dir: Path
}
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
function createServerSyncContext() {
export function createServerSyncContext() {
const serverSDK = useServerSDK()
const language = useLanguage()
const owner = getOwner()
@@ -425,9 +426,6 @@ function createServerSyncContext() {
},
}))
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSyncContext>>()
const dirSyncContextRefCounts = new Map<string, number>()
return {
data: globalStore,
set,
@@ -446,41 +444,20 @@ function createServerSyncContext() {
todo: {
set: setSessionTodo,
},
createDirSyncContext: (directory: string) => {
onCleanup(() => {
dirSyncContextRefCounts.set(directory, (dirSyncContextRefCounts.get(directory) ?? 0) - 1)
if (dirSyncContextRefCounts.get(directory) === 0) {
dirSyncContexts.delete(directory)
dirSyncContextRefCounts.delete(directory)
}
})
const cached = dirSyncContexts.get(directory)
if (cached) {
dirSyncContextRefCounts.set(directory, (dirSyncContextRefCounts.get(directory) ?? 0) + 1)
return cached
}
const ctx = createDirSyncContext(serverSDK.createClient({ directory, throwOnError: true }), directory)
dirSyncContexts.set(directory, ctx)
dirSyncContextRefCounts.set(directory, 1)
return ctx
export const { use: useServerSync, provider: ServerSyncProvider } = createSimpleContext({
name: "ServerSync",
init: () => {
const sync = createServerSyncContext()
return {
...sync,
createDirSyncContext: createRefCountMap((dir) => createDirSyncContext(dir, sync)),
}
},
}
}
const ServerSyncContext = createContext<ReturnType<typeof createServerSyncContext>>()
export function ServerSyncProvider(props: ParentProps) {
const value = createServerSyncContext()
return <ServerSyncContext.Provider value={value}>{props.children}</ServerSyncContext.Provider>
}
export function useServerSync() {
const context = useContext(ServerSyncContext)
if (!context) throw new Error("useServerSync must be used within ServerSyncProvider")
return context
}
})
export function useQueryOptions() {
return useServerSync().queryOptions
+26
View File
@@ -0,0 +1,26 @@
import { onCleanup } from "solid-js"
export function createRefCountMap<T>(create: (key: string) => T) {
const items = new Map<string, T>()
const refCounts = new Map<string, number>()
return (key: string) => {
onCleanup(() => {
refCounts.set(key, (refCounts.get(key) ?? 0) - 1)
if (refCounts.get(key) === 0) {
items.delete(key)
refCounts.delete(key)
}
})
const cached = items.get(key)
if (cached) {
refCounts.set(key, (refCounts.get(key) ?? 0) + 1)
return cached
}
const item = create(key)
items.set(key, item)
refCounts.set(key, 1)
return item
}
}