Skip to content

Instantly share code, notes, and snippets.

@gyzerok
Forked from david-plugge/route.ts
Created October 2, 2024 06:00
Show Gist options
  • Save gyzerok/9efe0b5fc0a2f8cc30b6f0a50ea0f33c to your computer and use it in GitHub Desktop.
Save gyzerok/9efe0b5fc0a2f8cc30b6f0a50ea0f33c to your computer and use it in GitHub Desktop.
Sveltekit typesafe routes
import { resolveRoute } from '$app/paths';
import type RouteMetadata from '$lib/../../.svelte-kit/types/route_meta_data.json';
type RouteMetadata = typeof RouteMetadata;
// eslint-disable-next-line @typescript-eslint/ban-types
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type ParseParam<T extends string> = T extends `...${infer Name}` ? Name : T;
type ParseParams<T extends string> = T extends `${infer A}[[${infer Param}]]${infer B}`
? ParseParams<A> & { [K in ParseParam<Param>]?: string } & ParseParams<B>
: T extends `${infer A}[${infer Param}]${infer B}`
? ParseParams<A> & { [K in ParseParam<Param>]: string } & ParseParams<B>
: // eslint-disable-next-line @typescript-eslint/ban-types
{};
type RequiredKeys<T extends object> = keyof {
// eslint-disable-next-line @typescript-eslint/ban-types
[P in keyof T as {} extends Pick<T, P> ? never : P]: 1;
};
type RemoveGroups<T> = T extends `${infer A}/(${string})${infer B}` ? `${A}${RemoveGroups<B>}` : T;
export type RouteId = RemoveGroups<keyof RouteMetadata>;
// export type RouteId = keyof RouteMetadata;
export type Routes = {
[K in RouteId]: Prettify<ParseParams<K>>;
};
type OptionalOptions<T extends RouteId> = {
query?: string | Record<string, string> | URLSearchParams | string[][];
hash?: string;
params?: Routes[T];
};
type RequiredOptions<T extends RouteId> = {
query?: string | Record<string, string> | URLSearchParams | string[][];
hash?: string;
params: Routes[T];
};
type RouteArgs<T extends RouteId> =
RequiredKeys<Routes[T]> extends never
? [options?: OptionalOptions<T>]
: [options: RequiredOptions<T>];
export function route<T extends RouteId>(routeId: T, ...[options]: RouteArgs<T>) {
const path = resolveRoute(routeId, options?.params ?? {});
const search = options?.query && new URLSearchParams(options.query).toString();
return path + (search ? `?${search}` : '') + (options?.hash ? `#${options.hash}` : '');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment