mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-27 08:58:10 +00:00
fix: break installation cycle in database context binding
This commit is contained in:
parent
825f51c39f
commit
e24369eaf1
10 changed files with 62 additions and 49 deletions
14
packages/opencode/src/effect/instance-bind.ts
Normal file
14
packages/opencode/src/effect/instance-bind.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { Fiber } from "effect"
|
||||
import * as ServiceMap from "effect/ServiceMap"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { InstanceRef } from "./instance-ref"
|
||||
|
||||
export function bind<F extends (...args: any[]) => any>(fn: F): F {
|
||||
try {
|
||||
return Instance.bind(fn)
|
||||
} catch {}
|
||||
const fiber = Fiber.getCurrent()
|
||||
const ctx = fiber ? ServiceMap.getReferenceUnsafe(fiber.services, InstanceRef) : undefined
|
||||
if (!ctx) return fn
|
||||
return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F
|
||||
}
|
||||
6
packages/opencode/src/effect/instance-ref.ts
Normal file
6
packages/opencode/src/effect/instance-ref.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { ServiceMap } from "effect"
|
||||
import type { InstanceContext } from "@/project/instance"
|
||||
|
||||
export const InstanceRef = ServiceMap.Reference<InstanceContext | undefined>("~opencode/InstanceRef", {
|
||||
defaultValue: () => undefined,
|
||||
})
|
||||
|
|
@ -1,28 +1,18 @@
|
|||
import { Effect, Fiber, ScopedCache, Scope, ServiceMap } from "effect"
|
||||
import { Effect, ScopedCache, Scope } from "effect"
|
||||
import { Instance, type InstanceContext } from "@/project/instance"
|
||||
import { bind as bindInstance } from "./instance-bind"
|
||||
import { InstanceRef } from "./instance-ref"
|
||||
import { registerDisposer } from "./instance-registry"
|
||||
|
||||
const TypeId = "~opencode/InstanceState"
|
||||
|
||||
export const InstanceRef = ServiceMap.Reference<InstanceContext | undefined>("~opencode/InstanceRef", {
|
||||
defaultValue: () => undefined,
|
||||
})
|
||||
|
||||
export interface InstanceState<A, E = never, R = never> {
|
||||
readonly [TypeId]: typeof TypeId
|
||||
readonly cache: ScopedCache.ScopedCache<string, A, E, R>
|
||||
}
|
||||
|
||||
export namespace InstanceState {
|
||||
export const bind = <F extends (...args: any[]) => any>(fn: F): F => {
|
||||
try {
|
||||
return Instance.bind(fn)
|
||||
} catch {}
|
||||
const fiber = Fiber.getCurrent()
|
||||
const ctx = fiber ? ServiceMap.getReferenceUnsafe(fiber.services, InstanceRef) : undefined
|
||||
if (!ctx) return fn
|
||||
return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F
|
||||
}
|
||||
export const bind = bindInstance
|
||||
|
||||
export const context = Effect.gen(function* () {
|
||||
const ref = yield* InstanceRef
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Effect, Layer, ManagedRuntime } from "effect"
|
||||
import * as ServiceMap from "effect/ServiceMap"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { InstanceRef } from "./instance-state"
|
||||
import { InstanceRef } from "./instance-ref"
|
||||
|
||||
export const memoMap = Layer.makeMemoMapUnsafe()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ import z from "zod"
|
|||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Log } from "../util/log"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_VERSION: string
|
||||
const OPENCODE_CHANNEL: string
|
||||
}
|
||||
import { CHANNEL as channel, VERSION as version } from "./meta"
|
||||
|
||||
import semver from "semver"
|
||||
|
||||
|
|
@ -60,8 +56,8 @@ export namespace Installation {
|
|||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "local"
|
||||
export const CHANNEL = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local"
|
||||
export const VERSION = version
|
||||
export const CHANNEL = channel
|
||||
export const USER_AGENT = `opencode/${CHANNEL}/${VERSION}/${Flag.OPENCODE_CLIENT}`
|
||||
|
||||
export function isPreview() {
|
||||
|
|
|
|||
7
packages/opencode/src/installation/meta.ts
Normal file
7
packages/opencode/src/installation/meta.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
declare global {
|
||||
const OPENCODE_VERSION: string
|
||||
const OPENCODE_CHANNEL: string
|
||||
}
|
||||
|
||||
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "local"
|
||||
export const CHANNEL = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local"
|
||||
|
|
@ -10,9 +10,9 @@ import { NamedError } from "@opencode-ai/util/error"
|
|||
import z from "zod"
|
||||
import path from "path"
|
||||
import { readFileSync, readdirSync, existsSync } from "fs"
|
||||
import { Installation } from "../installation"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { CHANNEL } from "../installation/meta"
|
||||
import { bind } from "@/effect/instance-bind"
|
||||
import { iife } from "@/util/iife"
|
||||
import { init } from "#db"
|
||||
|
||||
|
|
@ -29,10 +29,9 @@ const log = Log.create({ service: "db" })
|
|||
|
||||
export namespace Database {
|
||||
export function getChannelPath() {
|
||||
const channel = Installation.CHANNEL
|
||||
if (["latest", "beta"].includes(channel) || Flag.OPENCODE_DISABLE_CHANNEL_DB)
|
||||
if (["latest", "beta"].includes(CHANNEL) || Flag.OPENCODE_DISABLE_CHANNEL_DB)
|
||||
return path.join(Global.Path.data, "opencode.db")
|
||||
const safe = channel.replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||
const safe = CHANNEL.replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||
return path.join(Global.Path.data, `opencode-${safe}.db`)
|
||||
}
|
||||
|
||||
|
|
@ -143,10 +142,11 @@ export namespace Database {
|
|||
}
|
||||
|
||||
export function effect(fn: () => any | Promise<any>) {
|
||||
const bound = bind(fn)
|
||||
try {
|
||||
ctx.use().effects.push(InstanceState.bind(fn))
|
||||
ctx.use().effects.push(bound)
|
||||
} catch {
|
||||
fn()
|
||||
bound()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ export namespace Database {
|
|||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const effects: (() => void | Promise<void>)[] = []
|
||||
const txCallback = InstanceState.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx)))
|
||||
const txCallback = bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx)))
|
||||
const result = Client().transaction(txCallback, { behavior: options?.behavior })
|
||||
for (const effect of effects) effect()
|
||||
return result as NotPromise<T>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { afterEach, expect, test } from "bun:test"
|
||||
import { Cause, Deferred, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, ServiceMap } from "effect"
|
||||
import { InstanceRef, InstanceState } from "../../src/effect/instance-state"
|
||||
import { InstanceState } from "../../src/effect/instance-state"
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type * as PlatformError from "effect/PlatformError"
|
|||
import type * as Scope from "effect/Scope"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import type { Config } from "../../src/config/config"
|
||||
import { InstanceRef } from "../../src/effect/instance-state"
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { TestLLMServer } from "../lib/llm-server"
|
||||
|
||||
|
|
@ -115,8 +115,7 @@ export const provideInstance =
|
|||
Effect.promise<A>(async () =>
|
||||
Instance.provide({
|
||||
directory,
|
||||
fn: () =>
|
||||
Effect.runPromiseWith(services)(self.pipe(Effect.provideService(InstanceRef, Instance.current))),
|
||||
fn: () => Effect.runPromiseWith(services)(self.pipe(Effect.provideService(InstanceRef, Instance.current))),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,20 +4,6 @@ export type ClientOptions = {
|
|||
baseUrl: `${string}://${string}` | (string & {})
|
||||
}
|
||||
|
||||
export type EventInstallationUpdated = {
|
||||
type: "installation.updated"
|
||||
properties: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventInstallationUpdateAvailable = {
|
||||
type: "installation.update-available"
|
||||
properties: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
id: string
|
||||
worktree: string
|
||||
|
|
@ -47,6 +33,20 @@ export type EventProjectUpdated = {
|
|||
properties: Project
|
||||
}
|
||||
|
||||
export type EventInstallationUpdated = {
|
||||
type: "installation.updated"
|
||||
properties: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventInstallationUpdateAvailable = {
|
||||
type: "installation.update-available"
|
||||
properties: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerInstanceDisposed = {
|
||||
type: "server.instance.disposed"
|
||||
properties: {
|
||||
|
|
@ -964,9 +964,9 @@ export type EventSessionDeleted = {
|
|||
}
|
||||
|
||||
export type Event =
|
||||
| EventProjectUpdated
|
||||
| EventInstallationUpdated
|
||||
| EventInstallationUpdateAvailable
|
||||
| EventProjectUpdated
|
||||
| EventServerInstanceDisposed
|
||||
| EventServerConnected
|
||||
| EventGlobalDisposed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue