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
+62-20Lines changed: 62 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -121,17 +121,46 @@ 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. Build in parallel, do not bridge into Hono
124
+
### 4. Bridge into Hono behind a feature flag
125
125
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`.
126
+
The `HttpApi`routes are bridged into the Hono server via `HttpRouter.toWebHandler`with a shared `memoMap`. This means:
127
127
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`.
128
+
- one process, one port — no separate server
129
+
- the Effect handler shares layer instances with `AppRuntime` (same `Question.Service`, etc.)
130
+
- Effect middleware handles auth and instance lookup independently from Hono middleware
131
+
- Hono's `.all()` catch-all intercepts matching paths before the Hono route handlers
129
132
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.
133
+
The bridge is gated behind `OPENCODE_EXPERIMENTAL_HTTPAPI` (or `OPENCODE_EXPERIMENTAL`). When the flag is off (default), all requests go through the original Hono handlers unchanged.
If the parallel slice works well, migrate additional JSON route groups one at a time. Leave streaming-style endpoints on Hono until there is a clear reason to move them.
143
+
The Hono route handlers are always registered (after the bridge) so `hono-openapi` generates the OpenAPI spec entries that feed SDK codegen. When the flag is on, these handlers are dead code — the `.all()` bridge matches first.
144
+
145
+
### 5. Observability
146
+
147
+
The `webHandler` provides `Observability.layer` via `Layer.provideMerge`. Since the `memoMap` is shared with `AppRuntime`, the tracing provider is deduplicated — no extra initialization cost.
148
+
149
+
This gives:
150
+
151
+
-**spans**: `Effect.fn("QuestionHttpApi.list")` etc. appear in traces alongside service-layer spans
152
+
-**HTTP logs**: `HttpMiddleware.logger` emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` annotations, flowing to motel via `OtlpLogger`
153
+
154
+
### 6. Migrate JSON route groups gradually
155
+
156
+
As each route group is ported to `HttpApi`:
157
+
158
+
1. change its `root` path from `/experimental/httpapi/<group>` to `/<group>`
159
+
2. add `.all("/<group>", handler)` / `.all("/<group>/*", handler)` to the flag block in `instance/index.ts`
160
+
3. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
161
+
4. verify SDK output is unchanged
162
+
163
+
Leave streaming-style endpoints on Hono until there is a clear reason to move them.
135
164
136
165
## Schema rule for HttpApi work
137
166
@@ -302,36 +331,43 @@ The first slice is successful if:
302
331
- OpenAPI is generated from the `HttpApi` contract
303
332
- the tests are straightforward enough that the next slice feels mechanical
304
333
305
-
## Learnings from the question slice
334
+
## Learnings
306
335
307
-
The first parallel `question` spike gave us a concrete pattern to reuse.
336
+
### Schema
308
337
309
338
-`Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`.
310
339
- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes.
311
340
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects.
312
341
- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes.
313
-
- the experimental slice should stay as a standalone Effect server and keep calling the existing service layer unchanged.
314
-
- compare generated OpenAPI semantically at the route and schema level.
342
+
-`Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes.
343
+
344
+
### Integration
345
+
346
+
-`HttpRouter.toWebHandler` with the shared `memoMap` from `run-service.ts` cleanly bridges Effect routes into Hono — one process, one port, shared layer instances.
347
+
-`Observability.layer` must be explicitly provided via `Layer.provideMerge` in the routes layer for OTEL spans and HTTP logs to flow. The `memoMap` deduplicates it with `AppRuntime` — no extra cost.
348
+
-`HttpMiddleware.logger` (enabled by default when `disableLogger` is not set) emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` — these flow through `OtlpLogger` to motel.
349
+
- Hono OpenAPI stubs must remain registered for SDK codegen until the SDK pipeline reads from the Effect OpenAPI spec instead.
350
+
- the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag gates the bridge at the Hono router level — default off, no behavior change unless opted in.
315
351
316
352
## Route inventory
317
353
318
354
Status legend:
319
355
320
-
-`done` - parallel `HttpApi` slice exists
356
+
-`bridged` - Effect HttpApi slice exists and is bridged into Hono behind the flag
357
+
-`done` - Effect HttpApi slice exists but not yet bridged
321
358
-`next` - good near-term candidate
322
359
-`later` - possible, but not first wave
323
360
-`defer` - not a good early `HttpApi` target
324
361
325
362
Current instance route inventory:
326
363
327
-
-`question` - `done`
328
-
endpoints in slice: `GET /question`, `POST /question/:requestID/reply`
329
-
-`permission` - `done`
330
-
endpoints in slice: `GET /permission`, `POST /permission/:requestID/reply`
0 commit comments