docs(effect): add cleanup roadmap (#27228)

This commit is contained in:
Kit Langton
2026-05-12 22:45:26 -04:00
committed by GitHub
parent 588b5240d0
commit 13fbc9acfc
7 changed files with 804 additions and 1072 deletions

View File

@@ -1,57 +1,61 @@
# Route handler effectification
# HTTP Route Patterns
Practical reference for converting server route handlers in `packages/opencode` to a single `AppRuntime.runPromise(Effect.gen(...))` body.
Current guidance for `packages/opencode/src/server/routes/instance/httpapi`.
## Goal
## Handler Shape
Route handlers should wrap their entire body in a single `AppRuntime.runPromise(Effect.gen(...))` call, yielding services from context rather than calling facades one-by-one.
This eliminates multiple `runPromise` round-trips and lets handlers compose naturally.
Use `HttpApiBuilder.group(...)` for normal JSON and streaming HTTP API
endpoints. Yield stable services once while building the handler layer,
then close over those services in endpoint implementations.
```ts
// Before - one facade call per service
;async (c) => {
await SessionRunState.assertNotBusy(id)
await Session.removeMessage({ sessionID: id, messageID })
return c.json(true)
}
export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
Effect.gen(function* () {
const session = yield* Session.Service
// After - one Effect.gen, yield services from context
;async (c) => {
await AppRuntime.runPromise(
Effect.gen(function* () {
const state = yield* SessionRunState.Service
const session = yield* Session.Service
yield* state.assertNotBusy(id)
yield* session.removeMessage({ sessionID: id, messageID })
}),
)
return c.json(true)
}
return handlers.handle("list", () => session.list())
}),
)
```
## Rules
Use raw `HttpRouter` only for routes that do not fit the request/response
HttpApi model, such as WebSocket upgrades or catch-all fallback routes.
- Wrap the whole handler body in one `AppRuntime.runPromise(Effect.gen(...))` call when the handler is service-heavy.
- Yield services from context instead of calling async facades repeatedly.
- When independent service calls can run in parallel, use `Effect.all(..., { concurrency: "unbounded" })`.
- Prefer one composed Effect body over multiple separate `runPromise(...)` calls in the same handler.
Do not rebuild stable layers inside request handlers. Provide stable
services at the route/layer boundary and use request-level provisioning
only for request-derived context.
## Current route files
## Error Boundaries
Current instance route files live under `src/server/routes/instance/httpapi`.
Most handlers already yield stable services at route-layer construction and then
close over those services in endpoint implementations.
Expected service errors should be mapped at the handler boundary to
endpoint-declared public HTTP errors. Keep one-off mappings inline. Extract
small helpers when the same mapping repeats.
Files still worth tracking here:
Generic middleware should not become a domain-error mapper. It should
handle cross-cutting concerns and final unknown-defect fallback.
- [ ] `handlers/session.ts` — still the heaviest mixed file; some paths keep compatibility translations and direct event publication
- [ ] `handlers/experimental.ts` — mixed state; some handlers still rely on request-local context reads
- [ ] `middleware/*` — still contains compatibility policy for auth, compression, errors, instance context, and workspace routing
- [ ] `public.ts` — still owns SDK/OpenAPI compatibility translation shims
- [ ] raw route modules — WebSocket and catch-all routes should stay explicit and avoid rebuilding stable layers per request
Public JSON errors should be explicit schema contracts declared on each
endpoint or group. Built-in `HttpApiError.*` is fine only when its generated
body is intentionally the public wire shape.
## Notes
Preserve existing `{ name, data }` error bodies until a deliberate breaking
API change.
- Route conversion is now less about backend migration and more about removing the remaining direct `Instance.*` reads, request-local service plumbing, and OpenAPI compatibility shims.
- Prefer route-layer service capture over rebuilding or providing stable layers inside individual handlers.
## OpenAPI Compatibility
`public.ts` still owns SDK/OpenAPI compatibility transforms. Shrink those
transforms by tightening source schemas one workaround at a time.
When an OpenAPI-visible source schema changes:
- verify the generated SDK diff is intentional
- preserve legacy compatibility unless the PR explicitly changes it
- prefer source-schema fixes over new post-processing rules
## Checklist For Route PRs
- [ ] Stable services are yielded at handler-layer construction.
- [ ] Expected domain errors are translated at the route boundary.
- [ ] Endpoint/group error schemas describe the public body and status.
- [ ] Middleware does not gain new domain-specific name checks.
- [ ] Raw routes are used only when HttpApi is the wrong abstraction.