Convert LoadInput.init to Effect + extract InstanceBootstrap as a Service#25376
Merged
Conversation
…p as a Service
LoadInput.init switches from () => Promise<unknown> to Effect<void, never, R>
so callers can pass Effect-native init bodies without Promise/Effect ceremony.
The R is generic and propagates through load/reload/provide.
InstanceBootstrap is restructured from a top-level Effect into a Service with
.run on its interface. Tests can now swap a fake InstanceBootstrap.Service
layer; production wiring composes the default via InstanceBootstrap.defaultLayer.
Production callers (httpapi middleware, project handler) drop the
'() => AppRuntime.runPromise(InstanceBootstrap)' thunk and pass bootstrap.run
directly via 'yield* InstanceBootstrap.Service'.
Cycle break: containsPath moves from Instance namespace to instance-context
(alongside the InstanceContext type), which also lets a few callers drop their
Instance value imports.
Legacy Instance.provide({init: () => Promise}) keeps working via a small
Promise→Effect bridge (liftLegacyInput). Roughly 70 test sites still use the
Promise-style init for setup; a follow-up will migrate them and drop the bridge.
Tests: 30/30 pass across instance.test.ts, httpapi-instance-context.test.ts,
path-traversal.test.ts. Typecheck clean.
fc4b662 to
60254ed
Compare
Promise-style inits passed via Instance.provide({init: () => Promise})
often read Instance.directory etc. from the ALS context. The new
Effect-typed init path doesn't bind ALS — it provides InstanceRef.
liftLegacyInput now wraps the promise call with context.provide using
the ctx from InstanceRef, restoring the binding for legacy callers
without forcing every test to convert.
oleksii-honchar
pushed a commit
to oleksii-honchar/better-opencode
that referenced
this pull request
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cycle break
`containsPath` moves from the `Instance` namespace into `instance-context.ts` (alongside `InstanceContext`). The signature now requires an explicit `InstanceContext` (was implicitly via ALS). Five callers updated. This unlocks dropping `Instance` value imports in a few places (e.g. `file/index.ts`, `tool/external-directory.ts`, `tool/bash.ts`).
Why
`init: () => AppRuntime.runPromise(InstanceBootstrap)` was pure ceremony at every production call site — wrap an Effect in a thunk so the store can `await` it, then `Effect.promise()` it back into an Effect. Pointless round-trip in code that's otherwise moving to Effect-native.
Compatibility shim
Legacy `Instance.provide({init: () => Promise})` keeps working via a small Promise→Effect bridge in `instance.ts` (`liftLegacyInput`). About 70 test sites still pass Promise-style init for setup (env vars, file writes); a follow-up PR will migrate those and drop the bridge.
Tests
Followups noted