kit
kit copied to clipboard
Generated route param types should include param matchers guard checks
Describe the problem
Typed route param types do not match ones provided from params/* match functions.
Consider following setup
// params/fruit.ts
import type { ParamMatcher } from '@sveltejs/kit'
const fruits = ['apple', 'banana', 'cherry'] as const
type Fruit = typeof fruits[number]
export const match = ((param): param is Fruit =>
fruits.includes(param as Fruit)) satisfies ParamMatcher
Match function is strictly typed using type guard, so typescript knows that function only returns true when param is a fruit. Now trying to access it inside page using it gives you incorrect type
// routes/[slug=fruit]/+page.svelte
<script lang="ts">
import { page } from '$app/stores'
let slug = $page.params.slug // This will be typed as 'string' | 'undefined'
</script>
<h1>Fruit</h1>
<div>{slug}</div>
Describe the proposed solution
Generated route $types.d.ts should take into account match function guard checks, so instead of generated output of
type RouteParams = { slug: string }
it should be
type RouteParams = { slug: 'apple' | 'banana' | 'cherry' }
if using guard types it could even exclude undefined from final type since it would never match it too.
Alternatives considered
No response
Importance
nice to have
Additional Information
No response
I had the same thought the other day — this would be a cool addition
We can probably infer something for the load functions, but for the page store we can't because we can't scope-type it. You would need to do that manually, or rather we would need to provide a App.Params interface which you can type yourself in app.d.ts
@dummdidumm what if page was imported from something like ./$stores? Would that work?
import { page } from "./$stores";
$page.params.slug // string
We can infer which values would pass the match function using the following generic:
const fruits = ['apple', 'banana', 'cherry'] as const
type Fruit = typeof fruits[number]
export const match = (param : string) : param is Fruit => fruits.includes(param as Fruit)
//If there is a typeguard specified, use that
//otherwise fallback to string
type ValidParams<T> = T extends (param: string) => param is (infer U) ? U : string;
// ValidParams<typeof match> = 'apple' | 'banana' | 'cherry' - Gets inferred from typeguard
I actually get a red squiggly on the infer U bit in my IDE, but it still works so 🙃