Last active
November 20, 2025 20:44
-
-
Save markmals/d4ffd9dcc410f4244af64e604beda798 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { RoutePattern } from "@remix-run/route-pattern"; | |
| // MARK: Types | |
| interface RouteMap { | |
| [key: string]: Route<any> | RouteMap; | |
| } | |
| interface Route<Pattern extends string> extends RoutePattern<Pattern> { | |
| children?: RouteMap; | |
| } | |
| declare function route<Pattern extends string>(pattern: Pattern): Route<Pattern>; | |
| declare function index(): Route<"/">; | |
| declare const LAYOUT: unique symbol; | |
| // Pathless layout component | |
| declare function layout<Children extends RouteMap | undefined>( | |
| children: Children | |
| ): Children & { [LAYOUT]: true }; | |
| // Split "path?search" into path/search pieces | |
| type PathPart<S extends string> = S extends `${infer P}?${string}` ? P : S; | |
| type SearchPart<S extends string> = S extends `${string}?${infer Q}` ? `?${Q}` : ""; | |
| // Join two paths, avoiding "//" | |
| type JoinPaths<A extends string, B extends string> = A extends `${infer A2}/` | |
| ? B extends `/${infer B2}` | |
| ? `${A2}/${B2}` | |
| : `${A}${B}` | |
| : `${A}${B}`; | |
| // Compose two full patterns, pushing all "?..." to the end | |
| type JoinPattern<Base extends string, Child extends string> = `${JoinPaths< | |
| PathPart<Base>, | |
| PathPart<Child> | |
| >}${SearchPart<Base>}${SearchPart<Child>}`; | |
| type RouteObject = Record<string, any>; | |
| // Rewrite every Route<P> inside T as Route<JoinPattern<Base, P>> | |
| // and recursively descend into plain objects. | |
| type WithBase<Base extends string, T> = T extends Route<infer P> | |
| ? Route<JoinPattern<Base, P>> | |
| : T extends RouteObject | |
| ? { [K in keyof T]: WithBase<Base, T[K]> } | |
| : T; | |
| declare function prefix<Base extends string, const Children extends Record<string, any>>( | |
| pattern: Base, | |
| children: Children | |
| ): WithBase<Base, Children>; | |
| declare function route<Base extends string, const Children extends Record<string, any>>( | |
| base: Base, | |
| children: Children | |
| ): Route<Base> & WithBase<Base, Children>; | |
| export const ResourceMethods = ["new", "show", "edit"] as const; | |
| export type ResourceMethod = (typeof ResourceMethods)[number]; | |
| export interface ResourceOptions { | |
| /** | |
| * The resource methods to include in the route map. If not provided, all | |
| * methods (`show`, `new`, and `edit`) will be | |
| * included. | |
| */ | |
| only?: ResourceMethod[]; | |
| /** | |
| * Custom names to use for the resource routes. | |
| */ | |
| names?: { | |
| new?: string; | |
| show?: string; | |
| edit?: string; | |
| }; | |
| } | |
| type ResourceMethodsFor<Options extends ResourceOptions | undefined> = Options extends { | |
| only: readonly (infer M)[]; | |
| } | |
| ? M & ResourceMethod | |
| : ResourceMethod; | |
| type ResourceKeyFor< | |
| M extends ResourceMethod, | |
| Options extends ResourceOptions | undefined | |
| > = Options extends { names: infer N } | |
| ? N extends Record<string, string> | |
| ? M extends keyof N | |
| ? N[M] & string | |
| : M | |
| : M | |
| : M; | |
| type ResourceRouteFor<Base extends string, M extends ResourceMethod> = M extends "new" | |
| ? Route<`${Base}/new`> | |
| : M extends "show" | |
| ? Route<Base> | |
| : M extends "edit" | |
| ? Route<`${Base}/edit`> | |
| : never; | |
| type ResourceMap<Base extends string, Options extends ResourceOptions | undefined> = { | |
| [M in ResourceMethodsFor<Options> as ResourceKeyFor<M, Options>]: ResourceRouteFor<Base, M>; | |
| }; | |
| declare function resource<Base extends string, const Options extends ResourceOptions>( | |
| base: Base | RoutePattern<Base>, | |
| options?: Options | |
| ): ResourceMap<Base, Options>; | |
| export const ResourcesMethods = ["index", "new", "show", "edit"] as const; | |
| export type ResourcesMethod = (typeof ResourcesMethods)[number]; | |
| export type ResourcesOptions = { | |
| /** | |
| * The resource methods to include in the route map. If not provided, all | |
| * methods (`index`, `show`, `new`, `edit`) | |
| * will be included. | |
| */ | |
| only?: ResourcesMethod[]; | |
| /** | |
| * The parameter name to use for the resource. Defaults to `id`. | |
| */ | |
| param?: string; | |
| /** | |
| * Custom names to use for the resource routes. | |
| */ | |
| names?: { | |
| index?: string; | |
| new?: string; | |
| show?: string; | |
| edit?: string; | |
| }; | |
| }; | |
| type ResourcesMethodsFor<Options extends ResourcesOptions | undefined> = Options extends { | |
| only: readonly (infer M)[]; | |
| } | |
| ? M & ResourcesMethod | |
| : ResourcesMethod; | |
| type ResourcesKeyFor< | |
| M extends ResourcesMethod, | |
| Options extends ResourcesOptions | undefined | |
| > = Options extends { names: infer N } | |
| ? N extends Record<string, string> | |
| ? M extends keyof N | |
| ? N[M] & string | |
| : M | |
| : M | |
| : M; | |
| type ResourcesParamFor<Options extends ResourcesOptions | undefined> = Options extends { | |
| param: infer P extends string; | |
| } | |
| ? P | |
| : "id"; | |
| type ResourcesRouteFor< | |
| Base extends string, | |
| Param extends string, | |
| M extends ResourcesMethod | |
| > = M extends "index" | |
| ? Route<Base> | |
| : M extends "new" | |
| ? Route<`${Base}/new`> | |
| : M extends "show" | |
| ? Route<`${Base}/:${Param}`> | |
| : M extends "edit" | |
| ? Route<`${Base}/:${Param}/edit`> | |
| : never; | |
| type ResourcesMap<Base extends string, Options extends ResourcesOptions | undefined> = { | |
| [M in ResourcesMethodsFor<Options> as ResourcesKeyFor<M, Options>]: ResourcesRouteFor< | |
| Base, | |
| ResourcesParamFor<Options>, | |
| M | |
| >; | |
| }; | |
| declare function resources<Base extends string, const Options extends ResourcesOptions>( | |
| base: Base | RoutePattern<Base>, | |
| options?: Options | |
| ): ResourcesMap<Base, Options>; | |
| // MARK: Example | |
| let routes = route("/?q", { | |
| index: index(), | |
| contacts: prefix("/contacts", { | |
| show: route("/:contactId"), | |
| edit: route("/:contactId/edit"), | |
| }), | |
| }); | |
| // Outputs the types: | |
| // | |
| // let routes: Route<"/?q"> & { | |
| // readonly index: Route<"/?q">; | |
| // readonly contacts: { | |
| // readonly show: Route<"/contacts/:contactId?q">; | |
| // readonly edit: Route<"/contacts/:contactId/edit?q">; | |
| // }; | |
| // }; | |
| routes.href(null, { q: "Mark" }); | |
| routes.index.href(); | |
| routes.contacts.show.href({ contactId: "1" }); | |
| routes.contacts.edit.href({ contactId: "1" }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment