Skip to content

types: canonical RouteRules interface#1474

Merged
pi0 merged 1 commit into
mainfrom
types/route-rules
Jul 16, 2026
Merged

types: canonical RouteRules interface#1474
pi0 merged 1 commit into
mainfrom
types/route-rules

Conversation

@pi0x

@pi0x pi0x commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Route rules are implemented outside of h3 — by Nitro, and now by h3-rules. The behaviour lives outside core, but the type describing it currently has no canonical home: it is declared by whichever package implements the rules.

That means the module you augment depends on which package you happen to depend on. Nitro apps today augment h3-rules, and previously NitroRouteRules. If another module wants to read or contribute a rule, it has to pick one of those packages and take on a dependency for a type alone.

This PR moves the declaration to the one package everyone in the chain already depends on.

What's added

An empty RouteRules interface, exported from h3, as the canonical extension point:

declare module "h3" {
  interface RouteRules {
    swr?: number | boolean;
    redirect?: string | { to: string; status?: number };
  }
}

h3 itself declares no rules and implements no behaviour — it only owns the name, so that every module merges into the same interface.

Matched rules are also typed where rule modules already put them:

export interface H3EventContext {
  routeRules?: Readonly<RouteRules>;
}

Nitro's generated middleware already assigns event.context.routeRules, so this types a field that exists today rather than introducing one.

Notes

The interface is closed until augmented. With no augmentation, event.context.routeRules?.swr is a compile error rather than unknown — so typos in rule names are caught. Modules that want open-ended keys can add an index signature via augmentation.

Rules are exposed as Readonly. Rule matchers are commonly memoized (Nitro's is), so a single matched object can be shared across requests and mutating it in place would leak between them. Readonly is applied at the consumption site rather than asked of each augmenter, so existing augmentations get it for free and a future one can't forget it. Replacing the whole object still works, so how rule modules attach rules is unaffected. Note this is shallow: nested rule objects are not deeply frozen by the type.

For rule modules

Augmentations keep merging through h3's re-export, so h3-rules can re-export h3's interface and Nitro's existing declare module "h3-rules" augmentation continues to reach it — NitroRouteRules needs no change.

Test plan

  • Type tests in test/unit/types.test-d.ts cover that an augmentation merges into the interface used by event.context, that undeclared keys stay an error, that matched rules are readonly, and that whole-object assignment still compiles.
  • Verified against the built dist/ types that a downstream declare module "h3" resolves through the entry re-exports, so the extension point works for real consumers and not just in-repo.
  • pnpm lint and the full suite pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for defining and extending route rules with TypeScript.
    • Matched route rules are now available through the request context for route handlers.
    • Route rule values are exposed as read-only, helping prevent accidental changes during request handling.
  • Documentation
    • Added guidance for extending route rule types with custom application-specific settings.
  • Tests
    • Added type checks covering custom rules, read-only behavior, and invalid rule access.

Route rules are implemented outside of h3 (Nitro, and now `h3-rules`), but
the type describing them has no canonical home: it is declared by whichever
package implements the rules, so consumers augment a different module name
depending on which one they happen to depend on.

Declare an empty `RouteRules` interface in h3 as the single extension point
for the ecosystem to augment, and type the matched rules that rule modules
already expose on `event.context.routeRules`.

Rules are exposed as `Readonly`: matchers are commonly memoized, so a matched
object can be shared between requests and must not be mutated in place.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@pi0x
pi0x requested a review from pi0 as a code owner July 16, 2026 10:44
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an augmentable RouteRules type, re-exports it, and exposes matched rules on H3EventContext as readonly data. Type-level tests verify declaration merging, unknown-key rejection, readonly properties, and context replacement.

Changes

Route rules typing

Layer / File(s) Summary
RouteRules contract and export
src/types/route-rules.ts, src/index.ts
Defines the declaration-merging RouteRules interface and re-exports it from the package entry point.
Context exposure and type validation
src/types/context.ts, test/unit/types.test-d.ts
Adds optional readonly routeRules context data and tests augmented keys, unknown-key errors, readonly access, and object replacement.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: pi0

Poem

I’m a bunny with rules in my ear,
Typed paths now hop crystal-clear.
Readonly carrots stay put,
Unknown keys get a “nope” look,
And merged rules bring good cheer! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a canonical RouteRules interface for type augmentation.
✨ 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 types/route-rules

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.

@pi0 pi0 changed the title feat(types): canonical RouteRules interface types: canonical RouteRules interface Jul 16, 2026
@pi0
pi0 merged commit 74d53c5 into main Jul 16, 2026
8 of 9 checks passed
@pi0
pi0 deleted the types/route-rules branch July 16, 2026 10:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/unit/types.test-d.ts (1)

228-229: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Ensure @ts-expect-error specifically asserts the readonly constraint.

Since routeRules is an optional property on the context, event.context.routeRules.swr inherently triggers an "Object is possibly 'undefined'" error. This causes @ts-expect-error to swallow that error and pass the test regardless of whether the property is actually readonly.

Adding a non-null assertion (!) resolves the undefined error, ensuring the test correctly fails if the readonly constraint is ever accidentally removed.

♻️ Proposed refactor
       // `@ts-expect-error` matched rules must not be mutated in place
-      event.context.routeRules.swr = 60;
+      event.context.routeRules!.swr = 60;
🤖 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 `@test/unit/types.test-d.ts` around lines 228 - 229, Update the readonly
mutation assertion in the event context type test by adding a non-null assertion
to routeRules before assigning swr, so `@ts-expect-error` targets the readonly
constraint rather than the optional-property undefined error.
🤖 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.

Nitpick comments:
In `@test/unit/types.test-d.ts`:
- Around line 228-229: Update the readonly mutation assertion in the event
context type test by adding a non-null assertion to routeRules before assigning
swr, so `@ts-expect-error` targets the readonly constraint rather than the
optional-property undefined error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f3bb831-fcb2-4042-836a-379d15a46409

📥 Commits

Reviewing files that changed from the base of the PR and between 17e0cbd and 3b5a48f.

📒 Files selected for processing (4)
  • src/index.ts
  • src/types/context.ts
  • src/types/route-rules.ts
  • test/unit/types.test-d.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants