Skip to content

feat(hono): setup new propery handler generation strategy - #3518

Merged
melloware merged 5 commits into
masterfrom
feature/hono-handler-generation-strategy
Jun 1, 2026
Merged

feat(hono): setup new propery handler generation strategy#3518
melloware merged 5 commits into
masterfrom
feature/hono-handler-generation-strategy

Conversation

@anymaniax

@anymaniax anymaniax commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Handler files under override.hono.handlers are where users put business logic.
Since #3292 (shipped in 8.11.x), regeneration rebuilt the preamble + validator
chain from the spec and spliced back only the async (c) => {…} body — silently
deleting custom imports, middleware (authenticate(), rateLimit()), and
top-level helpers on every run. In one project a single regeneration stripped 66
handlers (~2,880 deletions), removing auth middleware from protected endpoints.

This PR replaces that with a configurable, non-destructive strategy.

New option: override.hono.handlerGenerationStrategy

Controls how an existing handler file is treated on regeneration (a
non-existent file is always generated fresh):

  • smart (default) — parse the file (TypeScript compiler API) and reconcile
    only orval-owned regions: its own imports (names, module paths,
    camelCase→PascalCase casing) and the zValidator(...) arguments, and append
    handlers for new operations. Custom imports, middleware, bodies, and top-level
    helpers are preserved.
  • skip — leave an existing file byte-for-byte unchanged.
  • full — previous behavior: rebuild header/imports/validators, splice back
    the body (drops custom imports/middleware/helpers).

smart is a general reconcile: adds missing imports/validators, removes stale
validators, migrates renamed schemas, appends new-operation stubs.

Also fixed

  • multipart/form-data: bodies with multipart/form-data or
    application/x-www-form-urlencoded now emit zValidator('form', …) + form:
    context instead of 'json'.

Implementation

  • New module packages/hono/src/handler-merge.ts (AST reconcile). Only rewrites a
    plain named import whose names are all orval-owned; locates imports by module
    specifier (no fuzzy name matching); resolves an aliased zValidator scoped to
    the validator module; on parse failure falls back to skip.
  • typescript is an optional peerDependency, loaded lazily via
    import('typescript'). If absent, smart/full degrade to skip with a
    warning. The compiler is not bundled.

⚠️ Breaking change

Default changes from the 8.11.x full (destructive) to smart (non-destructive).
Set handlerGenerationStrategy: 'full' for the old behavior.

Note: if output.clean is enabled and the handlers dir is under the output
target, handler files are deleted before generation, defeating preservation.

Tests

  • handler-merge.test.ts + index.test.ts: preservation of
    imports/middleware/helpers/bodies; camelCase→PascalCase migration (incl. mixed
    imports); add/remove/rename validators; new-operation append;
    namespace/default/aliased import safety; the getCommunityFilterContext clobber
    regression; inline-middleware handling; CRLF; typescript-unavailable fallback.
  • options.test.ts: default + explicit strategy.
  • tests/handler-preservation.spec.ts: end-to-end generate() ×2 with an edit
    between (clean disabled).
  • All existing generation snapshots unchanged.

Docs

  • output.mdx: documents handlerGenerationStrategy (+ typescript-peer & clean
    callouts).
  • hono.mdx: replaces the stale "refresh header/imports, preserve body" text.

Summary by CodeRabbit

  • New Features

    • Added handlerGenerationStrategy for Hono handlers: smart (default, non-destructive), skip (freeze), and full (regenerate wrapper and splice back bodies). Request-body validator now uses form for multipart/form-data and x-www-form-urlencoded.
  • Documentation

    • Expanded guides and configuration docs to describe regeneration strategies and the optional TypeScript peer dependency and its fallback behavior.
  • Tests

    • Added extensive unit and end-to-end tests covering reconciliation, preservation, strategy variations, and TypeScript-absent fallbacks.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6520266-4c7e-45db-aca5-272b05855ca9

📥 Commits

Reviewing files that changed from the base of the PR and between 6169756 and c5371b8.

📒 Files selected for processing (2)
  • packages/hono/src/handler-merge.test.ts
  • packages/hono/src/handler-merge.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/hono/src/handler-merge.test.ts
  • packages/hono/src/handler-merge.ts

📝 Walkthrough

Walkthrough

Adds a strategy-driven Hono handler regeneration system: smart (AST-based non-destructive reconciliation), skip (freeze), and full (rebuild while preserving handler bodies). Introduces optional TypeScript peer handling, form-body-aware validator selection, comprehensive tests, and documentation updates.

Changes

Handler Regeneration Strategy System

Layer / File(s) Summary
Type System & Configuration Setup
packages/core/src/types.ts, packages/orval/src/utils/options.ts, packages/orval/src/utils/options.test.ts, packages/core/src/test-utils/context.ts
New HonoHandlerStrategy union integrated into option types; normalization default set to smart; tests and test-utils updated.
Handler Merge Library (AST Utilities)
packages/hono/src/handler-merge.ts
New AST utilities: lazy TypeScript loader ensureTypeScript(), public types for validators/imports/handlers, parsing helpers, reconcileHandlerFile() (reconciles/removes Orval-owned imports, updates/inserts zValidator args, renames schema identifiers, appends stubs, applies edits), and extractHandlerBodies() for body extraction.
Handler Merge Library Tests
packages/hono/src/handler-merge.test.ts
Extensive tests covering reconciliation behavior, import/alias/case regressions, validator insertion/removal, schema migrations, CRLF handling, multi-handler cases, single-line call handling, and body extraction/parse-failure behavior.
Strategy-Driven Handler Generation
packages/hono/src/index.ts
generateHandlerFile now accepts strategy: HonoHandlerStrategy; implements skip (return existing), full (ensure TypeScript → extract bodies → rebuild + splice), and smart (ensure TypeScript → reconcile non-destructively). Forwards strategy across generation modes and centralizes fresh-file preamble generation.
Form Content Type Support
packages/hono/src/index.ts
Detects multipart/form-data and application/x-www-form-urlencoded and emits zValidator('form', ...); updates context body typing generation to emit form: vs json:.
Strategy Implementation Tests
packages/hono/src/index.test.ts, packages/hono/src/index.no-typescript.test.ts
Unit tests for skip/smart/full behaviors, multipart form validator generation, and missing-TypeScript fallback warning behavior.
End-to-End Integration Test
tests/handler-preservation.spec.ts, tests/vitest.snapshots.ts
Integration test that edits generated handlers, regenerates with smart, and asserts custom imports/middleware/helpers/bodies are preserved while Orval-managed validators stay synchronized; snapshot include updated.
User Documentation & Configuration
docs/content/docs/guides/hono.mdx, docs/content/docs/reference/configuration/output.mdx
Documents override.hono.handlerGenerationStrategy (`smart
TypeScript Peer Dependency
packages/hono/package.json
Declares typescript >= 5 as an optional peer dependency (peerDependencies + peerDependenciesMeta).
Test Fixtures & Helpers Updates
various test helpers and mocks across packages
Small formatting/default expansions to include handlerGenerationStrategy: 'smart' and vitest timeout increases to accommodate lazy TypeScript loads.

Sequence Diagram

sequenceDiagram
  participant User
  participant GenerateHandlerFile
  participant StrategyRouter
  participant ensureTypeScript
  participant TypeScriptAPI
  participant extractHandlerBodies
  participant reconcileHandlerFile

  User->>GenerateHandlerFile: generateHandlerFile(verbs, strategy)
  GenerateHandlerFile->>StrategyRouter: decide by strategy

  alt skip
    StrategyRouter->>GenerateHandlerFile: return existing source
  else full
    StrategyRouter->>ensureTypeScript: ensureTypeScript()
    ensureTypeScript->>TypeScriptAPI: dynamic import / parse check
    TypeScriptAPI-->>ensureTypeScript: available?
    ensureTypeScript-->>StrategyRouter: ok
    StrategyRouter->>extractHandlerBodies: extractHandlerBodies(existing)
    extractHandlerBodies->>TypeScriptAPI: parse AST & extract bodies
    extractHandlerBodies-->>StrategyRouter: bodies
    StrategyRouter->>GenerateHandlerFile: generate fresh file + splice bodies
    GenerateHandlerFile->>User: new file with user bodies
  else smart
    StrategyRouter->>ensureTypeScript: ensureTypeScript()
    ensureTypeScript->>TypeScriptAPI: dynamic import / parse check
    TypeScriptAPI-->>ensureTypeScript: available?
    ensureTypeScript-->>StrategyRouter: ok
    StrategyRouter->>reconcileHandlerFile: reconcileHandlerFile(existing, desired)
    reconcileHandlerFile->>TypeScriptAPI: parse AST & compute edits
    reconcileHandlerFile->>reconcileHandlerFile: applyEdits()
    reconcileHandlerFile-->>StrategyRouter: updated source
    StrategyRouter->>User: merged file preserving user code
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • orval-labs/orval#3292: Prior work on extracting/splicing handler bodies and preamble refresh; related to this PR's strategy/refactor.

Suggested labels

enhancement, zod

Suggested reviewers

  • snebjorn
  • melloware

Poem

🐰 I nibbled code and kept it tight,

Smart hops in and keeps your light.
Full will stitch the bodies back,
Skip will leave your file on track.
Validators hum where forms take flight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title contains a typo ('propery' instead of 'property') and is partially related to the changeset, describing one significant aspect of the change (handler generation strategy) but missing context about multipart/form-data fix. Correct the typo to 'feat(hono): setup new property handler generation strategy' or consider expanding to reflect both the strategy feature and the multipart/form-data fix.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/hono-handler-generation-strategy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Hono client’s handler regeneration behavior to prevent destructive overwrites of user-authored code by introducing a configurable handler merge strategy (defaulting to a new non-destructive “smart” strategy). It also adjusts validator generation for form-encoded request bodies and adds documentation + tests covering the new behavior and fallbacks.

Changes:

  • Add override.hono.handlerGenerationStrategy (smart default, skip, full) and normalize/type it across the codebase.
  • Implement AST-based reconciliation for existing handler files (optional typescript peer dependency; warn + fall back to skip when absent).
  • Fix request-body validator target/context generation for multipart/form-data and application/x-www-form-urlencoded, and add end-to-end/unit test coverage + docs.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/vitest.snapshots.ts Includes the new end-to-end handler preservation spec in the Vitest run.
tests/handler-preservation.spec.ts End-to-end regression test ensuring user code survives regeneration when clean is disabled.
packages/orval/src/utils/options.ts Normalizes handlerGenerationStrategy with default 'smart'.
packages/orval/src/utils/options.test.ts Tests defaulting + preserving explicit handlerGenerationStrategy.
packages/hono/src/index.ts Adds strategy-driven handler generation, TypeScript-optional behavior, and form-body validator/context fixes.
packages/hono/src/index.test.ts Tests smart/skip/full behaviors and form-body validator target.
packages/hono/src/index.no-typescript.test.ts Verifies warning + fallback behavior when typescript is unavailable.
packages/hono/src/handler-merge.ts New AST-based reconciler and body extractor for handler preservation.
packages/hono/src/handler-merge.test.ts Extensive reconciliation + safety regression tests (imports/validators/aliases/CRLF/etc.).
packages/hono/package.json Declares typescript as an optional peer dependency.
packages/core/src/types.ts Adds HonoHandlerStrategy type and options fields.
packages/core/src/test-utils/context.ts Updates test context defaults to include handlerGenerationStrategy: 'smart'.
docs/content/docs/reference/configuration/output.mdx Documents handlerGenerationStrategy and the TypeScript/clean caveats.
docs/content/docs/guides/hono.mdx Updates Hono guide to reflect the new strategy-based behavior.
bun.lock Records the optional peer dependency metadata for typescript.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/hono/src/handler-merge.ts
Comment thread packages/hono/src/handler-merge.ts Outdated
Comment thread packages/hono/src/handler-merge.ts
Comment thread packages/hono/src/handler-merge.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
tests/handler-preservation.spec.ts (1)

77-90: ⚡ Quick win

Make the mutation step less format-fragile.

This chained replace(...) sequence depends on exact emitted strings, so harmless formatting changes in generated handlers can break the test. Prefer stable anchors (e.g., regex on semantic tokens or AST/text markers) so the test validates behavior rather than formatting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/handler-preservation.spec.ts` around lines 77 - 90, The test's chained
string.replace calls are brittle; instead locate semantic anchors and perform
targeted edits via regex or AST-aware operations: find the "export const
createPetHandlers = factory.createHandlers(" invocation and inject
"authenticate()," into its argument list (use a regex that allows
whitespace/newlines), insert the USER_BODY line by matching the handler arrow
start pattern like "=>\\s*\\{" and replacing with "=> {\\n    return
c.json(await save()); // USER_BODY" (again using a flexible regex), and append
the async save() helper by matching the end of the module or a stable marker;
reference symbols to change: fresh, edited, createPetHandlers,
factory.createHandlers, authenticate(), save(), and USER_BODY. Ensure the
replacements tolerate varying whitespace/formatting so the test validates
behavior not exact formatting.
packages/hono/src/index.no-typescript.test.ts (1)

10-19: Confirm mocks match generateHandlerFile’s actual imports; consider isolating module-level warning state

  • packages/hono/src/index.ts imports ensureTypeScript from ./handler-merge and logWarning from @orval/core, so the vi.mock('./handler-merge'...) and vi.mock('@orval/core'...) overrides intercept the real calls.
  • Remaining fragility: warnedMissingTypeScript is module-scoped in index.ts, so the “no warning” expectation for the “full” path depends on the prior “smart” test run; reset module state / re-import between cases to avoid order sensitivity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/hono/src/index.no-typescript.test.ts` around lines 10 - 19, The
tests mock './handler-merge' and '`@orval/core`' but the suite is order-sensitive
because index.ts keeps a module-scoped warnedMissingTypeScript flag; update the
mocks to match the actual named exports used by generateHandlerFile
(ensureTypeScript and logWarning) and make each test isolated by clearing the
module cache and re-importing index.ts between cases (e.g., call
vi.resetModules() and then import the module afresh) or refactor index.ts to
expose/reset warnedMissingTypeScript so tests can reset it explicitly; ensure
the mock for './handler-merge' returns an exported ensureTypeScript function and
the '`@orval/core`' mock returns logWarning so imports line up exactly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/hono/src/handler-merge.ts`:
- Around line 420-428: The insertion logic for pendingInsertions assumes there's
a newline after the last import; when source.indexOf('\n', lastImport.getEnd())
returns -1 new imports get prepended. Change the computation of insertPos (used
with pendingInsertions, importDeclarations, lastImport, source, edits) to guard
for -1: compute const newlinePos = source.indexOf('\n', lastImport.getEnd());
then set insertPos = newlinePos === -1 ? source.length : newlinePos + 1 so new
imports are appended after the last import even if it's at EOF.
- Around line 519-531: The insertion currently returns lineStart(...) for the
handler which wrongly places validators at the start of the line for single-line
handlers; update validatorInsertPos to compute handlerStart =
handler.getStart(sourceFile) and return handlerStart unless the handler already
begins at the start of its line (i.e., lineStart(source, handlerStart) ===
handlerStart), in which case keep returning the lineStart; keep the fallback of
call.getEnd() - 1 when no handler is found.

In `@packages/hono/src/index.test.ts`:
- Around line 49-56: The tests are constructing NormalizedHonoOptions without
the required handlerGenerationStrategy (type HonoHandlerStrategy), causing
TS2741; update the fixture objects (e.g., the args/fixtures that build hono: {
compositeRoute, validator, validatorOutputPath }) to include a
handlerGenerationStrategy property (for example 'smart' or the appropriate
HonoHandlerStrategy value) so the constructed NormalizedHonoOptions satisfies
the type, or alternatively ensure normalization/defaulting that sets
handlerGenerationStrategy runs before the object is typed as
NormalizedHonoOptions; refer to the NormalizedHonoOptions type and add
handlerGenerationStrategy to the failing fixtures (and the args helper if used)
to fix the compile error.

In `@packages/hono/src/index.ts`:
- Around line 528-539: extractHandlerBodies() currently returns an empty Map on
parse failure which makes the 'full' branch regenerate handlers with
DEFAULT_HANDLER_BODY and drop user code; change extractHandlerBodies to return
null (or another sentinel) on parse/parse-failure and update this branch in
packages/hono/src/index.ts to detect that sentinel (e.g., bodies === null) and
treat it like the 'skip' behavior instead of calling generateFreshHandlerFile;
reference extractHandlerBodies, generateFreshHandlerFile, DEFAULT_HANDLER_BODY
and ensure when bodies is a real Map (not null) you continue to call
generateFreshHandlerFile with bodyFor using
bodies.get(`${operationName}Handlers`).

---

Nitpick comments:
In `@packages/hono/src/index.no-typescript.test.ts`:
- Around line 10-19: The tests mock './handler-merge' and '`@orval/core`' but the
suite is order-sensitive because index.ts keeps a module-scoped
warnedMissingTypeScript flag; update the mocks to match the actual named exports
used by generateHandlerFile (ensureTypeScript and logWarning) and make each test
isolated by clearing the module cache and re-importing index.ts between cases
(e.g., call vi.resetModules() and then import the module afresh) or refactor
index.ts to expose/reset warnedMissingTypeScript so tests can reset it
explicitly; ensure the mock for './handler-merge' returns an exported
ensureTypeScript function and the '`@orval/core`' mock returns logWarning so
imports line up exactly.

In `@tests/handler-preservation.spec.ts`:
- Around line 77-90: The test's chained string.replace calls are brittle;
instead locate semantic anchors and perform targeted edits via regex or
AST-aware operations: find the "export const createPetHandlers =
factory.createHandlers(" invocation and inject "authenticate()," into its
argument list (use a regex that allows whitespace/newlines), insert the
USER_BODY line by matching the handler arrow start pattern like "=>\\s*\\{" and
replacing with "=> {\\n    return c.json(await save()); // USER_BODY" (again
using a flexible regex), and append the async save() helper by matching the end
of the module or a stable marker; reference symbols to change: fresh, edited,
createPetHandlers, factory.createHandlers, authenticate(), save(), and
USER_BODY. Ensure the replacements tolerate varying whitespace/formatting so the
test validates behavior not exact formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c06feafa-a9b7-4622-ada6-15a5bc8e4211

📥 Commits

Reviewing files that changed from the base of the PR and between 814a0a6 and ecd6fed.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • docs/content/docs/guides/hono.mdx
  • docs/content/docs/reference/configuration/output.mdx
  • packages/core/src/test-utils/context.ts
  • packages/core/src/types.ts
  • packages/hono/package.json
  • packages/hono/src/handler-merge.test.ts
  • packages/hono/src/handler-merge.ts
  • packages/hono/src/index.no-typescript.test.ts
  • packages/hono/src/index.test.ts
  • packages/hono/src/index.ts
  • packages/orval/src/utils/options.test.ts
  • packages/orval/src/utils/options.ts
  • tests/handler-preservation.spec.ts
  • tests/vitest.snapshots.ts

Comment thread packages/hono/src/handler-merge.ts
Comment thread packages/hono/src/handler-merge.ts
Comment thread packages/hono/src/index.test.ts
Comment thread packages/hono/src/index.ts
@melloware melloware added the hono Hono related issue label Jun 1, 2026
@melloware melloware added this to the 8.15.0 milestone Jun 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.

Comment thread packages/hono/src/index.ts
Comment thread packages/hono/src/handler-merge.ts Outdated
@anymaniax

Copy link
Copy Markdown
Collaborator Author

Just flagging a pre-existing bug I noticed while working on this (not introduced
by this PR): the hono.validator option isn't actually applied to the generated
handlers. Whether it's set to true, 'hono', or false, the handlers come out
the same — always importing zValidator from the local .validator file and
including all validators.

The reason is that handler generation always receives the .validator.ts path,
so it never sees the 'hono' (use @hono/zod-validator directly) or false
(no validators) intent. With 'hono'/false the handler even imports from a
validator file that isn't emitted.

It's out of scope here so I'll fix it in a follow-up PR — just wanted to note it
so it's on the radar.

@melloware
melloware merged commit 51dd171 into master Jun 1, 2026
8 checks passed
@melloware
melloware deleted the feature/hono-handler-generation-strategy branch June 2, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hono Hono related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants