Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions packages/fetch-router/src/lib/route-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ import type { RequestMethod } from './request-methods.ts'
import type { Route, RouteMap } from './route-map.ts'

// prettier-ignore
export type RouteHandlers<T extends RouteMap> =
| RouteHandlersWithMiddleware<T>
export type RouteHandlers<routeMap extends RouteMap> =
| RouteHandlersWithMiddleware<routeMap>
| {
[K in keyof T]: (
T[K] extends Route<infer M, infer P> ? RouteHandler<M, P> :
T[K] extends RouteMap ? RouteHandlers<T[K]> :
[name in keyof routeMap]: (
routeMap[name] extends Route<infer method, infer pattern> ? RouteHandler<method, pattern> :
routeMap[name] extends RouteMap ? RouteHandlers<routeMap[name]> :
never
)
}
Comment on lines +9 to 17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export type RouteHandlers<routeMap extends RouteMap> =
| RouteHandlersWithMiddleware<routeMap>
| {
[K in keyof T]: (
T[K] extends Route<infer M, infer P> ? RouteHandler<M, P> :
T[K] extends RouteMap ? RouteHandlers<T[K]> :
[name in keyof routeMap]: (
routeMap[name] extends Route<infer method, infer pattern> ? RouteHandler<method, pattern> :
routeMap[name] extends RouteMap ? RouteHandlers<routeMap[name]> :
never
)
}
export type RouteHandlers<TRouteMap extends RouteMap> =
| RouteHandlersWithMiddleware<TRouteMap>
| {
[Key in keyof TRouteMap]: (
TRouteMap[Key] extends Route<infer Method, infer Pattern> ? RouteHandler<Method, Pattern> :
TRouteMap[Key] extends RouteMap ? RouteHandlers<TRouteMap[Key]> :
never
)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this as much as routeMap. TRouteMap is an awkward name.

The only reason people put a T in front of it is so that the name of the generic doesn't match the name of the interface it extends in the constraint. But if we follow the practice that all our interfaces/types are declared with uppercase names, and all our generics are declared with lowercase names, we will never have this problem.


type RouteHandlersWithMiddleware<T extends RouteMap> = {
type RouteHandlersWithMiddleware<routeMap extends RouteMap> = {
use: Middleware[]
handlers: RouteHandlers<T>
handlers: RouteHandlers<routeMap>
}

export function isRouteHandlersWithMiddleware<T extends RouteMap>(
handlers: any,
): handlers is RouteHandlersWithMiddleware<T> {
export function isRouteHandlersWithMiddleware<routeMap extends RouteMap>(
handlers: unknown,
): handlers is RouteHandlersWithMiddleware<routeMap> {
return (
typeof handlers === 'object' && handlers != null && 'use' in handlers && 'handlers' in handlers
)
Expand All @@ -32,27 +32,28 @@ export function isRouteHandlersWithMiddleware<T extends RouteMap>(
/**
* An individual route handler.
*/
export type RouteHandler<M extends RequestMethod | 'ANY', P extends string> =
| RequestHandlerWithMiddleware<M, P>
| RequestHandler<M, Params<P>>
export type RouteHandler<method extends RequestMethod | 'ANY', pattern extends string> =
| RequestHandlerWithMiddleware<method, pattern>
| RequestHandler<method, Params<pattern>>

type RequestHandlerWithMiddleware<M extends RequestMethod | 'ANY', P extends string> = {
use: Middleware<M, Params<P>>[]
handler: RequestHandler<M, Params<P>>
type RequestHandlerWithMiddleware<method extends RequestMethod | 'ANY', pattern extends string> = {
use: Middleware<method, Params<pattern>>[]
handler: RequestHandler<method, Params<pattern>>
}

export function isRequestHandlerWithMiddleware<M extends RequestMethod | 'ANY', P extends string>(
handler: any,
): handler is RequestHandlerWithMiddleware<M, P> {
export function isRequestHandlerWithMiddleware<
method extends RequestMethod | 'ANY',
pattern extends string,
>(handler: unknown): handler is RequestHandlerWithMiddleware<method, pattern> {
return typeof handler === 'object' && handler != null && 'use' in handler && 'handler' in handler
}

/**
* Build a `RouteHandler` type from a string, route pattern, or route.
*/
// prettier-ignore
export type BuildRouteHandler<M extends RequestMethod | 'ANY', T extends string | RoutePattern | Route> =
T extends string ? RouteHandler<M, T> :
T extends RoutePattern<infer P> ? RouteHandler<M, P> :
T extends Route<infer _, infer P> ? RouteHandler<M, P> :
export type BuildRouteHandler<method extends RequestMethod | 'ANY', route extends string | RoutePattern | Route> =
route extends string ? RouteHandler<method, route> :
route extends RoutePattern<infer pattern> ? RouteHandler<method, pattern> :
route extends Route<infer _, infer pattern> ? RouteHandler<method, pattern> :
never
68 changes: 34 additions & 34 deletions packages/fetch-router/src/lib/route-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@ import type { HrefBuilderArgs, Join, RouteMatch } from '@remix-run/route-pattern
import type { RequestMethod } from './request-methods.ts'
import type { Simplify } from './type-utils.ts'

export interface RouteMap<T extends string = string> {
[K: string]: Route<RequestMethod | 'ANY', T> | RouteMap<T>
export interface RouteMap<name extends string = string> {
[name: string]: Route<RequestMethod | 'ANY', name> | RouteMap<name>
}

export class Route<
M extends RequestMethod | 'ANY' = RequestMethod | 'ANY',
P extends string = string,
method extends RequestMethod | 'ANY' = RequestMethod | 'ANY',
pattern extends string = string,
> {
readonly method: M | 'ANY'
readonly pattern: RoutePattern<P>
readonly method: method | 'ANY'
readonly pattern: RoutePattern<pattern>

constructor(method: M | 'ANY', pattern: P | RoutePattern<P>) {
constructor(method: method | 'ANY', pattern: pattern | RoutePattern<pattern>) {
this.method = method
this.pattern = typeof pattern === 'string' ? new RoutePattern(pattern) : pattern
}

href(...args: HrefBuilderArgs<P>): string {
href(...args: HrefBuilderArgs<pattern>): string {
return this.pattern.href(...args)
}

match(url: string | URL): RouteMatch<P> | null {
match(url: string | URL): RouteMatch<pattern> | null {
return this.pattern.match(url)
}
}

/**
* Create a route map from a set of route definitions.
*/
export function createRoutes<const R extends RouteDefs>(defs: R): BuildRouteMap<'/', R>
export function createRoutes<P extends string, const R extends RouteDefs>(
base: P | RoutePattern<P>,
defs: R,
): BuildRouteMap<P, R>
export function createRoutes<const defs extends RouteDefs>(defs: defs): BuildRouteMap<'/', defs>
export function createRoutes<pattern extends string, const defs extends RouteDefs>(
base: pattern | RoutePattern<pattern>,
defs: defs,
): BuildRouteMap<pattern, defs>
export function createRoutes(baseOrDefs: any, defs?: RouteDefs): RouteMap {
return typeof baseOrDefs === 'string' || baseOrDefs instanceof RoutePattern
? buildRouteMap(
Expand All @@ -46,10 +46,10 @@ export function createRoutes(baseOrDefs: any, defs?: RouteDefs): RouteMap {
: buildRouteMap(new RoutePattern('/'), baseOrDefs)
}

function buildRouteMap<P extends string, R extends RouteDefs>(
base: RoutePattern<P>,
defs: R,
): BuildRouteMap<P, R> {
function buildRouteMap<pattern extends string, defs extends RouteDefs>(
base: RoutePattern<pattern>,
defs: defs,
): BuildRouteMap<pattern, defs> {
let routes: any = {}

for (let key in defs) {
Expand All @@ -70,31 +70,31 @@ function buildRouteMap<P extends string, R extends RouteDefs>(
}

// prettier-ignore
export type BuildRouteMap<P extends string, R extends RouteDefs> = Simplify<{
-readonly [K in keyof R]: (
R[K] extends Route<infer M extends RequestMethod | 'ANY', infer S extends string> ? Route<M, Join<P, S>> :
R[K] extends RouteDef ? BuildRoute<P, R[K]> :
R[K] extends RouteDefs ? BuildRouteMap<P, R[K]> :
export type BuildRouteMap<basePattern extends string, defs extends RouteDefs> = Simplify<{
-readonly [name in keyof defs]: (
defs[name] extends Route<infer method extends RequestMethod | 'ANY', infer pattern extends string> ? Route<method, Join<basePattern, pattern>> :
defs[name] extends RouteDef ? BuildRoute<basePattern, defs[name]> :
defs[name] extends RouteDefs ? BuildRouteMap<basePattern, defs[name]> :
never
)
}>

// prettier-ignore
type BuildRoute<P extends string, D extends RouteDef> =
D extends string ? Route<'ANY', Join<P, D>> :
D extends RoutePattern<infer S extends string> ? Route<'ANY', Join<P, S>> :
D extends { method: infer M, pattern: infer S } ? (
S extends string ? Route<M extends RequestMethod ? M : 'ANY', Join<P, S>> :
S extends RoutePattern<infer S extends string> ? Route<M extends RequestMethod ? M : 'ANY', Join<P, S>> :
type BuildRoute<basePattern extends string, def extends RouteDef> =
def extends string ? Route<'ANY', Join<basePattern, def>> :
def extends RoutePattern<infer pattern extends string> ? Route<'ANY', Join<basePattern, pattern>> :
def extends { method: infer method, pattern: infer pattern } ? (
pattern extends string ? Route<method extends RequestMethod ? method : 'ANY', Join<basePattern, pattern>> :
pattern extends RoutePattern<infer pattern extends string> ? Route<method extends RequestMethod ? method : 'ANY', Join<basePattern, pattern>> :
never
) :
never

export interface RouteDefs {
[K: string]: Route | RouteDef | RouteDefs
[name: string]: Route | RouteDef | RouteDefs
}

export type RouteDef<T extends string = string> =
| T
| RoutePattern<T>
| { method?: RequestMethod; pattern: T | RoutePattern<T> }
export type RouteDef<pattern extends string = string> =
| pattern
| RoutePattern<pattern>
| { method?: RequestMethod; pattern: pattern | RoutePattern<pattern> }