Skip to content

Convert LoadInput.init to Effect + extract InstanceBootstrap as a Service#25376

Merged
kitlangton merged 3 commits into
devfrom
kit/instance-init-effect
May 2, 2026
Merged

Convert LoadInput.init to Effect + extract InstanceBootstrap as a Service#25376
kitlangton merged 3 commits into
devfrom
kit/instance-init-effect

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

  • `LoadInput.init` switches from `() => Promise` to `Effect.Effect<void, never, R>`. The `R` is generic and propagates through `load`/`reload`/`provide` so callers can pass Effect-native init bodies without ceremony.
  • `InstanceBootstrap` is restructured from a top-level `Effect` const into a `Service` with `.run` on its interface. Tests can swap a fake `InstanceBootstrap.Service` layer; production wiring composes the default via `InstanceBootstrap.defaultLayer`.
  • Production callers (HttpApi instance-context middleware, project handler) drop `() => AppRuntime.runPromise(InstanceBootstrap)` and pass `bootstrap.run` directly via `yield* InstanceBootstrap.Service`.

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

  • `bun run test test/project/instance.test.ts test/server/httpapi-instance-context.test.ts test/file/path-traversal.test.ts` — 30/30 pass
  • `bun run typecheck` — clean

Followups noted

  1. Migrate ~70 test sites from `init: () => Promise` to Effect-style; drop `liftLegacyInput`.
  2. Bake `InstanceBootstrap.Service` into `InstanceStore` as default init so production callers don't pass it. Requires breaking the `Config → Instance → InstanceStore → bootstrap → Config` runtime cycle (move `Config.update` dispose responsibility to caller, etc).

kitlangton added 2 commits May 1, 2026 23:47
…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.
@kitlangton kitlangton force-pushed the kit/instance-init-effect branch from fc4b662 to 60254ed Compare May 2, 2026 03:48
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.
@kitlangton kitlangton merged commit f33aec1 into dev May 2, 2026
10 checks passed
@kitlangton kitlangton deleted the kit/instance-init-effect branch May 2, 2026 04:02
oleksii-honchar pushed a commit to oleksii-honchar/better-opencode that referenced this pull request May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant