65 lines
3.3 KiB
Markdown
65 lines
3.3 KiB
Markdown
# 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.
|
|
|
|
```ts
|
|
// 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`.
|
|
|
|
Files that are already mostly on the intended service-yielding shape:
|
|
|
|
- [x] `server/routes/instance/question.ts` — handlers yield `Question.Service`
|
|
- [x] `server/routes/instance/provider.ts` — handlers yield `Provider.Service`, `ProviderAuth.Service`, and `Config.Service`
|
|
- [x] `server/routes/instance/permission.ts` — handlers yield `Permission.Service`
|
|
- [x] `server/routes/instance/mcp.ts` — handlers mostly yield `MCP.Service`
|
|
- [x] `server/routes/instance/pty.ts` — handlers yield `Pty.Service`
|
|
|
|
Files still worth tracking here:
|
|
|
|
- [ ] `server/routes/instance/session.ts` — still the heaviest mixed file; many handlers are composed, but the file still mixes patterns and has direct `Bus.publish(...)` / `Session.list(...)` usage
|
|
- [ ] `server/routes/instance/index.ts` — mostly converted, but still has direct `Instance.dispose()` / `Instance.*` reads for `/instance/dispose` and `/path`
|
|
- [ ] `server/routes/instance/file.ts` — most handlers yield services, but `/find` still passes `Instance.directory` directly into ripgrep and `/find/symbol` is still stubbed
|
|
- [ ] `server/routes/instance/experimental.ts` — mixed state; many handlers are composed, but some still rely on `runRequest(...)` or direct `Instance.project` reads
|
|
- [ ] `server/routes/instance/middleware.ts` — still enters the instance via `Instance.provide(...)`
|
|
- [ ] `server/routes/global.ts` — still uses `Instance.disposeAll()` and remains partly outside the fully-composed style
|
|
|
|
## Notes
|
|
|
|
- Route conversion is now less about facade removal and more about removing the remaining direct `Instance.*` reads, `Instance.provide(...)` boundaries, and small Promise-style bridges inside route files.
|
|
- `jsonRequest(...)` / `runRequest(...)` already provide a good intermediate shape for many handlers. The remaining cleanup is mostly consistency work in the heavier files.
|