chore: initialize qiming workspace repository
This commit is contained in:
64
qimingcode/packages/opencode/specs/effect/routes.md
Normal file
64
qimingcode/packages/opencode/specs/effect/routes.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user