Normalize instance lifecycle wiring (#25501)
This commit is contained in:
11
packages/opencode/src/project/instance-layer.ts
Normal file
11
packages/opencode/src/project/instance-layer.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Effect, Layer } from "effect"
|
||||
import { InstanceStore } from "./instance-store"
|
||||
|
||||
export const layer = Layer.unwrap(
|
||||
Effect.promise(async () => {
|
||||
const { InstanceBootstrap } = await import("./bootstrap")
|
||||
return InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))
|
||||
}),
|
||||
)
|
||||
|
||||
export * as InstanceLayer from "./instance-layer"
|
||||
@@ -1,27 +1,16 @@
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { type InstanceContext } from "./instance-context"
|
||||
import { InstanceStore, type LoadInput } from "./instance-store"
|
||||
import { Effect, Layer } from "effect"
|
||||
|
||||
// Production InstanceStore wiring plus a bridge for Promise/ALS callers that
|
||||
// cannot yet yield InstanceStore.Service. This keeps InstanceStore itself
|
||||
// low-level while still giving legacy Hono and CLI paths the production
|
||||
// bootstrap implementation. Delete the Promise helpers once those callers are
|
||||
// migrated to Effect boundaries that provide InstanceStore directly.
|
||||
// Keep the bootstrap implementation import lazy: Instance is imported broadly,
|
||||
// and importing the app bootstrap graph at module load can trigger ESM cycles.
|
||||
export const layer = Layer.unwrap(
|
||||
Effect.promise(async () => {
|
||||
const { InstanceBootstrap } = await import("./bootstrap")
|
||||
return InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))
|
||||
}),
|
||||
)
|
||||
// Bridge for Promise/ALS callers that cannot yet yield InstanceStore.Service.
|
||||
// Delete this module once those callers are migrated to Effect boundaries that
|
||||
// provide InstanceStore directly.
|
||||
|
||||
const runtime = makeRuntime(InstanceStore.Service, layer)
|
||||
|
||||
export const load = (input: LoadInput) => runtime.runPromise((store) => store.load(input))
|
||||
export const disposeInstance = (ctx: InstanceContext) => runtime.runPromise((store) => store.dispose(ctx))
|
||||
export const disposeAllInstances = () => runtime.runPromise((store) => store.disposeAll())
|
||||
export const reloadInstance = (input: LoadInput) => runtime.runPromise((store) => store.reload(input))
|
||||
export const load = (input: LoadInput) => AppRuntime.runPromise(InstanceStore.Service.use((store) => store.load(input)))
|
||||
export const disposeInstance = (ctx: InstanceContext) =>
|
||||
AppRuntime.runPromise(InstanceStore.Service.use((store) => store.dispose(ctx)))
|
||||
export const disposeAllInstances = () => AppRuntime.runPromise(InstanceStore.Service.use((store) => store.disposeAll()))
|
||||
export const reloadInstance = (input: LoadInput) =>
|
||||
AppRuntime.runPromise(InstanceStore.Service.use((store) => store.reload(input)))
|
||||
|
||||
export * as InstanceRuntime from "./instance-runtime"
|
||||
|
||||
@@ -8,26 +8,18 @@ import { type InstanceContext } from "./instance-context"
|
||||
import { InstanceBootstrap } from "./bootstrap-service"
|
||||
import * as Project from "./project"
|
||||
|
||||
export interface LoadInput<R = never> {
|
||||
export interface LoadInput {
|
||||
directory: string
|
||||
/**
|
||||
* Additional setup to run after the default InstanceBootstrap.
|
||||
* Mainly used by tests for env-var setup or file writes that need the instance ALS context.
|
||||
*/
|
||||
init?: Effect.Effect<void, never, R>
|
||||
worktree?: string
|
||||
project?: Project.Info
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly load: <R = never>(input: LoadInput<R>) => Effect.Effect<InstanceContext, never, R>
|
||||
readonly reload: <R = never>(input: LoadInput<R>) => Effect.Effect<InstanceContext, never, R>
|
||||
readonly load: (input: LoadInput) => Effect.Effect<InstanceContext>
|
||||
readonly reload: (input: LoadInput) => Effect.Effect<InstanceContext>
|
||||
readonly dispose: (ctx: InstanceContext) => Effect.Effect<void>
|
||||
readonly disposeAll: () => Effect.Effect<void>
|
||||
readonly provide: <A, E, R, R2 = never>(
|
||||
input: LoadInput<R2>,
|
||||
effect: Effect.Effect<A, E, R>,
|
||||
) => Effect.Effect<A, E, R | R2>
|
||||
readonly provide: <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/InstanceStore") {}
|
||||
@@ -44,7 +36,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
const scope = yield* Scope.Scope
|
||||
const cache = new Map<string, Entry>()
|
||||
|
||||
const boot = <R>(input: LoadInput<R> & { directory: string }) =>
|
||||
const boot = (input: LoadInput & { directory: string }) =>
|
||||
Effect.gen(function* () {
|
||||
const ctx: InstanceContext =
|
||||
input.project && input.worktree
|
||||
@@ -61,7 +53,6 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
})),
|
||||
)
|
||||
yield* bootstrap.run.pipe(Effect.provideService(InstanceRef, ctx))
|
||||
if (input.init) yield* input.init.pipe(Effect.provideService(InstanceRef, ctx))
|
||||
return ctx
|
||||
}).pipe(Effect.withSpan("InstanceStore.boot"))
|
||||
|
||||
@@ -72,7 +63,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
return true
|
||||
})
|
||||
|
||||
const completeLoad = <R>(directory: string, input: LoadInput<R>, entry: Entry) =>
|
||||
const completeLoad = (directory: string, input: LoadInput, entry: Entry) =>
|
||||
Effect.gen(function* () {
|
||||
const exit = yield* Effect.exit(boot({ ...input, directory }))
|
||||
if (Exit.isFailure(exit)) yield* removeEntry(directory, entry)
|
||||
@@ -108,7 +99,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
return true
|
||||
})
|
||||
|
||||
const load = <R>(input: LoadInput<R>): Effect.Effect<InstanceContext, never, R> => {
|
||||
const load = (input: LoadInput): Effect.Effect<InstanceContext> => {
|
||||
const directory = AppFileSystem.resolve(input.directory)
|
||||
return Effect.uninterruptibleMask((restore) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -126,7 +117,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
).pipe(Effect.withSpan("InstanceStore.load"))
|
||||
}
|
||||
|
||||
const reload = <R>(input: LoadInput<R>): Effect.Effect<InstanceContext, never, R> => {
|
||||
const reload = (input: LoadInput): Effect.Effect<InstanceContext> => {
|
||||
const directory = AppFileSystem.resolve(input.directory)
|
||||
return Effect.uninterruptibleMask((restore) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -180,7 +171,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
return yield* cachedDisposeAll
|
||||
})
|
||||
|
||||
const provide = <A, E, R, R2>(input: LoadInput<R2>, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R | R2> =>
|
||||
const provide = <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
||||
load(input).pipe(Effect.flatMap((ctx) => effect.pipe(Effect.provideService(InstanceRef, ctx))))
|
||||
|
||||
yield* Effect.addFinalizer(() => disposeAll().pipe(Effect.ignore))
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { Effect } from "effect"
|
||||
import { context, type InstanceContext } from "./instance-context"
|
||||
import { InstanceRuntime } from "./instance-runtime"
|
||||
|
||||
export type { InstanceContext } from "./instance-context"
|
||||
export type { LoadInput } from "./instance-store"
|
||||
|
||||
export const Instance = {
|
||||
async provide<R>(input: { directory: string; init?: Effect.Effect<void>; fn: () => R }): Promise<R> {
|
||||
const ctx = await InstanceRuntime.load({ directory: input.directory, init: input.init })
|
||||
return context.provide(ctx, async () => input.fn())
|
||||
},
|
||||
get current() {
|
||||
return context.use()
|
||||
},
|
||||
|
||||
10
packages/opencode/src/project/with-instance.ts
Normal file
10
packages/opencode/src/project/with-instance.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { context } from "./instance-context"
|
||||
import { InstanceStore } from "./instance-store"
|
||||
|
||||
export async function provide<R>(input: { directory: string; fn: () => R }): Promise<R> {
|
||||
const ctx = await AppRuntime.runPromise(InstanceStore.Service.use((store) => store.load({ directory: input.directory })))
|
||||
return context.provide(ctx, () => input.fn())
|
||||
}
|
||||
|
||||
export * as WithInstance from "./with-instance"
|
||||
Reference in New Issue
Block a user