types: canonical RouteRules interface#1474
Conversation
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]>
📝 WalkthroughWalkthroughAdds an augmentable ChangesRoute rules typing
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
RouteRules interfaceRouteRules interface
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/unit/types.test-d.ts (1)
228-229: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winEnsure
@ts-expect-errorspecifically asserts thereadonlyconstraint.Since
routeRulesis an optional property on the context,event.context.routeRules.swrinherently triggers an "Object is possibly 'undefined'" error. This causes@ts-expect-errorto swallow that error and pass the test regardless of whether the property is actuallyreadonly.Adding a non-null assertion (
!) resolves the undefined error, ensuring the test correctly fails if thereadonlyconstraint 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
📒 Files selected for processing (4)
src/index.tssrc/types/context.tssrc/types/route-rules.tstest/unit/types.test-d.ts
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 previouslyNitroRouteRules. 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
RouteRulesinterface, exported fromh3, as the canonical extension point: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:
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?.swris a compile error rather thanunknown— 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-rulescan re-export h3's interface and Nitro's existingdeclare module "h3-rules"augmentation continues to reach it —NitroRouteRulesneeds no change.Test plan
test/unit/types.test-d.tscover that an augmentation merges into the interface used byevent.context, that undeclared keys stay an error, that matched rules are readonly, and that whole-object assignment still compiles.dist/types that a downstreamdeclare module "h3"resolves through the entry re-exports, so the extension point works for real consumers and not just in-repo.pnpm lintand the full suite pass.🤖 Generated with Claude Code
Summary by CodeRabbit