Files
opencode/packages/opencode/specs/effect/routes.md
2026-04-13 10:04:32 -04:00

3.0 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/instance, not server/routes.

The main migration targets are:

  • server/instance/session.ts — heaviest; still has many direct facade calls for Session, SessionPrompt, SessionRevert, SessionCompaction, SessionShare, SessionSummary, Agent, Bus
  • server/instance/global.ts — still has direct facade calls for Config and instance lifecycle actions
  • server/instance/provider.ts — still has direct facade calls for Config and Provider
  • server/instance/question.ts — partially converted; still worth tracking here until it consistently uses the composed style
  • server/instance/pty.ts — still calls Pty facades directly
  • server/instance/experimental.ts — mixed state; some handlers are already composed, others still use facades

Additional route files that still participate in the migration:

  • server/instance/index.ts — Vcs, Agent, Skill, LSP, Format
  • server/instance/file.ts — Ripgrep, File, LSP
  • server/instance/mcp.ts — MCP facade-heavy
  • server/instance/permission.ts — Permission
  • server/instance/workspace.ts — Workspace
  • server/instance/tui.ts — Bus and Session
  • server/instance/middleware.ts — Session and Workspace lookups

Notes

  • Some handlers already use AppRuntime.runPromise(Effect.gen(...)) in isolated places. Keep pushing those files toward one consistent style.
  • Route conversion is closely tied to facade removal. As services lose makeRuntime-backed async exports, route handlers should switch to yielding the service directly.