Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typesafe routes and endpoints #3090

Closed
ivanhofer opened this issue Dec 22, 2021 · 4 comments
Closed

typesafe routes and endpoints #3090

ivanhofer opened this issue Dec 22, 2021 · 4 comments

Comments

@ivanhofer
Copy link
Contributor

Describe the problem

In larger projects (also in smaller projects) it would be great if the goto and the fetch functions could offer more typesafety. Problems with missing typesafety are:

  • typos when you link to a route that does't exist e.g. /costumer instead of /customer
  • help for refactorings e.g. when renaming larger chunks of files inside the routes folder, all links need to be updated too
  • trying to call fetch with the wrong method e.g. using PUT instead of PATCH

It would be great if the goto and the fetch functions could output an error when you pass in a invalid relative slug.

Describe the proposed solution

The problem could be solved by providing advanced TypeScript types for the goto and the fetch function.
Similar tho the already generated .svelte-kit/dev/generated/manifest.js file, SvelteKit could generate a d.ts with types depending on the .svelte files inside the routes folder and depending on the function inside a .js and .ts Endpoints file.

These types then could be used to enhance the goto and fetch functions.
The typesafe functions could replace the existing import from app/navigation. I'm not sure how this could work for the fetch function since you don't really import it from anywhere.
Or this could be an additional function you need to import from app/typesafe or something similar.

Here is a working example how I think this could look like:

  • helper-types: for cheching if a slug is valid
type SplitPath<S extends string> =
	S extends `/${infer Part}/${infer Rest}`
		? ['/', Part, '/', ...SplitPath<Rest>]
		: S extends `${infer Part}/${infer Rest}`
			? [Part, '/', ...SplitPath<Rest>]
			: S extends `/${infer Part}`
				? ['/', Part]
				: S extends ''
					? []
					: S extends `${infer Part}`
						? [Part]
						: []

type RemoveEmptyEntries<A extends Array<unknown>> =
	A extends []
		? []
		: A extends [infer Item, ...infer Rest]
			? Item extends ''
				? RemoveEmptyEntries<Rest>
				: [Item, ...RemoveEmptyEntries<Rest>]
			: []
  • routes: for the goto function
// alias type to get better TypeScript hints inside tooltips
type id = string

// this type is dynamic and get's generated
type Routes =
	| ['/'] // index.svelte
	| ['/', 'about'] // about/index.svelte
	| ['/', 'products'] // products/index.svelte
	| ['/', 'products', '/', 'create'] // products/index.svelte
	| ['/', 'products', '/', id] // products/[id]/index.svelte
	| ['/', 'products', '/', id, '/', 'edit'] // products/[id]/edit.svelte

export type IsValidRoute<R extends string> =
	R extends `http${string}`
		? R
		: RemoveEmptyEntries<SplitPath<R>> extends Routes
			? R
			: 'No such Route'

const goto = <Route extends string>(href: IsValidRoute<Route>): void => {
	// TODO: goto href
}

// @ts-expect-error
goto('')
goto('/')
goto('/about')
// @ts-expect-error
goto('/invalid')
// @ts-expect-error
goto('/product')
goto('/products')
// @ts-expect-error
goto('/products/')
// @ts-expect-error
goto('/products/create/')
goto('/products/123456')
// @ts-expect-error
goto('/products/123456/')
// @ts-expect-error
goto('/products/123456/add')
goto('/products/123456/edit')
// @ts-expect-error
goto('/products/123456/5678/edit')

goto('https://kit.svelte.dev')
  • endpoints: for the fetch function
type Methods =
	| 'GET'
	| 'POST'
	| 'PUT'
	| 'PATCH'
	| 'DELETE'

// this type is dynamic and get's generated
type Endpoints = {
	GET:
	| ['/', 'products']
	| ['/', 'products', '/', id]
	POST:
	| ['/', 'products']
	PATCH:
	| ['/', 'products', '/', id]
}

export type IsValidEndpoint<M extends Methods, R extends string> =
	R extends `http${string}`
		? R
		: M extends keyof Endpoints
			? RemoveEmptyEntries<SplitPath<R>> extends Endpoints[M]
				? R
				: 'No such Endpoint'
			: 'No such Method'

const fetch = <Endpoint extends string, Method extends Methods = 'GET'>(endpoint: IsValidEndpoint<Method, Endpoint>, options?: { method?: Method, [key: string]: any }): void => {
	// TODO: call fetch
}

fetch('/products')
// @ts-expect-error
fetch('products')
fetch('/products/12345')
fetch('/products', { method: 'POST' })
// @ts-expect-error
fetch('/products', { method: 'PATCH' })
// @ts-expect-error
fetch('/products/12345', { method: 'POST' })
fetch('/products/12345', { method: 'PATCH' })

fetch('http://example.com/articles')

You can copy these examples to a .ts file and try passing some valid/invalid strings to the goto and fetch functions.
Lines annotated with // @ts-expect-error are invalid and will throw a TypeScript Error.

Alternatives considered

Don't offer typesafe routes and api-endpoints.

Importance

would make my life easier

Additional Information

If adding this feature is considered, I'm happy to help creating a PR.

@ebeloded
Copy link

Great idea. These types can also be used for invalidate, prefetch, prefetchRoutes functions. Basically everything from $app/navigation

It would be even better if such route checking was done for the links in templates, not only for the goto function.

@dummdidumm
Copy link
Member

Sounds like a duplicate of #647 . Would you mind closing this and post your ideas there?

@ivanhofer
Copy link
Contributor Author

@dummdidumm sure, I haven't come accross this issue when I looked for similar issues. I'll add a comment.

@dummdidumm
Copy link
Member

Thanks! Closing this one then. I like your proposal btw!

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

No branches or pull requests

3 participants