Repository navigation
Conversation
Fixes voidzero-dev#564 ## Summary Run per-route `defineHandler` middleware around the final HTTP Response instead of around a temporary leaf body. Pages finish load/render (and action revalidation) inside an endpoint step; routes still go through `convertReturnValue`. Nested compositions flatten; middleware overloads keep leaf `withValidator` phantoms (`__validators` / `__output`). Most of this patch was written by a coding agent. The change is large on purpose: clearing `finalized` while keeping two exit paths was not a single owner for "handler result → Response". Happy to change if maintainers prefer one of the alternatives below. ## Changes - Add `executeComposedHandler` and composition metadata on composed handlers. - Page protocol: composed loaders/actions render inside the middleware endpoint; validation `issues` 400s become page field errors there. - `createHttpRouteHandler`: composed handlers return the middleware Response; plain handlers still use `convertReturnValue`. - Tests for page middleware cases, route `etag` on the final body, and validator types through middleware. ## Testing - `packages/void` unit: `define-handler.test.ts`, `pages-protocol.test.ts` - `packages/void` typecheck (incl. middleware + `withValidator`) ## Alternatives This PR is the shape we landed on: one executor (`executeComposedHandler`) that runs middleware around the final HTTP Response, with pages doing load/render (and action revalidation) inside the endpoint step. We also looked at a smaller patch that clears `finalized` and keeps separate exits for routes vs pages. That still left two places that turn a handler result into a Response, so we did not ship it. Another option is to wrap middleware only around the leaf mutation on POST and re-render the page outside. After-next would then only see JSON/204, not the HTML the client actually gets. A third option is to keep returning the raw leaf value from `defineHandler` and let every caller adapt it. That matches less with the route path (`convertReturnValue` / final Response) and is easy to misuse from page code. On how the page Response enters the context: this PR sets `c.res` from a full `Response` so after-next can mutate it. Rebuilding with `c.newResponse()` / `c.body()` would merge headers set before `next()` more like stock Hono, but needs care with deferred / streaming. For types: this PR uses explicit `TypedHandler<V, R>` overloads per middleware arity. A single `H extends TypedHandler` overload is shorter and may be fine if maintainers prefer that. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #564
Summary
Run per-route
defineHandlermiddleware around the final HTTP Response instead of around a temporary leaf body. Pages finish load/render (and action revalidation) inside an endpoint step; routes still go throughconvertReturnValue. Nested compositions flatten; middleware overloads keep leafwithValidatorphantoms (__validators/__output).Most of this patch was written by a coding agent. The change is large on purpose: clearing
finalizedwhile keeping two exit paths was not a single owner for "handler result → Response".Happy to change if maintainers prefer one of the alternatives below.
Changes
executeComposedHandlerand composition metadata on composed handlers.issues400s become page field errors there.createHttpRouteHandler: composed handlers return the middleware Response; plain handlers still useconvertReturnValue.etagon the final body, and validator types through middleware.Testing
packages/voidunit:define-handler.test.ts,pages-protocol.test.tspackages/voidtypecheck (incl. middleware +withValidator)Alternatives
This PR is the shape we landed on: one executor (
executeComposedHandler) that runs middleware around the final HTTP Response, with pages doing load/render (and action revalidation) inside the endpoint step.We also looked at a smaller patch that clears
finalizedand keeps separate exits for routes vs pages. That still left two places that turn a handler result into a Response, so we did not ship it.Another option is to wrap middleware only around the leaf mutation on POST and re-render the page outside. After-next would then only see JSON/204, not the HTML the client actually gets.
A third option is to keep returning the raw leaf value from
defineHandlerand let every caller adapt it. That matches less with the route path (convertReturnValue/ final Response) and is easy to misuse from page code.On how the page Response enters the context: this PR sets
c.resfrom a fullResponseso after-next can mutate it. Rebuilding withc.newResponse()/c.body()would merge headers set beforenext()more like stock Hono, but needs care with deferred / streaming.For types: this PR uses explicit
TypedHandler<V, R>overloads per middleware arity. A singleH extends TypedHandleroverload is shorter and may be fine if maintainers prefer that.🐸