You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/opencode/specs/effect/http-api.md
+27-50Lines changed: 27 additions & 50 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -121,14 +121,13 @@ Why `question` first:
121
121
122
122
Do not re-architect business logic during the HTTP migration. `HttpApi` handlers should call the same Effect services already used by the Hono handlers.
123
123
124
-
### 4. Run in parallel before replacing
124
+
### 4. Build in parallel, do not bridge into Hono
125
125
126
-
Prefer mounting an experimental `HttpApi` surface alongside the existing Hono routes first. That lowers migration risk and lets us compare:
126
+
The `HttpApi` implementation lives under `src/server/instance/httpapi/` as a standalone Effect HTTP server. It is **not mounted into the Hono app**. There is no `toWebHandler` bridge, no Hono `Handler` export, and no `.route()` call wiring it into `experimental.ts`.
127
127
128
-
- handler ergonomics
129
-
- OpenAPI output
130
-
- auth and middleware integration
131
-
- test ergonomics
128
+
The standalone server (`httpapi/server.ts`) can be started independently and proves the routes work. Tests exercise it via `HttpRouter.serve` with `NodeHttpServer.layerTest`.
129
+
130
+
The goal is to build enough route coverage in the Effect server that the Hono server can eventually be replaced entirely. Until then, the two implementations exist side by side but are completely separate processes.
132
131
133
132
### 5. Migrate JSON route groups gradually
134
133
@@ -218,17 +217,15 @@ Placement rule:
218
217
219
218
Suggested file layout for a repeatable spike:
220
219
221
-
-`src/server/instance/httpapi/question.ts`
222
-
-`src/server/instance/httpapi/index.ts`
223
-
-`test/server/question-httpapi.test.ts`
224
-
-`test/server/question-httpapi-openapi.test.ts`
220
+
-`src/server/instance/httpapi/question.ts` — contract and handler layer for one route group
221
+
-`src/server/instance/httpapi/server.ts` — standalone Effect HTTP server that composes all groups
222
+
-`test/server/question-httpapi.test.ts` — end-to-end test against the real service
225
223
226
224
Suggested responsibilities:
227
225
228
-
-`question.ts` defines the `HttpApi` contract and `HttpApiBuilder.group(...)` handlers for the experimental slice
229
-
-`index.ts` combines experimental `HttpApi` groups and exposes the mounted handler or layer
230
-
-`question-httpapi.test.ts` proves the route works end-to-end against the real service
231
-
-`question-httpapi-openapi.test.ts` proves the generated OpenAPI is acceptable for the migrated endpoints
226
+
-`question.ts` defines the `HttpApi` contract and `HttpApiBuilder.group(...)` handlers
227
+
-`server.ts` composes all route groups into one `HttpRouter.serve` layer with shared middleware (auth, instance lookup)
228
+
- tests use `ExperimentalHttpApiServer.layerTest` to run against a real in-process HTTP server
232
229
233
230
## Example migration shape
234
231
@@ -248,11 +245,12 @@ Each route-group spike should follow the same shape.
248
245
- keep handler bodies thin
249
246
- keep transport mapping at the HTTP boundary only
250
247
251
-
### 3. Mounting
248
+
### 3. Standalone server
252
249
253
-
- mount under an experimental prefix such as `/experimental/httpapi`
254
-
- keep existing Hono routes unchanged
255
-
- expose separate OpenAPI output for the experimental slice first
250
+
- the Effect HTTP server is self-contained in `httpapi/server.ts`
251
+
- it is **not** mounted into the Hono app — no bridge, no `toWebHandler`
252
+
- route paths use the `/experimental/httpapi` prefix so they match the eventual cutover
253
+
- each route group exposes its own OpenAPI doc endpoint
256
254
257
255
### 4. Verification
258
256
@@ -263,53 +261,32 @@ Each route-group spike should follow the same shape.
263
261
264
262
## Boundary composition
265
263
266
-
The first slices should keep the existing outer server composition and only replace the route contract and handler layer.
264
+
The standalone Effect server owns its own middleware stack. It does not share middleware with the Hono server.
267
265
268
266
### Auth
269
267
270
-
- keep `AuthMiddleware` at the outer Hono app level
271
-
- do not duplicate auth checks inside each `HttpApi` group for the first parallel slices
272
-
- treat auth as an already-satisfied transport concern before the request reaches the `HttpApi` handler
273
-
274
-
Practical rule:
275
-
276
-
- if a route is currently protected by the shared server middleware stack, the experimental `HttpApi` route should stay mounted behind that same stack
268
+
- the standalone server implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
269
+
- each route group's `HttpApi` is wrapped with `.middleware(Authorization)` before being served
270
+
- this is independent of the Hono `AuthMiddleware` — when the Effect server eventually replaces Hono, this becomes the only auth layer
277
271
278
272
### Instance and workspace lookup
279
273
280
-
- keep `WorkspaceRouterMiddleware` as the source of truth for resolving `directory`, `workspace`, and session-derived workspace context
281
-
- let that middleware provide `Instance.current` and `WorkspaceContext` before the request reaches the `HttpApi` handler
282
-
- keep the `HttpApi` handlers unaware of path-to-instance lookup details when the existing Hono middleware already handles them
283
-
284
-
Practical rule:
285
-
286
-
-`HttpApi` handlers should yield services from context and assume the correct instance has already been provided
287
-
- only move instance lookup into the `HttpApi` layer if we later decide to migrate the outer middleware boundary itself
274
+
- the standalone server resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
275
+
- this is the Effect equivalent of the Hono `WorkspaceRouterMiddleware`
276
+
-`HttpApi` handlers yield services from context and assume the correct instance has already been provided
288
277
289
278
### Error mapping
290
279
291
280
- keep domain and service errors typed in the service layer
292
281
- declare typed transport errors on the endpoint only when the route can actually return them intentionally
293
-
- prefer explicit endpoint-level error schemas over relying on the outer Hono `ErrorMiddleware` for expected route behavior
294
-
295
-
Practical rule:
296
-
297
-
- request decoding failures should remain transport-level `400`s
282
+
- request decoding failures are transport-level `400`s handled by Effect `HttpApi` automatically
298
283
- storage or lookup failures that are part of the route contract should be declared as typed endpoint errors
299
-
- unexpected defects can still fall through to the outer error middleware while the slice is experimental
300
-
301
-
For the current parallel slices, this means:
302
-
303
-
- auth still composes outside `HttpApi`
304
-
- instance selection still composes outside `HttpApi`
305
-
- success payloads should be schema-defined from canonical Effect schemas
306
-
- known route errors should be modeled at the endpoint boundary incrementally instead of all at once
307
284
308
285
## Exit criteria for the spike
309
286
310
287
The first slice is successful if:
311
288
312
-
- the endpoints run in parallel with the current Hono routes
289
+
- the standalone Effect server starts and serves the endpoints independently of the Hono server
313
290
- the handlers reuse the existing Effect service
314
291
- request decoding and response shapes are schema-defined from canonical Effect schemas
315
292
- any remaining Zod boundary usage is derived from `.zod` or clearly temporary
@@ -324,8 +301,8 @@ The first parallel `question` spike gave us a concrete pattern to reuse.
324
301
- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes.
325
302
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects.
326
303
- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes.
327
-
- the experimental slice should stay mounted in parallel and keep calling the existing service layer unchanged.
328
-
- compare generated OpenAPI semantically at the route and schema level; in the current setup the exported OpenAPI paths do not include the outer Hono mount prefix.
304
+
- the experimental slice should stay as a standalone Effect server and keep calling the existing service layer unchanged.
305
+
- compare generated OpenAPI semantically at the route and schema level.
0 commit comments