kit icon indicating copy to clipboard operation
kit copied to clipboard

Generated route param types should include param matchers guard checks

Open minht11 opened this issue 2 years ago • 2 comments

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

minht11 avatar Dec 13 '22 21:12 minht11

I had the same thought the other day — this would be a cool addition

Rich-Harris avatar Dec 15 '22 23:12 Rich-Harris

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 avatar Dec 16 '22 08:12 dummdidumm

@dummdidumm what if page was imported from something like ./$stores? Would that work?

import { page } from "./$stores";
$page.params.slug // string

olehmisar avatar Aug 08 '23 18:08 olehmisar

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 🙃

LorisSigrist avatar Sep 19 '23 13:09 LorisSigrist