refactor(app): extract refcount utility and clean up server sdk context (#29155)
This commit is contained in:
@@ -8,10 +8,11 @@ import {
|
|||||||
getSessionPrefetchPromise,
|
getSessionPrefetchPromise,
|
||||||
setSessionPrefetch,
|
setSessionPrefetch,
|
||||||
} from "./global-sync/session-prefetch"
|
} 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 type { Message, OpencodeClient, Part } from "@opencode-ai/sdk/v2/client"
|
||||||
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
|
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
|
||||||
import { diffs as list, message as clean } from "@/utils/diffs"
|
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"])
|
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) => {
|
export const createDirSyncContext = (directory: string, serverSync: ReturnType<typeof createServerSyncContext>) => {
|
||||||
const serverSync = useServerSync()
|
const serverSDK = useServerSDK()
|
||||||
|
const client = serverSDK.createClient({ directory, throwOnError: true })
|
||||||
|
|
||||||
type Child = ReturnType<(typeof serverSync)["child"]>
|
type Child = ReturnType<(typeof serverSync)["child"]>
|
||||||
type Setter = Child[1]
|
type Setter = Child[1]
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||||||
init: (props: { directory: string }) => {
|
init: (props: { directory: string }) => {
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
|
|
||||||
return serverSDK.createDirSyncContext(props.directory)
|
return serverSDK.createDirSdkContext(props.directory)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,23 +6,20 @@ import { batch, onCleanup, onMount } from "solid-js"
|
|||||||
import { createSdkForServer } from "@/utils/server"
|
import { createSdkForServer } from "@/utils/server"
|
||||||
import { useLanguage } from "./language"
|
import { useLanguage } from "./language"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
import { useServer } from "./server"
|
import { ServerConnection, useServer } from "./server"
|
||||||
|
import { createRefCountMap } from "@/utils/refcount"
|
||||||
|
|
||||||
const isAbortError = (error: unknown) =>
|
const isAbortError = (error: unknown) =>
|
||||||
error !== null && typeof error === "object" && "name" in error && error.name === "AbortError"
|
error !== null && typeof error === "object" && "name" in error && error.name === "AbortError"
|
||||||
|
|
||||||
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
|
function createServerSdkContext(server: ServerConnection.Any) {
|
||||||
name: "GlobalSDK",
|
|
||||||
init: () => {
|
|
||||||
const language = useLanguage()
|
|
||||||
const server = useServer()
|
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
|
|
||||||
const eventFetch = (() => {
|
const eventFetch = (() => {
|
||||||
if (!platform.fetch || !server.current) return
|
if (!platform.fetch || !server) return
|
||||||
try {
|
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"
|
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1"
|
||||||
if (url.protocol === "http:" && !loopback) return platform.fetch
|
if (url.protocol === "http:" && !loopback) return platform.fetch
|
||||||
} catch {
|
} 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({
|
const eventSdk = createSdkForServer({
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
fetch: eventFetch,
|
fetch: eventFetch,
|
||||||
server: currentServer.http,
|
server: server.http,
|
||||||
})
|
})
|
||||||
const emitter = createGlobalEmitter<{
|
const emitter = createGlobalEmitter<{
|
||||||
[key: string]: Event
|
[key: string]: Event
|
||||||
@@ -142,7 +136,7 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
|
|||||||
if (streamErrorLogged) return
|
if (streamErrorLogged) return
|
||||||
streamErrorLogged = true
|
streamErrorLogged = true
|
||||||
console.error("[global-sdk] event stream error", {
|
console.error("[global-sdk] event stream error", {
|
||||||
url: currentServer.http.url,
|
url: server.http.url,
|
||||||
fetch: eventFetch ? "platform" : "webview",
|
fetch: eventFetch ? "platform" : "webview",
|
||||||
error,
|
error,
|
||||||
})
|
})
|
||||||
@@ -184,7 +178,7 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
|
|||||||
if (!aborted(error) && !streamErrorLogged) {
|
if (!aborted(error) && !streamErrorLogged) {
|
||||||
streamErrorLogged = true
|
streamErrorLogged = true
|
||||||
console.error("[global-sdk] event stream failed", {
|
console.error("[global-sdk] event stream failed", {
|
||||||
url: currentServer.http.url,
|
url: server.http.url,
|
||||||
fetch: eventFetch ? "platform" : "webview",
|
fetch: eventFetch ? "platform" : "webview",
|
||||||
error,
|
error,
|
||||||
})
|
})
|
||||||
@@ -227,16 +221,13 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
|
|||||||
})
|
})
|
||||||
|
|
||||||
const sdk = createSdkForServer({
|
const sdk = createSdkForServer({
|
||||||
server: server.current.http,
|
server: server.http,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSdkContext>>()
|
|
||||||
const dirSdkContextRefCounts = new Map<string, number>()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: currentServer.http.url,
|
url: server.http.url,
|
||||||
client: sdk,
|
client: sdk,
|
||||||
event: {
|
event: {
|
||||||
on: emitter.on.bind(emitter),
|
on: emitter.on.bind(emitter),
|
||||||
@@ -244,34 +235,26 @@ export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleCo
|
|||||||
start,
|
start,
|
||||||
},
|
},
|
||||||
createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
|
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({
|
return createSdkForServer({
|
||||||
server: s.http,
|
server: server.http,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
...opts,
|
...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)
|
export const { use: useServerSDK, provider: ServerSDKProvider } = createSimpleContext({
|
||||||
if (cached) {
|
name: "ServerSDK",
|
||||||
dirSdkContextRefCounts.set(directory, (dirSdkContextRefCounts.get(directory) ?? 0) + 1)
|
init: () => {
|
||||||
return cached
|
const language = useLanguage()
|
||||||
}
|
const server = useServer()
|
||||||
const ctx = createDirSdkContext(directory)
|
|
||||||
dirSyncContexts.set(directory, ctx)
|
|
||||||
dirSdkContextRefCounts.set(directory, 1)
|
|
||||||
|
|
||||||
return ctx
|
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 }>
|
[key in Event["type"]]: Extract<Event, { type: key }>
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDirSdkContext(directory: string) {
|
function createDirSdkContext(directory: string, serverSDK: ReturnType<typeof createServerSdkContext>) {
|
||||||
const serverSDK = useServerSDK()
|
|
||||||
|
|
||||||
const client = serverSDK.createClient({
|
const client = serverSDK.createClient({
|
||||||
directory,
|
directory,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import { createRefreshQueue } from "./global-sync/queue"
|
|||||||
import { directoryKey } from "./global-sync/utils"
|
import { directoryKey } from "./global-sync/utils"
|
||||||
import { PathKey } from "@/utils/path-key"
|
import { PathKey } from "@/utils/path-key"
|
||||||
import { createDirSyncContext } from "./directory-sync"
|
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 = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -72,7 +73,7 @@ function makeQueryOptionsApi(serverSDK: () => OpencodeClient, sdkFor: (dir: Path
|
|||||||
}
|
}
|
||||||
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
|
export type QueryOptionsApi = ReturnType<typeof makeQueryOptionsApi>
|
||||||
|
|
||||||
function createServerSyncContext() {
|
export function createServerSyncContext() {
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const owner = getOwner()
|
const owner = getOwner()
|
||||||
@@ -425,9 +426,6 @@ function createServerSyncContext() {
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const dirSyncContexts = new Map<string, ReturnType<typeof createDirSyncContext>>()
|
|
||||||
const dirSyncContextRefCounts = new Map<string, number>()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: globalStore,
|
data: globalStore,
|
||||||
set,
|
set,
|
||||||
@@ -446,41 +444,20 @@ function createServerSyncContext() {
|
|||||||
todo: {
|
todo: {
|
||||||
set: setSessionTodo,
|
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)
|
export const { use: useServerSync, provider: ServerSyncProvider } = createSimpleContext({
|
||||||
if (cached) {
|
name: "ServerSync",
|
||||||
dirSyncContextRefCounts.set(directory, (dirSyncContextRefCounts.get(directory) ?? 0) + 1)
|
init: () => {
|
||||||
return cached
|
const sync = createServerSyncContext()
|
||||||
|
|
||||||
|
return {
|
||||||
|
...sync,
|
||||||
|
createDirSyncContext: createRefCountMap((dir) => createDirSyncContext(dir, sync)),
|
||||||
}
|
}
|
||||||
const ctx = createDirSyncContext(serverSDK.createClient({ directory, throwOnError: true }), directory)
|
|
||||||
dirSyncContexts.set(directory, ctx)
|
|
||||||
dirSyncContextRefCounts.set(directory, 1)
|
|
||||||
|
|
||||||
return ctx
|
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
export function useQueryOptions() {
|
||||||
return useServerSync().queryOptions
|
return useServerSync().queryOptions
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user