refactor(instance): remove remaining bind call sites (#27731)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Effect, Exit, Fiber } from "effect"
|
import { Context, Effect, Exit, Fiber } from "effect"
|
||||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||||
import { Instance } from "@/project/instance"
|
import { Instance } from "@/project/instance"
|
||||||
import type { InstanceContext } from "@/project/instance-context"
|
import type { InstanceContext } from "@/project/instance-context"
|
||||||
@@ -11,6 +11,7 @@ export interface Shape {
|
|||||||
readonly promise: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>
|
readonly promise: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>
|
||||||
readonly fork: <A, E, R>(effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>
|
readonly fork: <A, E, R>(effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>
|
||||||
readonly run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E>
|
readonly run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E>
|
||||||
|
readonly bind: <Args extends readonly unknown[], Result>(fn: (...args: Args) => Result) => (...args: Args) => Result
|
||||||
}
|
}
|
||||||
|
|
||||||
function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R {
|
function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R {
|
||||||
@@ -22,6 +23,28 @@ function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceI
|
|||||||
return fn()
|
return fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function captureSync() {
|
||||||
|
const fiber = Fiber.getCurrent()
|
||||||
|
const value = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined
|
||||||
|
const instance =
|
||||||
|
value ??
|
||||||
|
(() => {
|
||||||
|
try {
|
||||||
|
return Instance.current
|
||||||
|
} catch (err) {
|
||||||
|
if (!(err instanceof LocalContext.NotFound)) throw err
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
const workspace = (fiber ? Context.getReferenceUnsafe(fiber.context, WorkspaceRef) : undefined) ??
|
||||||
|
WorkspaceContext.workspaceID
|
||||||
|
return { instance, workspace }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bind = <Args extends readonly unknown[], Result>(fn: (...args: Args) => Result) => {
|
||||||
|
const captured = captureSync()
|
||||||
|
return (...args: Args) => restore(captured.instance, captured.workspace, () => fn(...args))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bridge from Effect into a Promise-returning JS callback while installing
|
* Bridge from Effect into a Promise-returning JS callback while installing
|
||||||
* legacy `Instance.context` and `WorkspaceContext` AsyncLocalStorage for
|
* legacy `Instance.context` and `WorkspaceContext` AsyncLocalStorage for
|
||||||
@@ -45,16 +68,9 @@ export function make(): Effect.Effect<Shape> {
|
|||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const ctx = yield* Effect.context()
|
const ctx = yield* Effect.context()
|
||||||
const value = yield* InstanceRef
|
const value = yield* InstanceRef
|
||||||
const instance =
|
const captured = captureSync()
|
||||||
value ??
|
const instance = value ?? captured.instance
|
||||||
(() => {
|
const workspace = (yield* WorkspaceRef) ?? captured.workspace
|
||||||
try {
|
|
||||||
return Instance.current
|
|
||||||
} catch (err) {
|
|
||||||
if (!(err instanceof LocalContext.NotFound)) throw err
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
const workspace = (yield* WorkspaceRef) ?? WorkspaceContext.workspaceID
|
|
||||||
const attach = <A, E, R>(effect: Effect.Effect<A, E, R>) => attachWith(effect, { instance, workspace })
|
const attach = <A, E, R>(effect: Effect.Effect<A, E, R>) => attachWith(effect, { instance, workspace })
|
||||||
const wrap = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
const wrap = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
||||||
attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect<A, E, never>
|
attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect<A, E, never>
|
||||||
@@ -72,6 +88,10 @@ export function make(): Effect.Effect<Shape> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
bind:
|
||||||
|
<Args extends readonly unknown[], Result>(fn: (...args: Args) => Result) =>
|
||||||
|
(...args: Args) =>
|
||||||
|
restore(instance, workspace, () => fn(...args)),
|
||||||
} satisfies Shape
|
} satisfies Shape
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { readdir, realpath } from "fs/promises"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
import { EffectBridge } from "@/effect/bridge"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { Git } from "@/git"
|
import { Git } from "@/git"
|
||||||
@@ -88,13 +89,13 @@ export const layer = Layer.effect(
|
|||||||
if (!w) return
|
if (!w) return
|
||||||
|
|
||||||
log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend })
|
log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend })
|
||||||
|
const bridge = yield* EffectBridge.make()
|
||||||
const subs: ParcelWatcher.AsyncSubscription[] = []
|
const subs: ParcelWatcher.AsyncSubscription[] = []
|
||||||
yield* Effect.addFinalizer(() =>
|
yield* Effect.addFinalizer(() =>
|
||||||
Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
|
Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
|
||||||
)
|
)
|
||||||
|
|
||||||
const cb: ParcelWatcher.SubscribeCallback = InstanceState.bind((err, evts) => {
|
const cb: ParcelWatcher.SubscribeCallback = bridge.bind((err, evts) => {
|
||||||
if (err) return
|
if (err) return
|
||||||
for (const evt of evts) {
|
for (const evt of evts) {
|
||||||
if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
|
if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ const live: Layer.Layer<
|
|||||||
|
|
||||||
const bridge = yield* EffectBridge.make()
|
const bridge = yield* EffectBridge.make()
|
||||||
const approvedToolsForSession = new Set<string>()
|
const approvedToolsForSession = new Set<string>()
|
||||||
workflowModel.approvalHandler = InstanceState.bind(async (approvalTools) => {
|
workflowModel.approvalHandler = bridge.bind(async (approvalTools) => {
|
||||||
const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[]
|
const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[]
|
||||||
// Auto-approve tools that were already approved in this session
|
// Auto-approve tools that were already approved in this session
|
||||||
// (prevents infinite approval loops for server-side MCP tools)
|
// (prevents infinite approval loops for server-side MCP tools)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import path from "path"
|
|||||||
import { readFileSync, readdirSync, existsSync } from "fs"
|
import { readFileSync, readdirSync, existsSync } from "fs"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { InstallationChannel } from "@opencode-ai/core/installation/version"
|
import { InstallationChannel } from "@opencode-ai/core/installation/version"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { EffectBridge } from "@/effect/bridge"
|
||||||
import { init } from "#db"
|
import { init } from "#db"
|
||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export function use<T>(callback: (trx: TxOrDb) => T): T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function effect(fn: () => any | Promise<any>) {
|
export function effect(fn: () => any | Promise<any>) {
|
||||||
const bound = InstanceState.bind(fn)
|
const bound = EffectBridge.bind(fn)
|
||||||
try {
|
try {
|
||||||
ctx.use().effects.push(bound)
|
ctx.use().effects.push(bound)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -188,7 +188,7 @@ export function transaction<T>(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof LocalContext.NotFound) {
|
if (err instanceof LocalContext.NotFound) {
|
||||||
const effects: (() => void | Promise<void>)[] = []
|
const effects: (() => void | Promise<void>)[] = []
|
||||||
const txCallback = InstanceState.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx)))
|
const txCallback = EffectBridge.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx)))
|
||||||
const result = Client().transaction(txCallback, { behavior: options?.behavior })
|
const result = Client().transaction(txCallback, { behavior: options?.behavior })
|
||||||
for (const effect of effects) effect()
|
for (const effect of effects) effect()
|
||||||
return result as NotPromise<T>
|
return result as NotPromise<T>
|
||||||
|
|||||||
Reference in New Issue
Block a user