Skip to content

Add type-safe routing with RoutePaths and typed state#23

Merged
sv2dev merged 12 commits intomainfrom
feat/typesafe-routes
Mar 31, 2026
Merged

Add type-safe routing with RoutePaths and typed state#23
sv2dev merged 12 commits intomainfrom
feat/typesafe-routes

Conversation

@sv2dev
Copy link
Copy Markdown
Owner

@sv2dev sv2dev commented Mar 31, 2026

Summary

This PR adds comprehensive type safety for route navigation in esroute:

  • Type-safe paths: Routes are validated at compile time via RoutePaths<R> — typos in route paths are caught by TypeScript
  • Typed state: Route handlers can declare required state via NavOpts<S>, and navigation enforces that state is provided with the correct type
  • Null consistency: State defaults to null (matching the History API), and is non-nullable when explicitly typed
  • No string fallback: Removed the string overload from go() to prevent silent errors

Key Changes

  • routes.ts: Added HandlerFor<R, P>, StateOf<F>, and NeedsState<F> type utilities for extracting state types from route handlers
  • router.ts: Updated go() overloads to require state when route handlers declare it; refactored internal navigation to avoid deep type instantiation
  • nav-opts.ts: Changed default generic param from any to null; state is now always assigned (not optional)
  • Documentation: Expanded the "Typesafe navigation" section with examples of path and state validation
  • Version: Bumped to 0.12.0

Breaking Changes

  • string is no longer a valid overload for router.go() — use string[] or a valid path string
  • Routes that declare a typed state now require that state when navigating

- Add RawRoutes type constraint for route shape inference
- Add RoutePaths<R> utility type that walks route trees and emits valid path literals
  - Excludes guard ('?') keys
  - Handles wildcard ('*') segments as ${string}
  - Transparent virtual ('') routes
- Add S = any state type parameter to:
  - NavMeta<S> and StrictNavMeta<S>
  - NavOpts<S>
  - Resolve<T, S>
  - Routes<T, S>
  - Resolved<T, S>
  - Router<T, S, R>
  - RouterConf<T, S, R>
  - OnResolveListener<T, S>
- Add R extends RawRoutes type parameter to Router and RouterConf for routes shape inference
- Update createRouter to infer R from the passed routes via R & Routes<T, S> intersection
- Add typed go() overload accepting RoutePaths<R> for IDE autocomplete
- All changes are backward compatible with defaults
- Add typecheck script to workspace packages

Examples:
  const router = createRouter({
    routes: { foo: () => 'foo', bar: { baz: () => 'baz' } }
  });
  router.go('/foo');  // ✓ autocomplete
  router.go('/bar/baz');  // ✓ autocomplete
  router.go('/unknown');  // type error ✓

  interface AppState { userId?: string }
  const router = createRouter<string, AppState>({
    routes: { foo: ({ state }) => `hello ${state?.userId}` }
  });
  await router.go('/foo', { state: { userId: '123' } });  // ✓
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

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

Walkthrough

Adds a generic navigation-state parameter S across nav/route/router types, introduces RawRoutes and RoutePaths type utilities, adds per-package typecheck scripts and tsconfig.check.json files, and updates tests to use the new generics and stricter typings.

Changes

Cohort / File(s) Summary
npm scripts & check configs
packages/esroute-lit/package.json, packages/esroute/package.json, packages/esroute-lit/tsconfig.check.json, packages/esroute/tsconfig.check.json
Added typecheck npm scripts and package-level tsconfig.check.json files for no-emit TypeScript type checking and project references.
Navigation options generics
packages/esroute/src/nav-opts.ts
Made NavMeta, StrictNavMeta, and NavOpts generic over S (default any); updated state types, constructor overloads, and go to construct NavOpts<S> so nav state preserves S.
Route resolver generics
packages/esroute/src/route-resolver.ts
Generalized Resolved, RouteResolver, and resolve to carry S and use NavOpts<S> / Resolve<T, S> throughout; only type plumbing changed.
Route definitions & utilities
packages/esroute/src/routes.ts
Added exported RawRoutes and RoutePaths types; made Resolve and Routes generic over S and propagated NavOpts<S> in resolver signatures.
Router API generics & plumbing
packages/esroute/src/router.ts
Extended Router, RouterConf, OnResolveListener, createRouter, and internals with generics T, S, R extends RawRoutes; updated go() overloads/normalization to accept typed path forms and to construct NavOpts<S>.
Tests
packages/esroute-lit/src/render-routes-directive.spec.ts, packages/esroute/src/route-resolver.spec.ts, packages/esroute/src/router.spec.ts
Updated tests to use explicit generic types or any for handlers, made redirect-state increment nullish-safe, adjusted go() usages to typed forms, and added a type-safety test for router path typing. Minimal behavioral changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through types both near and far,
I threaded S into each routing bar,
RawRoutes and paths I balanced with care,
NavOpts now carry state typed and fair,
A rabbit cheers — the builds dance in the air 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add type-safe routing with RoutePaths and typed state' accurately summarizes the main changes: introducing RoutePaths for type-safe route paths and adding generic type parameter S for typed state management across the routing system.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 feat/typesafe-routes

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.

- Create tsconfig.check.json in each package to include **/*.ts files for full typecheck coverage
- Update typecheck script to use tsconfig.check.json instead of tsconfig.json
- Add depth limit to RoutePaths utility type to prevent infinite type recursion
- Fix type annotations in test files:
  - Explicitly type NavOpts<number> in route-resolver.spec.ts
  - Add explicit 'next' parameter types in router.spec.ts
  - Use 'any' types in render-routes-directive.spec.ts to avoid deep recursion
- Add typecheck script to both workspace packages

All 40 tests pass + complete typecheck coverage of test files
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/esroute/src/router.ts`:
- Around line 29-32: The go() overload that currently unions RoutePaths<R> with
PathOrHref widens typed paths back to plain string; update the overloads so that
RoutePaths<R> is not unioned with PathOrHref — keep the free-form string/array
escape hatch only on the overload that accepts a { href } / PathOrHref variant
and keep the strongly-typed overload strictly as RoutePaths<R> (or number /
StrictNavMeta<S> / (prev => NavMeta<S>)) so router.go("/not-a-route") no longer
type-checks; apply the same change to the other overloaded method signatures
referenced (the similar overload groups around the other declarations noted in
the review) by separating the typed RoutePaths<R> overload from the PathOrHref/{
href } overload.

In `@packages/esroute/src/routes.ts`:
- Around line 19-22: The index-route branch in RoutePaths currently emits a
trailing-slash string `${Prefix}/`, which mismatches runtime serialization that
strips empty segments; change the branch so an index key (`K extends ""`) yields
the canonical path without a trailing slash: return Prefix (but when Prefix is
the empty string emit "/" — i.e. use conditional logic like `Prefix extends "" ?
"/" : Prefix`) instead of `${Prefix}/` so paths like `/users` type-check
correctly; update the RoutePaths/RawRoutes branch that references `K`/`Prefix`
accordingly.
🪄 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: 20b3afe6-9b46-437f-8f4f-42385e6a7474

📥 Commits

Reviewing files that changed from the base of the PR and between 60c8d93 and 42138ab.

📒 Files selected for processing (6)
  • packages/esroute-lit/package.json
  • packages/esroute/package.json
  • packages/esroute/src/nav-opts.ts
  • packages/esroute/src/route-resolver.ts
  • packages/esroute/src/router.ts
  • packages/esroute/src/routes.ts

Sven Rogge added 3 commits March 31, 2026 19:51
- Refactor router.spec.ts to use properly typed router instance
- Extract routes to a separate const and type it with Routes<string>
- Use ReturnType<typeof createRouter<string, any, typeof routes>> to get properly inferred type
- Add 'type safety' test suite to demonstrate:
  - Valid typed paths work correctly
  - NavOpts objects are type-safe
  - String array paths still work
  - Router type inference from routes shape works
- Demonstrates that IDE autocomplete for router.go() now shows: "/" | "/foo" | "/bar" | "/fail"
- All 44 tests pass + complete typecheck coverage
- Remove PathOrHref (string | string[]) from go() signature in favour of
  explicit string[] for array paths and RoutePaths<R> for string paths
- Invalid paths like "/fo" are now type errors; only defined routes compile
- Fix router.spec.ts typo: go("/fo") → go("/foo")
- Fix router.spec.ts: go("/foo?a=b") → go("/foo", { search: { a: "b" } })
- Fix router.spec.ts: go("/baz") → go(["baz"]) for intentionally non-existent route
- Cast dynamic anchor href to RoutePaths<R> in link click handler
IsMaxDepth matched every depth level 1-10 instead of only the maximum,
causing the first nested route level to immediately widen to string.
Changed to only match exactly 10 x's so nesting works up to 10 levels.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/esroute/src/router.ts (1)

29-32: Route handlers still bypass the new path typing.

Only Router.go() is narrowed to RoutePaths<R>. Resolver-side redirects still come from NavOpts.go(path: PathOrHref) in packages/esroute/src/nav-opts.ts, where PathOrHref = string | string[] — untyped and broader than RoutePaths<R>. This means notFound and route handlers can still emit arbitrary strings, breaking end-to-end path narrowing. To enforce typed routing throughout, R needs to flow into NavOpts so go() can narrow its path parameter to RoutePaths<R>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.ts` around lines 29 - 32, The resolver-side
redirect API still accepts an untyped PathOrHref (string | string[]) via
NavOpts.go, so route handlers can emit arbitrary strings; make NavOpts generic
over R and change NavOpts.go(path: ...) to accept RoutePaths<R> (or
RoutePaths<R> | number when supporting numeric history navigation) instead of
PathOrHref, and update the PathOrHref alias/removals and all NavOpts
instantiations/creations so the generic R flows from Router (and any factory
functions that construct NavOpts) into NavOpts to enforce end-to-end path
narrowing for RoutePaths<R>.
packages/esroute/src/router.spec.ts (1)

8-17: Add type-only assertions for the type-safety suite with a concrete state type and edge cases.

The test suite only validates that valid flat routes compile and uses S = any instead of a concrete state type. Add assertions to catch regressions in:

  • Typed state parameter (replace any with a concrete type)
  • Invalid path literals that should fail type checking
  • Edge cases: "*", "?", and nested "" routes

Use @ts-expect-error or expectTypeOf to create type-only assertions that verify both valid and invalid paths at compile time.

Also applies to: 167-201

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.spec.ts` around lines 8 - 17, The test currently
uses a loose state type and only checks flat routes; update the type-safety
suite by replacing the generic state parameter S = any in the createRouter
instantiation with a concrete state type (e.g., { userId: string }) and add
type-only assertions around the routes object and createRouter result: use
expectTypeOf to assert valid paths like "" and "foo" resolve to string, add
`@ts-expect-error` comments for invalid path literals (e.g., "invalid", nested ""
like "nested/"), and add checks for edge cases "*" and "?" to ensure they either
accept or reject as expected; target the test helpers around the routes constant
and the createRouter<string, S, typeof routes> usage (and duplicate the same
assertions in the later suite region) so compile-time failures surface for
regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/esroute/src/router.spec.ts`:
- Around line 169-187: The tests call router.init() multiple times without
tearing down global listeners; ensure each initialized router is disposed by
adding a teardown that calls router.dispose() after those tests (e.g., add an
afterEach or explicitly call router.dispose() at the end of the spec blocks that
invoke router.init()) so the global popstate/click listeners installed by
router.init() are removed; reference the router.init() and router.dispose()
calls to locate where to add the cleanup.

---

Nitpick comments:
In `@packages/esroute/src/router.spec.ts`:
- Around line 8-17: The test currently uses a loose state type and only checks
flat routes; update the type-safety suite by replacing the generic state
parameter S = any in the createRouter instantiation with a concrete state type
(e.g., { userId: string }) and add type-only assertions around the routes object
and createRouter result: use expectTypeOf to assert valid paths like "" and
"foo" resolve to string, add `@ts-expect-error` comments for invalid path literals
(e.g., "invalid", nested "" like "nested/"), and add checks for edge cases "*"
and "?" to ensure they either accept or reject as expected; target the test
helpers around the routes constant and the createRouter<string, S, typeof
routes> usage (and duplicate the same assertions in the later suite region) so
compile-time failures surface for regressions.

In `@packages/esroute/src/router.ts`:
- Around line 29-32: The resolver-side redirect API still accepts an untyped
PathOrHref (string | string[]) via NavOpts.go, so route handlers can emit
arbitrary strings; make NavOpts generic over R and change NavOpts.go(path: ...)
to accept RoutePaths<R> (or RoutePaths<R> | number when supporting numeric
history navigation) instead of PathOrHref, and update the PathOrHref
alias/removals and all NavOpts instantiations/creations so the generic R flows
from Router (and any factory functions that construct NavOpts) into NavOpts to
enforce end-to-end path narrowing for RoutePaths<R>.
🪄 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: 464ae990-48c1-45b5-adeb-372678297066

📥 Commits

Reviewing files that changed from the base of the PR and between a48238e and 1734463.

📒 Files selected for processing (2)
  • packages/esroute/src/router.spec.ts
  • packages/esroute/src/router.ts

Comment on lines +169 to +187
router.init();
// These should all compile without type errors and accept valid paths
// The paths "/" | "/foo" | "/bar" | "/fail" are properly typed from the routes
await router.go("/foo");
await router.go("/bar");
});

it("should work with NavOpts objects", async () => {
router.init();
// Type-safe NavOpts construction with proper typing
const opts = new NavOpts("/foo");
await router.go(opts);
});

it("should work with string array paths", async () => {
router.init();
// String arrays should still work alongside typed string paths
await router.go(["foo"]);
await router.go(["bar"]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dispose the router in these init() cases.

Each init() installs global popstate/click listeners, and these tests never tear them down. Adding three more leaked routers here makes the file more order-dependent and can duplicate later event handling.

🧹 Suggested cleanup
afterEach(() => {
  router.dispose();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.spec.ts` around lines 169 - 187, The tests call
router.init() multiple times without tearing down global listeners; ensure each
initialized router is disposed by adding a teardown that calls router.dispose()
after those tests (e.g., add an afterEach or explicitly call router.dispose() at
the end of the spec blocks that invoke router.init()) so the global
popstate/click listeners installed by router.init() are removed; reference the
router.init() and router.dispose() calls to locate where to add the cleanup.

When a nested route has "" as a terminal resolver (index route), the path
for that route is the parent segment itself (e.g. "/x"), not with a trailing
slash ("/x/"). Only the root index route produces "/".
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/esroute/src/router.spec.ts (3)

22-22: Consider a simpler type alias for readability.

The ReturnType<typeof createRouter<string, any, typeof routes>> expression is verbose. Consider extracting to a type alias if this pattern is reused.

💡 Optional simplification
+type TestRouter = ReturnType<typeof createRouter<string, any, typeof routes>>;
+
 describe("Router", () => {
   let onResolve: Mock;
-  let router: ReturnType<typeof createRouter<string, any, typeof routes>>;
+  let router: TestRouter;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.spec.ts` at line 22, The variable declaration
using ReturnType<typeof createRouter<string, any, typeof routes>> is verbose;
create a small type alias (e.g., RouterType) capturing ReturnType<typeof
createRouter<string, any, typeof routes>> and then declare router as let router:
RouterType; update any other occurrences that repeat this pattern; reference the
createRouter function and the routes symbol to ensure the alias matches the same
generic instantiation.

114-127: Test uses non-existent route "baz".

router.go(["baz"]) navigates to a route that doesn't exist in the defined routes object. While this may work for testing deferred rendering behavior, using a valid route like ["foo"] or ["bar"] would be clearer and avoid relying on undefined behavior.

💡 Suggested fix
     it("should render only once, if render is called with defer function", async () => {
       await router.render(async () => {
-        await router.go(["baz"]);
+        await router.go(["foo"]);
         await router.go(-1);
-        await router.go("/foo");
+        await router.go("/bar");
       });

       expect(history.pushState).toHaveBeenCalledTimes(2);
       expect(history.go).toHaveBeenCalledTimes(1);
       expect(onResolve).toHaveBeenCalledWith({
-        opts: new NavOpts("/foo", { pop: false }),
-        value: "foo",
+        opts: new NavOpts("/bar", { pop: false }),
+        value: "bar",
       });
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.spec.ts` around lines 114 - 127, The test calls
router.go(["baz"]) but "baz" isn't a defined route; change that call to a valid
route (e.g., router.go(["bar"])) so the sequence inside router.render uses
existing routes. Update the call to router.go(["bar"]) in the test that invokes
router.render(async () => { ... }) so history.pushState, history.go, onResolve
and NavOpts("/foo", ...) assertions still exercise deferred rendering behavior
with valid routes.

195-206: Type safety assertion is incomplete and doesn't validate at compile time.

The manually defined ValidPaths type is incomplete—it's missing /x and /x/y from the routes. More importantly, this test doesn't actually enforce type safety at compile time; it just checks that router is defined at runtime.

Consider using a compile-time assertion that derives the paths from the routes:

♻️ Proposed fix to derive paths from routes
     it("should infer router types from routes", () => {
-      // Verify that router is properly typed with inferred routes shape
-      // The router type is: Router<string, any, typeof routes>
-      // which means router.go() provides autocomplete for: "/" | "/foo" | "/bar" | "/fail"
-
-      // Demonstrate that valid paths are properly typed
-      type ValidPaths = "/" | "/foo" | "/bar" | "/fail";
-
-      // Each valid route is properly typed and provides IDE autocomplete
-      const router_is_typed: typeof router = router;
-      expect(router_is_typed).toBeDefined();
+      // Compile-time assertion: derive valid paths from routes
+      type ActualPaths = Parameters<typeof router.go>[0];
+      // If RoutePaths is working, this should be: "/" | "/foo" | "/bar" | "/fail" | "/x" | "/x/y" | number | string[] | ...
+      
+      // Runtime sanity check
+      expect(router).toBeDefined();
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.spec.ts` around lines 195 - 206, The test
currently uses a manually defined ValidPaths and only runtime checks
router_is_typed; replace that with a compile-time assertion that derives the
path union from the actual routes and ensures it matches the expected set — e.g.
create a derived type from the existing routes value (referencing routes and
router) and add a TypeScript-only equality/assertion (using a small helper type
like AssertEqual or by assigning to a variable with the derived type) so `/x`
and `/x/y` are included and the compiler will fail if the inferred paths differ
from the expected union instead of relying on a runtime expect.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/esroute/src/router.spec.ts`:
- Line 5: The import of the symbol "y" from "happy-dom/lib/PropertySymbol" in
router.spec.ts is unused; remove the unused import line (the import statement
that references "y") so the file no longer imports "y" and update any nearby
imports if needed to keep formatting consistent.

---

Nitpick comments:
In `@packages/esroute/src/router.spec.ts`:
- Line 22: The variable declaration using ReturnType<typeof createRouter<string,
any, typeof routes>> is verbose; create a small type alias (e.g., RouterType)
capturing ReturnType<typeof createRouter<string, any, typeof routes>> and then
declare router as let router: RouterType; update any other occurrences that
repeat this pattern; reference the createRouter function and the routes symbol
to ensure the alias matches the same generic instantiation.
- Around line 114-127: The test calls router.go(["baz"]) but "baz" isn't a
defined route; change that call to a valid route (e.g., router.go(["bar"])) so
the sequence inside router.render uses existing routes. Update the call to
router.go(["bar"]) in the test that invokes router.render(async () => { ... })
so history.pushState, history.go, onResolve and NavOpts("/foo", ...) assertions
still exercise deferred rendering behavior with valid routes.
- Around line 195-206: The test currently uses a manually defined ValidPaths and
only runtime checks router_is_typed; replace that with a compile-time assertion
that derives the path union from the actual routes and ensures it matches the
expected set — e.g. create a derived type from the existing routes value
(referencing routes and router) and add a TypeScript-only equality/assertion
(using a small helper type like AssertEqual or by assigning to a variable with
the derived type) so `/x` and `/x/y` are included and the compiler will fail if
the inferred paths differ from the expected union instead of relying on a
runtime expect.
🪄 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: 95f013ee-adb2-4e3e-9591-44f6d02b96ff

📥 Commits

Reviewing files that changed from the base of the PR and between 1734463 and 1e4d182.

📒 Files selected for processing (2)
  • packages/esroute/src/router.spec.ts
  • packages/esroute/src/routes.ts

import { Router, createRouter } from "./router";
import { Routes } from "./routes";
import { createRouter } from "./router";
import { y } from "happy-dom/lib/PropertySymbol";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The y symbol from happy-dom/lib/PropertySymbol is imported but never used in this file. This appears to be an accidental import that should be removed.

🧹 Proposed fix
-import { y } from "happy-dom/lib/PropertySymbol";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { y } from "happy-dom/lib/PropertySymbol";
// Line 5 is removed - the import statement is deleted
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/esroute/src/router.spec.ts` at line 5, The import of the symbol "y"
from "happy-dom/lib/PropertySymbol" in router.spec.ts is unused; remove the
unused import line (the import statement that references "y") so the file no
longer imports "y" and update any nearby imports if needed to keep formatting
consistent.

Sven Rogge added 6 commits March 31, 2026 22:29
Add type-safe navigation by introducing HandlerFor, StateOf, and NeedsState
type utilities that infer required state from route handler signatures. The
go() method now requires state when the route handler declares a concrete
state type, preventing runtime errors from missing state parameters.

Also refactor navigation options handling and update tests to verify state
type enforcement works correctly with nested routes.
State is now a non-optional property with definite assignment
assertion. Update tests to pass properly typed state and remove
optional chaining in assertions.
Change state initialization to always use null as the default value instead of leaving it undefined. This provides a consistent, falsy value for state when not explicitly provided, making the state field definitively typed as `S | null` rather than optional. Update test expectations accordingly.
Change the default generic parameter of NavOpts from `S = any` to `S = null`,
and make the state property non-nullable with proper type casting. Update
related code to use explicit type parameters for NavOpts and remove unnecessary
non-null assertions. Also update NeedsState type guard to check for both null
and undefined, fixing type inference for optional state parameters.
@sv2dev sv2dev merged commit 1706d47 into main Mar 31, 2026
2 checks passed
@sv2dev sv2dev deleted the feat/typesafe-routes branch March 31, 2026 22:07
@sv2dev sv2dev linked an issue Mar 31, 2026 that may be closed by this pull request
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.

Route states should be typed

1 participant