UNPKG

next-safe-navigation

Version:
152 lines (147 loc) 8.83 kB
import { z } from 'zod'; import { Route } from 'next'; type Prettify<T> = { [K in keyof T]: T[K]; } & {}; type ExcludeAny<T> = unknown extends T ? never : T; type PathBlueprint = `/${string}`; type Suffix = `?${string}`; /** * When `experimental.typeRoutes` is disabled, * `Route` is `string & {}`, therefore `string extends Route` is a truthy condition. * If this is the case, we simply use the `Path` value to infer the literal string. * * If `experimental.typeRoutes` is enabled, * `Route` will be a union of string literals, therefore `string extends Route` is a falsy condition. * If this is the case, we use `Route<Path>` so that we have auto-complete on the available routes * generated by NextJS and validation check against dynamic routes (that are checked by passing the string generic). */ type SafePath<Path extends string> = string extends Route ? Path : Route<Path>; type ExtractPathParams<T extends string> = T extends `${infer Rest}[[...${infer Param}]]` ? Param | ExtractPathParams<Rest> : T extends `${infer Rest}[...${infer Param}]` ? Param | ExtractPathParams<Rest> : T extends `${string}[${infer Param}]${infer Rest}` ? Param | ExtractPathParams<Rest> : never; type RouteBuilder<Path extends string, Params extends z.ZodSchema, Search extends z.ZodSchema> = [ Params, Search ] extends [never, never] ? { (): Path; getSchemas: () => { params: never; search: never; }; } : [Params, Search] extends [z.ZodSchema, never] ? { (options: z.input<Params>): Path; getSchemas: () => { params: Params; search: never; }; } : [Params, Search] extends [never, z.ZodSchema] ? undefined extends z.input<Search> ? { (options?: { search?: z.input<Search>; }): Path | `${Path}${Suffix}`; getSchemas: () => { params: never; search: Search; }; } : { (options: { search: z.input<Search>; }): `${Path}${Suffix}`; getSchemas: () => { params: never; search: Search; }; } : [Params, Search] extends [z.ZodSchema, z.ZodSchema] ? undefined extends z.input<Search> ? { (options: z.input<Params> & { search?: z.input<Search>; }): Path | `${Path}${Suffix}`; getSchemas: () => { params: Params; search: Search; }; } : { (options: z.input<Params> & { search: z.input<Search>; }): `${Path}${Suffix}`; getSchemas: () => { params: Params; search: Search; }; } : never; type EnsurePathWithNoParams<Path extends string> = ExtractPathParams<Path> extends never ? SafePath<Path> : `[ERROR]: Missing validation for path params`; /** * Ensures no extra values are passed to params validation */ type StrictParams<Schema extends z.ZodSchema, Keys extends string> = Schema extends z.ZodObject<infer Params> ? [ keyof Params ] extends [Keys] ? Schema : z.ZodObject<{ [Key in keyof Params]: Key extends Keys ? Params[Key] : never; }> : never; type RouteBuilderResult<Path extends string, PathParams extends string, Params extends z.ZodObject<any>, Search extends z.ZodSchema> = [ PathParams, Search ] extends [string, never] ? RouteBuilder<Path, Params, never> : [PathParams, Search] extends [never, z.ZodSchema] ? RouteBuilder<Path, never, Search> : [PathParams, Search] extends [string, z.ZodSchema] ? RouteBuilder<Path, Params, Search> : never; declare function makeRouteBuilder<Path extends PathBlueprint>(path: EnsurePathWithNoParams<Path>): RouteBuilder<Path, never, never>; declare function makeRouteBuilder<Path extends PathBlueprint, Params extends z.ZodObject<{ [K in ExtractPathParams<Path>]: z.ZodSchema; }>, Search extends z.ZodSchema = never>(path: SafePath<Path>, schemas: ExtractPathParams<Path> extends never ? { search: Search | z.ZodOptional<z.ZodSchema>; } : { params: StrictParams<Params, ExtractPathParams<Path>>; search?: Search | z.ZodOptional<z.ZodSchema>; }): RouteBuilderResult<Path, ExtractPathParams<Path>, ExcludeAny<Params>, ExcludeAny<Search>>; type makeRouteBuilder = typeof makeRouteBuilder; type AnyRouteBuilder = RouteBuilder<string, any, any> | RouteBuilder<string, any, never> | RouteBuilder<string, never, any> | RouteBuilder<string, never, never>; type NavigationConfig = Record<string, AnyRouteBuilder>; type SafeRootRoute<Path extends string> = () => Path; type SafeRouteWithParams<Path extends string, Params extends z.ZodSchema> = { (options: z.input<Params>): Path; $parseParams: (params: unknown) => z.output<Params>; }; type SafeRouteWithSearch<Path extends string, Search extends z.ZodSchema> = { (options?: { search?: z.input<Search>; }): Path; $parseSearchParams: (searchParams: unknown) => z.output<Search>; }; type SafeRouteWithRequiredSearch<Path extends string, Search extends z.ZodSchema> = { (options: { search: z.input<Search>; }): Path; $parseSearchParams: (searchParams: unknown) => z.output<Search>; }; type SafeRouteWithParamsAndSearch<Path extends string, Params extends z.ZodSchema, Search extends z.ZodSchema, Options = z.input<Params> & { search?: z.input<Search>; }> = { (options: Prettify<Options>): Path; $parseParams: (params: unknown) => z.output<Params>; $parseSearchParams: (searchParams: unknown) => z.output<Search>; }; type SafeRouteWithParamsAndRequiredSearch<Path extends string, Params extends z.ZodSchema, Search extends z.ZodSchema, Options = z.input<Params> & { search: z.input<Search>; }> = { (options: Prettify<Options>): Path; $parseParams: (params: unknown) => z.output<Params>; $parseSearchParams: (searchParams: unknown) => z.output<Search>; }; type SafeRoute<Path extends string, Params extends z.ZodSchema, Search extends z.ZodSchema> = [ Params, Search ] extends [never, never] ? SafeRootRoute<Path> : [Params, Search] extends [z.ZodSchema, never] ? SafeRouteWithParams<Path, Params> : [Params, Search] extends [never, z.ZodSchema] ? undefined extends z.input<Search> ? SafeRouteWithSearch<Path, Search> : SafeRouteWithRequiredSearch<Path, Search> : [Params, Search] extends [z.ZodSchema, z.ZodSchema] ? undefined extends z.input<Search> ? SafeRouteWithParamsAndSearch<Path, Params, Search> : SafeRouteWithParamsAndRequiredSearch<Path, Params, Search> : never; type RouteWithParams<Config extends NavigationConfig> = { [Route in keyof Config & string]: Config[Route] extends (RouteBuilder<string, infer Params extends z.ZodSchema, infer _>) ? Params extends z.ZodSchema ? Route : never : never; }[keyof Config & string]; type RouteWithSearchParams<Config extends NavigationConfig> = { [Route in keyof Config & string]: Config[Route] extends (RouteBuilder<string, infer _ extends z.ZodSchema, infer Search extends z.ZodSchema>) ? Search extends z.ZodSchema ? Route : never : never; }[keyof Config & string]; type SafeNavigation<Config extends NavigationConfig> = { [Route in keyof Config]: Config[Route] extends (RouteBuilder<infer Path extends string, infer Params extends z.ZodSchema, infer Search extends z.ZodSchema>) ? SafeRoute<Path, Params, Search> : never; }; type ValidatedRouteParams<Config extends NavigationConfig, Route extends string, AcceptableRoute extends string, Router = SafeNavigation<Config>> = Route extends keyof Pick<Router, AcceptableRoute & keyof Router> ? Router[Route] extends (SafeRoute<string, infer Params extends z.ZodSchema, any> | SafeRoute<string, infer Params extends z.ZodSchema, never>) ? z.output<Params> : never : never; type ValidatedRouteSearchParams<Config extends NavigationConfig, Route extends string, AcceptableRoute extends string, Router = SafeNavigation<Config>> = Route extends keyof Pick<Router, AcceptableRoute & keyof Router> ? Router[Route] extends (SafeRoute<string, any, infer Search extends z.ZodSchema> | SafeRoute<string, never, infer Search extends z.ZodSchema>) ? z.output<Search> : never : never; interface SafeNavigationConfigImpl<Config extends NavigationConfig, $SafeRouter extends SafeNavigation<any> = SafeNavigation<Config>, $RouteWithParams extends string = RouteWithParams<Config>, $RouteWithSearchParams extends string = RouteWithSearchParams<Config>> { routes: $SafeRouter; useSafeParams: <Route extends keyof $SafeRouter & string>(route: Extract<$RouteWithParams, Route>) => ValidatedRouteParams<Config, Route, $RouteWithParams>; useSafeSearchParams: <Route extends keyof $SafeRouter & string>(route: Extract<$RouteWithSearchParams, Route>) => ValidatedRouteSearchParams<Config, Route, $RouteWithSearchParams>; } type SafeNavigationConfig<Config extends NavigationConfig> = SafeNavigationConfigImpl<Config>; declare function createNavigationConfig<Config extends NavigationConfig>(createConfig: (defineRoute: makeRouteBuilder) => Config): SafeNavigationConfig<Config>; export { createNavigationConfig };