2.5 KiB
2.5 KiB
Route handler effectification
Practical reference for converting server route handlers in packages/opencode to a single AppRuntime.runPromise(Effect.gen(...)) body.
Goal
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.
// Before - one facade call per service
;async (c) => {
await SessionRunState.assertNotBusy(id)
await Session.removeMessage({ sessionID: id, messageID })
return c.json(true)
}
// 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)
}
Rules
- 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.
Current route files
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.
Files still worth tracking here:
handlers/session.ts— still the heaviest mixed file; some paths keep compatibility translations and direct event publicationhandlers/experimental.ts— mixed state; some handlers still rely on request-local context readsmiddleware/*— still contains compatibility policy for auth, compression, errors, instance context, and workspace routingpublic.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
Notes
- 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.