From 2699d6c9ad472f126e3c137940f685ddac6858f4 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Sun, 5 Jan 2025 17:19:29 -0600 Subject: [PATCH 01/11] close but not quite right --- src/services/createRoute.ts | 20 ++++++++++---------- src/types/route.ts | 14 ++++---------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 39b31980..1497f9bb 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -30,7 +30,7 @@ export function createRoute< & WithoutComponents & WithoutParent & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName> +Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState> export function createRoute< const TParent extends Route, @@ -45,7 +45,7 @@ export function createRoute< & WithoutComponents & WithParent & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']> +Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent> export function createRoute< TComponent extends Component, @@ -57,10 +57,10 @@ export function createRoute< const TState extends Record = Record >(options: CreateRouteOptions & WithHooks - & WithComponent, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName>> + & WithComponent, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> & WithoutParent & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName> +Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState> export function createRoute< TComponent extends Component, @@ -73,10 +73,10 @@ export function createRoute< const TState extends Record = Record >(options: CreateRouteOptions & WithHooks - & WithComponent, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']>> + & WithComponent, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> & WithParent & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']> +Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent> export function createRoute< TComponents extends Record, @@ -88,10 +88,10 @@ export function createRoute< const TState extends Record = Record >(options: CreateRouteOptions & WithHooks - & WithComponents, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName>> + & WithComponents, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> & WithoutParent & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName> +Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState> export function createRoute< TComponents extends Record, @@ -104,10 +104,10 @@ export function createRoute< const TState extends Record = Record >(options: CreateRouteOptions & WithHooks - & WithComponents, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']>> + & WithComponents, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> & WithParent & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']> +Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent> export function createRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/types/route.ts b/src/types/route.ts index 30e7a251..cd2e083c 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -1,4 +1,4 @@ -import { CreateRouteOptions, WithComponent, WithComponents, WithHost, WithParent, WithState, WithoutComponents, WithoutHost, WithoutParent, WithoutState } from '@/types/createRouteOptions' +import { CreateRouteOptions } from '@/types/createRouteOptions' import { Hash } from '@/types/hash' import { Host } from '@/types/host' import { Param } from '@/types/paramTypes' @@ -6,18 +6,12 @@ import { Path } from '@/types/path' import { PrefetchConfig } from '@/types/prefetch' import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' -import { WithHooks } from '@/types/hooks' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. */ export type Routes = readonly Route[] -/** - * The Route properties originally provided to `createRoute`. The only change is normalizing meta to always default to an empty object. - */ -type CreateRouteOptionsMatched = CreateRouteOptions & WithHooks & (WithHost | WithoutHost) & (WithComponent | WithComponents | WithoutComponents) & (WithParent | WithoutParent) & (WithState | WithoutState) & { id: string, meta: RouteMeta } - /** * Represents the structure of a route within the application. Return value of `createRoute` * @template TName - Represents the unique name identifying the route, typically a string. @@ -32,7 +26,7 @@ export type Route< THash extends Hash = Hash, TMeta extends RouteMeta = RouteMeta, TState extends Record = Record, - TMatchNames extends string | undefined = string | undefined + TParent extends CreateRouteOptions = CreateRouteOptions > = { /** * Unique identifier for the route, generated by router. @@ -41,12 +35,12 @@ export type Route< /** * The specific route properties that were matched in the current route. */ - matched: CreateRouteOptionsMatched, + matched: CreateRouteOptions, /** * The specific route properties that were matched in the current route, including any ancestors. * Order of routes will be from greatest ancestor to narrowest matched. */ - matches: CreateRouteOptionsMatched[], + matches: (TParent | CreateRouteOptions)[], /** * Identifier for the route as defined by user. Name must be unique among named routes. Name is used for routing and for matching. */ From 924b74b82826ba3eefff91ff454e1cea8e4e6747 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Sun, 5 Jan 2025 17:43:20 -0600 Subject: [PATCH 02/11] improved but still WIP --- src/services/createRoute.ts | 72 ++++++++++++++++++++++++++--- src/types/route.ts | 7 +-- src/types/routeWithParams.spec-d.ts | 8 ++-- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 1497f9bb..da7f02de 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -30,7 +30,17 @@ export function createRoute< & WithoutComponents & WithoutParent & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState> +Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + TMeta, + TState, + CreateRouteOptions, + [CreateRouteOptions] +> export function createRoute< const TParent extends Route, @@ -45,7 +55,17 @@ export function createRoute< & WithoutComponents & WithParent & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent> +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, + CombineState, + CreateRouteOptions, + [...TParent['matches'], CreateRouteOptions] +> export function createRoute< TComponent extends Component, @@ -60,7 +80,17 @@ export function createRoute< & WithComponent, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> & WithoutParent & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState> +Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + TMeta, + TState, + CreateRouteOptions, + [CreateRouteOptions] +> export function createRoute< TComponent extends Component, @@ -76,7 +106,17 @@ export function createRoute< & WithComponent, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> & WithParent & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent> +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, + CombineState, + CreateRouteOptions, + [...TParent['matches'], CreateRouteOptions] +> export function createRoute< TComponents extends Record, @@ -91,7 +131,17 @@ export function createRoute< & WithComponents, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> & WithoutParent & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState> +Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + TMeta, + TState, + CreateRouteOptions, + [CreateRouteOptions] +> export function createRoute< TComponents extends Record, @@ -107,7 +157,17 @@ export function createRoute< & WithComponents, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> & WithParent & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent> +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, + CombineState, + CreateRouteOptions, + [...TParent['matches'], CreateRouteOptions] +> export function createRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/types/route.ts b/src/types/route.ts index cd2e083c..ccc9125d 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -26,7 +26,8 @@ export type Route< THash extends Hash = Hash, TMeta extends RouteMeta = RouteMeta, TState extends Record = Record, - TParent extends CreateRouteOptions = CreateRouteOptions + TMatched extends CreateRouteOptions = CreateRouteOptions, + TMatches extends CreateRouteOptions[] = CreateRouteOptions[] > = { /** * Unique identifier for the route, generated by router. @@ -35,12 +36,12 @@ export type Route< /** * The specific route properties that were matched in the current route. */ - matched: CreateRouteOptions, + matched: TMatched, /** * The specific route properties that were matched in the current route, including any ancestors. * Order of routes will be from greatest ancestor to narrowest matched. */ - matches: (TParent | CreateRouteOptions)[], + matches: TMatches, /** * Identifier for the route as defined by user. Name must be unique among named routes. Name is used for routing and for matching. */ diff --git a/src/types/routeWithParams.spec-d.ts b/src/types/routeWithParams.spec-d.ts index 17e7dc9d..595fee2d 100644 --- a/src/types/routeWithParams.spec-d.ts +++ b/src/types/routeWithParams.spec-d.ts @@ -6,12 +6,14 @@ import { Query } from '@/types/query' import { Route } from '@/types/route' import { RouteGetByKey } from '@/types/routeWithParams' import { routes } from '@/utilities/testHelpers' -import { RouteMeta } from './register' -import { Param } from './paramTypes' +import { RouteMeta } from '@/types/register' +import { Param } from '@/types/paramTypes' +import { CreateRouteOptions } from '@/types/createRouteOptions' test('RouteGetByName works as expected', () => { type Source = RouteGetByKey - type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, 'parentA'> + type Matched = CreateRouteOptions<'parentA', '/parentA/[paramA]', undefined, undefined> + type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, Matched, [Matched]> expectTypeOf().toEqualTypeOf() }) From 1300769a16a35b6c6a306a91b796c6e17deb2b42 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Sun, 5 Jan 2025 17:49:41 -0600 Subject: [PATCH 03/11] moving hooks, state into CreateRouteOptions type --- src/services/createRoute.ts | 27 +++++++-------------------- src/types/createRouteOptions.ts | 10 +++++++--- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index da7f02de..40414263 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -6,7 +6,7 @@ import { CombineQuery } from '@/services/combineQuery' import { CombineState } from '@/services/combineState' import { createRouteId } from '@/services/createRouteId' import { host } from '@/services/host' -import { CreateRouteOptions, WithComponent, WithComponents, WithParent, WithState, WithoutComponents, WithoutParent, WithoutState, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' +import { CreateRouteOptions, WithComponent, WithComponents, WithParent, WithoutComponents, WithoutParent, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' import { Hash, toHash, ToHash } from '@/types/hash' import { Host } from '@/types/host' import { toName, ToName } from '@/types/name' @@ -16,7 +16,6 @@ import { Query, ToQuery, toQuery } from '@/types/query' import { RouteMeta } from '@/types/register' import { Route } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' -import { WithHooks } from '@/types/hooks' export function createRoute< const TName extends string | undefined = undefined, @@ -26,10 +25,8 @@ export function createRoute< const TMeta extends RouteMeta = RouteMeta, const TState extends Record = Record >(options: CreateRouteOptions - & WithHooks & WithoutComponents - & WithoutParent - & (WithState | WithoutState)): + & WithoutParent): Route< ToName, Host<'', {}>, @@ -51,10 +48,8 @@ export function createRoute< const TMeta extends RouteMeta = RouteMeta, const TState extends Record = Record >(options: CreateRouteOptions - & WithHooks & WithoutComponents - & WithParent - & (WithState | WithoutState)): + & WithParent): Route< ToName, Host<'', {}>, @@ -76,10 +71,8 @@ export function createRoute< const TMeta extends RouteMeta = RouteMeta, const TState extends Record = Record >(options: CreateRouteOptions - & WithHooks & WithComponent, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> - & WithoutParent - & (WithState | WithoutState)): + & WithoutParent): Route< ToName, Host<'', {}>, @@ -102,10 +95,8 @@ export function createRoute< const TMeta extends RouteMeta = RouteMeta, const TState extends Record = Record >(options: CreateRouteOptions - & WithHooks & WithComponent, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> - & WithParent - & (WithState | WithoutState)): + & WithParent): Route< ToName, Host<'', {}>, @@ -127,10 +118,8 @@ export function createRoute< const TMeta extends RouteMeta = RouteMeta, const TState extends Record = Record >(options: CreateRouteOptions - & WithHooks & WithComponents, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> - & WithoutParent - & (WithState | WithoutState)): + & WithoutParent): Route< ToName, Host<'', {}>, @@ -153,10 +142,8 @@ export function createRoute< const TMeta extends RouteMeta = RouteMeta, const TState extends Record = Record >(options: CreateRouteOptions - & WithHooks & WithComponents, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> - & WithParent - & (WithState | WithoutState)): + & WithParent): Route< ToName, Host<'', {}>, diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index f656a636..1fb715a4 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -14,8 +14,9 @@ import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' import { Route } from '@/types/route' import { MaybePromise } from '@/types/utilities' -import { ResolvedRoute } from './resolved' -import { PropsCallbackContext } from './props' +import { ResolvedRoute } from '@/types/resolved' +import { PropsCallbackContext } from '@/types/props' +import { WithHooks } from '@/types/hooks' export type WithHost = { /** @@ -102,7 +103,8 @@ export type CreateRouteOptions< TPath extends string | Path | undefined = string | Path | undefined, TQuery extends string | Query | undefined = string | Query | undefined, THash extends string | Hash | undefined = string | Hash | undefined, - TMeta extends RouteMeta = RouteMeta + TMeta extends RouteMeta = RouteMeta, + TState extends Record = Record > = { /** * Name for route, used to create route keys and in navigation. @@ -129,6 +131,8 @@ export type CreateRouteOptions< */ prefetch?: PrefetchConfig, } +& WithHooks +& (WithState | WithoutState) export function combineRoutes(parent: Route, child: Route): Route { return { From 6872817157cce18d646c84683c9f20cc91e71346 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Mon, 6 Jan 2025 20:45:07 -0600 Subject: [PATCH 04/11] WIP, using single generic for createRoute overloads --- src/services/createRoute.ts | 226 +++++++++++++++++------------------- src/types/route.ts | 35 ++++-- 2 files changed, 136 insertions(+), 125 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 40414263..5e8133b2 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -14,147 +14,139 @@ import { Param } from '@/types/paramTypes' import { Path, ToPath, toPath } from '@/types/path' import { Query, ToQuery, toQuery } from '@/types/query' import { RouteMeta } from '@/types/register' -import { Route } from '@/types/route' +import { Route, ToMeta, ToState } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' export function createRoute< - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions + const TOptions extends CreateRouteOptions +>(options: TOptions & WithoutComponents & WithoutParent): -Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - TMeta, - TState, - CreateRouteOptions, - [CreateRouteOptions] -> +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends Path | string | undefined, + infer TQuery extends Query | string | undefined, + infer THash extends Hash | string | undefined, + infer TMeta extends RouteMeta, + infer TState extends Record +> ? Route, Host<'', {}>, ToPath, ToQuery, ToHash, ToMeta, ToState> : never export function createRoute< const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions + const TOptions extends CreateRouteOptions +>(options: TOptions & WithoutComponents & WithParent): -Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, - CombineState, - CreateRouteOptions, - [...TParent['matches'], CreateRouteOptions] -> +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends Path | string | undefined, + infer TQuery extends Query | string | undefined, + infer THash extends Hash | string | undefined, + infer TMeta extends RouteMeta, + infer TState extends Record +> ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']> + > : never export function createRoute< - TComponent extends Component, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithComponent, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> + const TComponent extends Component, + const TOptions extends CreateRouteOptions +>(options: TOptions + & WithComponent & WithoutParent): -Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - TMeta, - TState, - CreateRouteOptions, - [CreateRouteOptions] -> +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends Path | string | undefined, + infer TQuery extends Query | string | undefined, + infer THash extends Hash | string | undefined, + infer TMeta extends RouteMeta, + infer TState extends Record +> ? Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState + > : never export function createRoute< - TComponent extends Component, const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithComponent, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> + const TComponent extends Component, + const TOptions extends CreateRouteOptions +>(options: TOptions + & WithComponent & WithParent): -Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, - CombineState, - CreateRouteOptions, - [...TParent['matches'], CreateRouteOptions] -> +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends Path | string | undefined, + infer TQuery extends Query | string | undefined, + infer THash extends Hash | string | undefined, + infer TMeta extends RouteMeta, + infer TState extends Record +> ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']> + > : never export function createRoute< - TComponents extends Record, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithComponents, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState>> + const TComponents extends Record, + const TOptions extends CreateRouteOptions +>(options: TOptions + & WithComponents & WithoutParent): -Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - TMeta, - TState, - CreateRouteOptions, - [CreateRouteOptions] -> +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends Path | string | undefined, + infer TQuery extends Query | string | undefined, + infer THash extends Hash | string | undefined, + infer TMeta extends RouteMeta, + infer TState extends Record +> ? Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState + > : never export function createRoute< - TComponents extends Record, const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithComponents, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TParent>> + const TComponents extends Record, + const TOptions extends CreateRouteOptions +>(options: TOptions + & WithComponents & WithParent): -Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, - CombineState, - CreateRouteOptions, - [...TParent['matches'], CreateRouteOptions] -> +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends Path | string | undefined, + infer TQuery extends Query | string | undefined, + infer THash extends Hash | string | undefined, + infer TMeta extends RouteMeta, + infer TState extends Record +> ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']> + > : never export function createRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/types/route.ts b/src/types/route.ts index ccc9125d..25d35fe9 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -1,17 +1,36 @@ -import { CreateRouteOptions } from '@/types/createRouteOptions' -import { Hash } from '@/types/hash' +import { Component } from 'vue' +import { CreateRouteOptions, WithoutState, WithState } from '@/types/createRouteOptions' +import { Hash, ToHash } from '@/types/hash' import { Host } from '@/types/host' import { Param } from '@/types/paramTypes' -import { Path } from '@/types/path' +import { Path, ToPath } from '@/types/path' import { PrefetchConfig } from '@/types/prefetch' -import { Query } from '@/types/query' +import { Query, ToQuery } from '@/types/query' import { RouteMeta } from '@/types/register' +import { WithHooks } from './hooks' +import { ToName } from './name' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. */ export type Routes = readonly Route[] +export type ToMeta = TMeta extends undefined ? {} : TMeta +export type ToState | undefined> = TState extends undefined ? Record : TState + +export type CreatedRouteOptions< + TName extends string | undefined = string | undefined, + TPath extends Path | undefined = Path | undefined, + TQuery extends Query | undefined = Query | undefined, + THash extends Hash | undefined = Hash | undefined, + TMeta extends RouteMeta = RouteMeta, + TState extends Record = Record, + TComponents extends Record = Record +> = CreateRouteOptions, ToPath, ToQuery, ToHash, ToMeta, ToState> & { + id: string, + component: TComponents, +} + /** * Represents the structure of a route within the application. Return value of `createRoute` * @template TName - Represents the unique name identifying the route, typically a string. @@ -26,8 +45,8 @@ export type Route< THash extends Hash = Hash, TMeta extends RouteMeta = RouteMeta, TState extends Record = Record, - TMatched extends CreateRouteOptions = CreateRouteOptions, - TMatches extends CreateRouteOptions[] = CreateRouteOptions[] + TComponents extends Record = Record, + TParentMatches extends CreatedRouteOptions[] = CreatedRouteOptions[] > = { /** * Unique identifier for the route, generated by router. @@ -36,12 +55,12 @@ export type Route< /** * The specific route properties that were matched in the current route. */ - matched: TMatched, + matched: CreatedRouteOptions, /** * The specific route properties that were matched in the current route, including any ancestors. * Order of routes will be from greatest ancestor to narrowest matched. */ - matches: TMatches, + matches: [...TParentMatches, CreatedRouteOptions], /** * Identifier for the route as defined by user. Name must be unique among named routes. Name is used for routing and for matching. */ From 78508a073a9487c56cfc18699d3a14136b83fbab Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 7 Jan 2025 19:55:19 -0600 Subject: [PATCH 05/11] feeling like the right direction --- src/services/createRoute.ts | 28 ++++++++++++++++++------- src/types/route.ts | 32 ++++++++++------------------- src/types/routeWithParams.spec-d.ts | 11 ++++++---- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 5e8133b2..4e245107 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -14,7 +14,7 @@ import { Param } from '@/types/paramTypes' import { Path, ToPath, toPath } from '@/types/path' import { Query, ToQuery, toQuery } from '@/types/query' import { RouteMeta } from '@/types/register' -import { Route, ToMeta, ToState } from '@/types/route' +import { CreatedRouteOptions, Route, ToMeta, ToState } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' export function createRoute< @@ -29,7 +29,16 @@ TOptions extends CreateRouteOptions< infer THash extends Hash | string | undefined, infer TMeta extends RouteMeta, infer TState extends Record -> ? Route, Host<'', {}>, ToPath, ToQuery, ToHash, ToMeta, ToState> : never +> ? Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [CreatedRouteOptions & TOptions] + > : never export function createRoute< const TParent extends Route, @@ -51,7 +60,8 @@ TOptions extends CreateRouteOptions< CombineQuery>, CombineHash>, CombineMeta, TParent['meta']>, - CombineState, TParent['state']> + CombineState, TParent['state']>, + [...TParent['matches'], CreatedRouteOptions & TOptions] > : never export function createRoute< @@ -74,7 +84,8 @@ TOptions extends CreateRouteOptions< ToQuery, ToHash, ToMeta, - ToState + ToState, + [CreatedRouteOptions & TOptions & WithComponent] > : never export function createRoute< @@ -98,7 +109,8 @@ TOptions extends CreateRouteOptions< CombineQuery>, CombineHash>, CombineMeta, TParent['meta']>, - CombineState, TParent['state']> + CombineState, TParent['state']>, + [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponent] > : never export function createRoute< @@ -121,7 +133,8 @@ TOptions extends CreateRouteOptions< ToQuery, ToHash, ToMeta, - ToState + ToState, + [CreatedRouteOptions & TOptions & WithComponents] > : never export function createRoute< @@ -145,7 +158,8 @@ TOptions extends CreateRouteOptions< CombineQuery>, CombineHash>, CombineMeta, TParent['meta']>, - CombineState, TParent['state']> + CombineState, TParent['state']>, + [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponents] > : never export function createRoute(options: CreateRouteOptions): Route { diff --git a/src/types/route.ts b/src/types/route.ts index 25d35fe9..7670a91d 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -1,14 +1,12 @@ -import { Component } from 'vue' -import { CreateRouteOptions, WithoutState, WithState } from '@/types/createRouteOptions' -import { Hash, ToHash } from '@/types/hash' +import { CreateRouteOptions } from '@/types/createRouteOptions' +import { Hash } from '@/types/hash' import { Host } from '@/types/host' import { Param } from '@/types/paramTypes' -import { Path, ToPath } from '@/types/path' +import { Path } from '@/types/path' import { PrefetchConfig } from '@/types/prefetch' -import { Query, ToQuery } from '@/types/query' +import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' -import { WithHooks } from './hooks' -import { ToName } from './name' +import { Component } from 'vue' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. @@ -18,17 +16,10 @@ export type Routes = readonly Route[] export type ToMeta = TMeta extends undefined ? {} : TMeta export type ToState | undefined> = TState extends undefined ? Record : TState -export type CreatedRouteOptions< - TName extends string | undefined = string | undefined, - TPath extends Path | undefined = Path | undefined, - TQuery extends Query | undefined = Query | undefined, - THash extends Hash | undefined = Hash | undefined, - TMeta extends RouteMeta = RouteMeta, - TState extends Record = Record, - TComponents extends Record = Record -> = CreateRouteOptions, ToPath, ToQuery, ToHash, ToMeta, ToState> & { +export type CreatedRouteOptions = CreateRouteOptions & { id: string, - component: TComponents, + component?: Component, + components?: Record, } /** @@ -45,8 +36,7 @@ export type Route< THash extends Hash = Hash, TMeta extends RouteMeta = RouteMeta, TState extends Record = Record, - TComponents extends Record = Record, - TParentMatches extends CreatedRouteOptions[] = CreatedRouteOptions[] + TMatches extends CreatedRouteOptions[] = CreatedRouteOptions[] > = { /** * Unique identifier for the route, generated by router. @@ -55,12 +45,12 @@ export type Route< /** * The specific route properties that were matched in the current route. */ - matched: CreatedRouteOptions, + matched: TMatches extends [...any[], infer Last extends CreatedRouteOptions] ? Last : CreatedRouteOptions, /** * The specific route properties that were matched in the current route, including any ancestors. * Order of routes will be from greatest ancestor to narrowest matched. */ - matches: [...TParentMatches, CreatedRouteOptions], + matches: TMatches, /** * Identifier for the route as defined by user. Name must be unique among named routes. Name is used for routing and for matching. */ diff --git a/src/types/routeWithParams.spec-d.ts b/src/types/routeWithParams.spec-d.ts index 595fee2d..de594e3e 100644 --- a/src/types/routeWithParams.spec-d.ts +++ b/src/types/routeWithParams.spec-d.ts @@ -3,17 +3,20 @@ import { Hash } from '@/types/hash' import { Host } from '@/types/host' import { Path } from '@/types/path' import { Query } from '@/types/query' -import { Route } from '@/types/route' +import { CreatedRouteOptions, Route } from '@/types/route' import { RouteGetByKey } from '@/types/routeWithParams' import { routes } from '@/utilities/testHelpers' import { RouteMeta } from '@/types/register' import { Param } from '@/types/paramTypes' -import { CreateRouteOptions } from '@/types/createRouteOptions' test('RouteGetByName works as expected', () => { type Source = RouteGetByKey - type Matched = CreateRouteOptions<'parentA', '/parentA/[paramA]', undefined, undefined> - type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, Matched, [Matched]> + type Matched = CreatedRouteOptions & { + readonly name: 'parentA', + readonly path: '/parentA/[paramA]', + } + type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, [Matched]> + expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() }) From 5cee8bd409cbae3e8490a9b0b51f22776141c1b1 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 7 Jan 2025 20:42:17 -0600 Subject: [PATCH 06/11] working version --- src/services/createExternalRoute.ts | 52 ++++++++++++++++++++--------- src/types/hash.ts | 16 +++++---- src/types/host.ts | 15 +++++---- src/types/name.ts | 6 +++- src/types/path.ts | 16 +++++---- src/types/query.ts | 16 +++++---- src/types/routeWithParams.spec-d.ts | 2 +- src/types/routesMap.spec-ts.ts | 2 +- src/utilities/testHelpers.ts | 2 ++ 9 files changed, 82 insertions(+), 45 deletions(-) diff --git a/src/services/createExternalRoute.ts b/src/services/createExternalRoute.ts index e30902fc..a5862aad 100644 --- a/src/services/createExternalRoute.ts +++ b/src/services/createExternalRoute.ts @@ -11,28 +11,50 @@ import { toName, ToName } from '@/types/name' import { Path, toPath, ToPath } from '@/types/path' import { Query, toQuery, ToQuery } from '@/types/query' import { RouteMeta } from '@/types/register' -import { Route } from '@/types/route' +import { CreatedRouteOptions, Route } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' export function createExternalRoute< const THost extends string | Host, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta ->(options: CreateRouteOptions & WithHost & WithoutParent): -Route, ToHost, ToPath, ToQuery, ToHash, TMeta> + const TOptions extends CreateRouteOptions +>(options: TOptions & WithHost & WithoutParent): +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends string | Path | undefined, + infer TQuery extends string | Query | undefined, + infer THash extends string | Hash | undefined, + infer TMeta extends RouteMeta +> ? Route< + ToName, + ToHost, + ToPath, + ToQuery, + ToHash, + TMeta, + {}, + [CreatedRouteOptions & TOptions] + > : never export function createExternalRoute< const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta ->(options: CreateRouteOptions & WithoutHost & WithParent): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta> + const TOptions extends CreateRouteOptions +>(options: TOptions & WithoutHost & WithParent): +TOptions extends CreateRouteOptions< + infer TName extends string | undefined, + infer TPath extends string | Path | undefined, + infer TQuery extends string | Query | undefined, + infer THash extends string | Hash | undefined, + infer TMeta extends RouteMeta +> ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, + {}, + [CreatedRouteOptions & TOptions] + > : never export function createExternalRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/types/hash.ts b/src/types/hash.ts index 0c93c1b7..85970d08 100644 --- a/src/types/hash.ts +++ b/src/types/hash.ts @@ -6,13 +6,15 @@ export type Hash< > = { value: THash, } -export type ToHash = T extends string - ? Hash - : T extends undefined - ? Hash<''> - : unknown extends T - ? Hash<''> - : T +export type ToHash = + [Hash | string | undefined] extends [T] ? Hash<''> : + T extends string + ? Hash + : T extends undefined + ? Hash<''> + : unknown extends T + ? Hash<''> + : T function isHash(value: unknown): value is Hash { return isRecord(value) && typeof value.hash === 'string' diff --git a/src/types/host.ts b/src/types/host.ts index 8d973b84..775889e9 100644 --- a/src/types/host.ts +++ b/src/types/host.ts @@ -27,13 +27,16 @@ export type Host< params: string extends THost ? Record : Identity>, } -export type ToHost = T extends string - ? Host - : T extends undefined +export type ToHost = + [string | undefined] extends [T] ? Host<'', {}> - : unknown extends T - ? Host<'', {}> - : T + : T extends string + ? Host + : T extends undefined + ? Host<'', {}> + : unknown extends T + ? Host<'', {}> + : T function isHost(maybeHost: unknown): maybeHost is Host { return isRecord(maybeHost) && typeof maybeHost.value === 'string' diff --git a/src/types/name.ts b/src/types/name.ts index 730456e5..f6ad57c5 100644 --- a/src/types/name.ts +++ b/src/types/name.ts @@ -1,4 +1,8 @@ -export type ToName = T extends string ? T : '' +export type ToName = [string | undefined] extends [T] + ? '' + : T extends string + ? T + : '' export function toName(value: T): ToName export function toName(value: T): string { diff --git a/src/types/path.ts b/src/types/path.ts index b0e1ce0d..5518829e 100644 --- a/src/types/path.ts +++ b/src/types/path.ts @@ -26,13 +26,15 @@ export type Path< value: TPath, params: string extends TPath ? Record : Identity>, } -export type ToPath = T extends string - ? Path - : T extends undefined - ? Path<'', {}> - : unknown extends T - ? Path<'', {}> - : T +export type ToPath = + [Path | string | undefined] extends [T] ? Path<'', {}> : + T extends string + ? Path + : T extends undefined + ? Path<'', {}> + : unknown extends T + ? Path<'', {}> + : T function isPath(maybePath: unknown): maybePath is Path { return isRecord(maybePath) && typeof maybePath.value === 'string' diff --git a/src/types/query.ts b/src/types/query.ts index a3f4e089..1e6403c1 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -29,13 +29,15 @@ export type Query< params: string extends TQuery ? Record : Identity>, } -export type ToQuery = T extends string - ? Query - : T extends undefined - ? Query<'', {}> - : unknown extends T - ? Query<'', {}> - : T +export type ToQuery = + [Query | string | undefined] extends [T] ? Query<'', {}> : + T extends string + ? Query + : T extends undefined + ? Query<'', {}> + : unknown extends T + ? Query<'', {}> + : T function isQuery(maybeQuery: unknown): maybeQuery is Query { return isRecord(maybeQuery) && typeof maybeQuery.value === 'string' diff --git a/src/types/routeWithParams.spec-d.ts b/src/types/routeWithParams.spec-d.ts index de594e3e..1a6531ca 100644 --- a/src/types/routeWithParams.spec-d.ts +++ b/src/types/routeWithParams.spec-d.ts @@ -16,7 +16,7 @@ test('RouteGetByName works as expected', () => { readonly path: '/parentA/[paramA]', } type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, [Matched]> - expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() }) diff --git a/src/types/routesMap.spec-ts.ts b/src/types/routesMap.spec-ts.ts index cbf4ab92..24f0d3a0 100644 --- a/src/types/routesMap.spec-ts.ts +++ b/src/types/routesMap.spec-ts.ts @@ -45,7 +45,7 @@ test('RoutesMap given unnamed parents, removes them from return value and childr zooFoo, bar, zooBar, - ] + ] as const type Map = RoutesMap diff --git a/src/utilities/testHelpers.ts b/src/utilities/testHelpers.ts index 0a8333a7..16633596 100644 --- a/src/utilities/testHelpers.ts +++ b/src/utilities/testHelpers.ts @@ -30,6 +30,8 @@ const parentA = createRoute({ path: '/parentA/[paramA]', }) +export type T1 = typeof parentA['path'] + const childA = createRoute({ parent: parentA, name: 'parentA.childA', From 15696a6fa26c9fec8c2f184aa4f77e6cc3820ac3 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 7 Jan 2025 21:19:32 -0600 Subject: [PATCH 07/11] face_palm --- src/utilities/testHelpers.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utilities/testHelpers.ts b/src/utilities/testHelpers.ts index 16633596..0a8333a7 100644 --- a/src/utilities/testHelpers.ts +++ b/src/utilities/testHelpers.ts @@ -30,8 +30,6 @@ const parentA = createRoute({ path: '/parentA/[paramA]', }) -export type T1 = typeof parentA['path'] - const childA = createRoute({ parent: parentA, name: 'parentA.childA', From c8dd9f4732e402a0094c7ba7febedabf3c5b6d5e Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 7 Jan 2025 21:24:47 -0600 Subject: [PATCH 08/11] LastInArray utility --- src/types/route.ts | 3 ++- src/types/utilities.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/route.ts b/src/types/route.ts index 7670a91d..3d140347 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -7,6 +7,7 @@ import { PrefetchConfig } from '@/types/prefetch' import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' import { Component } from 'vue' +import { LastInArray } from './utilities' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. @@ -45,7 +46,7 @@ export type Route< /** * The specific route properties that were matched in the current route. */ - matched: TMatches extends [...any[], infer Last extends CreatedRouteOptions] ? Last : CreatedRouteOptions, + matched: LastInArray, /** * The specific route properties that were matched in the current route, including any ancestors. * Order of routes will be from greatest ancestor to narrowest matched. diff --git a/src/types/utilities.ts b/src/types/utilities.ts index c8329b5b..a7b14473 100644 --- a/src/types/utilities.ts +++ b/src/types/utilities.ts @@ -14,6 +14,8 @@ export type MaybeArray = T | T[] export type AsArray = T extends MaybeArray ? U[] : T +export type LastInArray = T extends [...any[], infer Last] ? Last : TFallback + export type MaybePromise = T | Promise // Copied and modified from [type-fest](https://github.com/sindresorhus/type-fest/blob/main/source/replace.d.ts) From f31811bf148369c559d2a8570110565c4b601d2b Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Wed, 8 Jan 2025 22:19:59 -0600 Subject: [PATCH 09/11] WIP but moving in the right direction? --- src/guards/routes.spec-d.ts | 6 +- src/services/createExternalRoute.ts | 69 ++++----- src/services/createRoute.ts | 211 ++++++++++------------------ src/types/createRouteOptions.ts | 43 ++---- src/types/route.ts | 4 +- src/types/routeWithParams.spec-d.ts | 57 +++++--- 6 files changed, 162 insertions(+), 228 deletions(-) diff --git a/src/guards/routes.spec-d.ts b/src/guards/routes.spec-d.ts index 2a2cbe9e..806c38ac 100644 --- a/src/guards/routes.spec-d.ts +++ b/src/guards/routes.spec-d.ts @@ -49,9 +49,9 @@ test('router route can be narrowed', () => { expectTypeOf().toMatchTypeOf<'parentA' | 'childA'>() } - if (isRoute(route, 'parentB', { exact: false })) { - expectTypeOf().toMatchTypeOf<'parentB'>() - } + // if (isRoute(route, 'parentB', { exact: false })) { + // expectTypeOf().toMatchTypeOf<'parentB'>() + // } if (isRoute(route, 'parentA')) { expectTypeOf().toMatchTypeOf<'parentA' | 'childA'>() diff --git a/src/services/createExternalRoute.ts b/src/services/createExternalRoute.ts index a5862aad..7d419ddb 100644 --- a/src/services/createExternalRoute.ts +++ b/src/services/createExternalRoute.ts @@ -5,56 +5,43 @@ import { CombinePath } from '@/services/combinePath' import { CombineQuery } from '@/services/combineQuery' import { createRouteId } from '@/services/createRouteId' import { combineRoutes, CreateRouteOptions, isWithHost, isWithParent, WithHost, WithoutHost, WithoutParent, WithParent } from '@/types/createRouteOptions' -import { Hash, toHash, ToHash } from '@/types/hash' +import { toHash, ToHash } from '@/types/hash' import { Host, toHost, ToHost } from '@/types/host' import { toName, ToName } from '@/types/name' -import { Path, toPath, ToPath } from '@/types/path' -import { Query, toQuery, ToQuery } from '@/types/query' -import { RouteMeta } from '@/types/register' -import { CreatedRouteOptions, Route } from '@/types/route' +import { toPath, ToPath } from '@/types/path' +import { toQuery, ToQuery } from '@/types/query' +import { CreatedRouteOptions, Route, ToMeta } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' export function createExternalRoute< const THost extends string | Host, - const TOptions extends CreateRouteOptions ->(options: TOptions & WithHost & WithoutParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends string | Path | undefined, - infer TQuery extends string | Query | undefined, - infer THash extends string | Hash | undefined, - infer TMeta extends RouteMeta -> ? Route< - ToName, - ToHost, - ToPath, - ToQuery, - ToHash, - TMeta, - {}, - [CreatedRouteOptions & TOptions] - > : never + const TOptions extends CreateRouteOptions & WithHost & WithoutParent +>(options: TOptions): +Route< + ToName, + ToHost, + ToPath, + ToQuery, + ToHash, + ToMeta, + {}, + [CreatedRouteOptions & TOptions] +> export function createExternalRoute< const TParent extends Route, - const TOptions extends CreateRouteOptions ->(options: TOptions & WithoutHost & WithParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends string | Path | undefined, - infer TQuery extends string | Query | undefined, - infer THash extends string | Hash | undefined, - infer TMeta extends RouteMeta -> ? Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, - {}, - [CreatedRouteOptions & TOptions] - > : never + const TOptions extends CreateRouteOptions & WithoutHost & WithParent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + {}, + [CreatedRouteOptions & TOptions] +> export function createExternalRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 4e245107..7443bdb0 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -6,161 +6,104 @@ import { CombineQuery } from '@/services/combineQuery' import { CombineState } from '@/services/combineState' import { createRouteId } from '@/services/createRouteId' import { host } from '@/services/host' -import { CreateRouteOptions, WithComponent, WithComponents, WithParent, WithoutComponents, WithoutParent, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' -import { Hash, toHash, ToHash } from '@/types/hash' +import { CreateRouteOptions, WithComponent, WithComponents, WithParent, WithoutComponents, WithoutParent, combineRoutes, isWithParent } from '@/types/createRouteOptions' +import { toHash, ToHash } from '@/types/hash' import { Host } from '@/types/host' import { toName, ToName } from '@/types/name' -import { Param } from '@/types/paramTypes' -import { Path, ToPath, toPath } from '@/types/path' -import { Query, ToQuery, toQuery } from '@/types/query' -import { RouteMeta } from '@/types/register' +import { ToPath, toPath } from '@/types/path' +import { ToQuery, toQuery } from '@/types/query' import { CreatedRouteOptions, Route, ToMeta, ToState } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' export function createRoute< - const TOptions extends CreateRouteOptions ->(options: TOptions - & WithoutComponents - & WithoutParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends Path | string | undefined, - infer TQuery extends Query | string | undefined, - infer THash extends Hash | string | undefined, - infer TMeta extends RouteMeta, - infer TState extends Record -> ? Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - [CreatedRouteOptions & TOptions] - > : never + const TOptions extends CreateRouteOptions & WithoutComponents & WithoutParent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [CreatedRouteOptions & TOptions] +> export function createRoute< const TParent extends Route, - const TOptions extends CreateRouteOptions ->(options: TOptions - & WithoutComponents - & WithParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends Path | string | undefined, - infer TQuery extends Query | string | undefined, - infer THash extends Hash | string | undefined, - infer TMeta extends RouteMeta, - infer TState extends Record -> ? Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, TParent['meta']>, - CombineState, TParent['state']>, - [...TParent['matches'], CreatedRouteOptions & TOptions] - > : never + const TOptions extends CreateRouteOptions & WithoutComponents & WithParent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']>, + [...TParent['matches'], CreatedRouteOptions & TOptions] +> export function createRoute< - const TComponent extends Component, - const TOptions extends CreateRouteOptions ->(options: TOptions - & WithComponent - & WithoutParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends Path | string | undefined, - infer TQuery extends Query | string | undefined, - infer THash extends Hash | string | undefined, - infer TMeta extends RouteMeta, - infer TState extends Record -> ? Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - [CreatedRouteOptions & TOptions & WithComponent] - > : never + const TOptions extends CreateRouteOptions & WithoutParent & WithComponent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [CreatedRouteOptions & TOptions] +> export function createRoute< const TParent extends Route, const TComponent extends Component, - const TOptions extends CreateRouteOptions ->(options: TOptions - & WithComponent - & WithParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends Path | string | undefined, - infer TQuery extends Query | string | undefined, - infer THash extends Hash | string | undefined, - infer TMeta extends RouteMeta, - infer TState extends Record -> ? Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, TParent['meta']>, - CombineState, TParent['state']>, - [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponent] - > : never + const TOptions extends CreateRouteOptions & WithComponent & WithParent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']>, + [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponent] +> export function createRoute< const TComponents extends Record, - const TOptions extends CreateRouteOptions ->(options: TOptions - & WithComponents - & WithoutParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends Path | string | undefined, - infer TQuery extends Query | string | undefined, - infer THash extends Hash | string | undefined, - infer TMeta extends RouteMeta, - infer TState extends Record -> ? Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - [CreatedRouteOptions & TOptions & WithComponents] - > : never + const TOptions extends CreateRouteOptions & WithComponents & WithoutParent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [CreatedRouteOptions & TOptions & WithComponents] +> export function createRoute< const TParent extends Route, const TComponents extends Record, - const TOptions extends CreateRouteOptions ->(options: TOptions - & WithComponents - & WithParent): -TOptions extends CreateRouteOptions< - infer TName extends string | undefined, - infer TPath extends Path | string | undefined, - infer TQuery extends Query | string | undefined, - infer THash extends Hash | string | undefined, - infer TMeta extends RouteMeta, - infer TState extends Record -> ? Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, TParent['meta']>, - CombineState, TParent['state']>, - [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponents] - > : never + const TOptions extends CreateRouteOptions & WithComponents & WithParent +>(options: TOptions): +Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']>, + [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponents] +> export function createRoute(options: CreateRouteOptions): Route { const id = createRouteId() @@ -169,7 +112,7 @@ export function createRoute(options: CreateRouteOptions): Route { const query = toQuery(options.query) const hash = toHash(options.hash) const meta = options.meta ?? {} - const state = isWithState(options) ? options.state : {} + const state = options.state ?? {} const rawRoute = markRaw({ id, meta: {}, state: {}, ...options }) const route = { diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 1fb715a4..3f3eaa17 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -47,13 +47,13 @@ export type WithoutParent = { export type WithComponent< TComponent extends Component = Component, - TRoute extends Route = Route + TProps extends (route: ResolvedRoute, context: PropsCallbackContext) => MaybePromise> = (route: ResolvedRoute, context: PropsCallbackContext) => MaybePromise> > = { /** * A Vue component, which can be either synchronous or asynchronous components. */ component: TComponent, - props?: (route: ResolvedRoute, context: PropsCallbackContext) => TComponent extends Component ? MaybePromise> : {}, + props?: TProps, } export function isWithComponent(options: CreateRouteOptions): options is CreateRouteOptions & WithComponent { @@ -83,56 +83,37 @@ export function isWithComponents(options: CreateRouteOptions): options is Create return 'components' in options && Boolean(options.components) } -export type WithState = Record> = { - /** - * Type params for additional data intended to be stored in history state, all keys will be optional unless a default is provided. - */ - state: TState, -} - -export function isWithState(options: CreateRouteOptions): options is CreateRouteOptions & WithState { - return 'state' in options && Boolean(options.state) -} - -export type WithoutState = { - state?: never, -} - -export type CreateRouteOptions< - TName extends string | undefined = string | undefined, - TPath extends string | Path | undefined = string | Path | undefined, - TQuery extends string | Query | undefined = string | Query | undefined, - THash extends string | Hash | undefined = string | Hash | undefined, - TMeta extends RouteMeta = RouteMeta, - TState extends Record = Record -> = { +export type CreateRouteOptions = { /** * Name for route, used to create route keys and in navigation. */ - name?: TName, + name?: string | undefined, /** * Path part of URL. */ - path?: TPath, + path?: string | Path | undefined, /** * Query (aka search) part of URL. */ - query?: TQuery, + query?: string | Query | undefined, /** * Hash part of URL. */ - hash?: THash, + hash?: string | Hash | undefined, /** * Represents additional metadata associated with a route, customizable via declaration merging. */ - meta?: TMeta, + meta?: RouteMeta, /** * Determines what assets are prefetched when router-link is rendered for this route. Overrides router level prefetch. */ prefetch?: PrefetchConfig, + /** + * Type params for additional data intended to be stored in history state, all keys will be optional unless a default is provided. + */ + state?: Record, } & WithHooks -& (WithState | WithoutState) export function combineRoutes(parent: Route, child: Route): Route { return { diff --git a/src/types/route.ts b/src/types/route.ts index 3d140347..c7ec8b12 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -14,8 +14,8 @@ import { LastInArray } from './utilities' */ export type Routes = readonly Route[] -export type ToMeta = TMeta extends undefined ? {} : TMeta -export type ToState | undefined> = TState extends undefined ? Record : TState +export type ToMeta = TMeta extends undefined ? {} : unknown extends TMeta ? {} : TMeta +export type ToState | undefined> = TState extends undefined ? Record : unknown extends TState ? {} : TState export type CreatedRouteOptions = CreateRouteOptions & { id: string, diff --git a/src/types/routeWithParams.spec-d.ts b/src/types/routeWithParams.spec-d.ts index 1a6531ca..1339e630 100644 --- a/src/types/routeWithParams.spec-d.ts +++ b/src/types/routeWithParams.spec-d.ts @@ -1,22 +1,45 @@ import { expectTypeOf, test } from 'vitest' -import { Hash } from '@/types/hash' -import { Host } from '@/types/host' -import { Path } from '@/types/path' -import { Query } from '@/types/query' -import { CreatedRouteOptions, Route } from '@/types/route' -import { RouteGetByKey } from '@/types/routeWithParams' -import { routes } from '@/utilities/testHelpers' -import { RouteMeta } from '@/types/register' -import { Param } from '@/types/paramTypes' +// import { Hash } from '@/types/hash' +// import { Host } from '@/types/host' +// import { Path } from '@/types/path' +// import { Query } from '@/types/query' +// import { CreatedRouteOptions, Route } from '@/types/route' +// import { RouteGetByKey } from '@/types/routeWithParams' +// import { routes } from '@/utilities/testHelpers' +// import { RouteMeta } from '@/types/register' +// import { Param } from '@/types/paramTypes' +import { createRoute } from '@/main' +import echo from '@/components/echo' -test('RouteGetByName works as expected', () => { - type Source = RouteGetByKey - type Matched = CreatedRouteOptions & { - readonly name: 'parentA', - readonly path: '/parentA/[paramA]', +// test('RouteGetByName works as expected', () => { +// type Source = RouteGetByKey +// type Matched = CreatedRouteOptions & { +// readonly name: 'parentA', +// readonly path: '/parentA/[paramA]', +// } +// type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, [Matched]> +// expectTypeOf().toEqualTypeOf() + +// expectTypeOf().toEqualTypeOf() +// }) + +test('prop type is preserved', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const route = createRoute({ + path: '/parentA/[paramA]', + component: echo, + props() { + return { value: 'test', hello: 'world' } + }, + }) + + type Matched = typeof route.matched + type Props = Matched['props'] + + type Expected = { + value: string, + hello: string, } - type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, [Matched]> - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() }) From 4db477d21b2686ae6656b9c9a0fa9c8c16fddfb0 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 9 Jan 2025 16:59:06 -0600 Subject: [PATCH 10/11] Matched types working without breaking anything but the route argument of the props callback (I think) --- src/services/createRoute.spec-d.ts | 29 ++++ src/services/createRoute.ts | 221 +++++++++++++++++++---------- src/types/createRouteOptions.ts | 5 +- 3 files changed, 175 insertions(+), 80 deletions(-) create mode 100644 src/services/createRoute.spec-d.ts diff --git a/src/services/createRoute.spec-d.ts b/src/services/createRoute.spec-d.ts new file mode 100644 index 00000000..c415e305 --- /dev/null +++ b/src/services/createRoute.spec-d.ts @@ -0,0 +1,29 @@ +import { expectTypeOf, test } from 'vitest' +import { createRoute } from '@/main' +import echo from '@/components/echo' + +test('prop type is preserved', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const parent = createRoute({ + component: echo, + }) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const route = createRoute({ + path: '/[id]', + component: echo, + props: (route) => ({ value: 'value', hello: 'world' }), + }) + + type Matched = typeof route.matched + type Props = Matched['props'] + + type Component = Matched['component'] + + type Expected = { + value: string, + hello: string, + } + + expectTypeOf>().toEqualTypeOf() +}) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 7443bdb0..561fe5ee 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -12,98 +12,165 @@ import { Host } from '@/types/host' import { toName, ToName } from '@/types/name' import { ToPath, toPath } from '@/types/path' import { ToQuery, toQuery } from '@/types/query' -import { CreatedRouteOptions, Route, ToMeta, ToState } from '@/types/route' +import { Route, ToMeta, ToState } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' export function createRoute< - const TOptions extends CreateRouteOptions & WithoutComponents & WithoutParent ->(options: TOptions): -Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - [CreatedRouteOptions & TOptions] -> + const TOptions +>(options: TOptions & CreateRouteOptions & WithoutComponents & WithoutParent): +TOptions extends CreateRouteOptions + ? Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [TOptions & { id: string }] + > + : Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [TOptions & { id: string }] + > export function createRoute< - const TParent extends Route, - const TOptions extends CreateRouteOptions & WithoutComponents & WithParent ->(options: TOptions): -Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, TParent['meta']>, - CombineState, TParent['state']>, - [...TParent['matches'], CreatedRouteOptions & TOptions] -> + const TOptions, + const TParent extends Route +>(options: TOptions & CreateRouteOptions & WithoutComponents & WithParent): +TOptions extends CreateRouteOptions + ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']>, + [...TParent['matches'], TOptions & { id: string }] + > + : Route< + TParent['name'], + Host<'', {}>, + TParent['path'], + TParent['query'], + TParent['hash'], + TParent['meta'], + TParent['state'], + [TOptions & { id: string }] + > export function createRoute< - const TOptions extends CreateRouteOptions & WithoutParent & WithComponent ->(options: TOptions): -Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - [CreatedRouteOptions & TOptions] -> + const TOptions, + TComponent extends Component +>(options: TOptions & CreateRouteOptions & WithoutParent & WithComponent): +TOptions extends CreateRouteOptions + ? Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [TOptions & { id: string }] + > + : Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [TOptions & { id: string }] + > export function createRoute< + const TOptions, const TParent extends Route, - const TComponent extends Component, - const TOptions extends CreateRouteOptions & WithComponent & WithParent ->(options: TOptions): -Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, TParent['meta']>, - CombineState, TParent['state']>, - [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponent] -> + const TComponent extends Component +>(options: TOptions & CreateRouteOptions & WithComponent & WithParent): +TOptions extends CreateRouteOptions + ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']>, + [...TParent['matches'], TOptions & { id: string }] + > + : Route< + TParent['name'], + Host<'', {}>, + TParent['path'], + TParent['query'], + TParent['hash'], + TParent['meta'], + TParent['state'], + [TOptions & { id: string }] + > export function createRoute< - const TComponents extends Record, - const TOptions extends CreateRouteOptions & WithComponents & WithoutParent ->(options: TOptions): -Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - [CreatedRouteOptions & TOptions & WithComponents] -> + const TOptions, + const TComponents extends Record +>(options: TOptions & CreateRouteOptions & WithComponents & WithoutParent): +TOptions extends CreateRouteOptions + ? Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [TOptions & { id: string }] + > + : Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + [TOptions & { id: string }] + > export function createRoute< + const TOptions, const TParent extends Route, - const TComponents extends Record, - const TOptions extends CreateRouteOptions & WithComponents & WithParent ->(options: TOptions): -Route< - ToName, - Host<'', {}>, - CombinePath>, - CombineQuery>, - CombineHash>, - CombineMeta, TParent['meta']>, - CombineState, TParent['state']>, - [...TParent['matches'], CreatedRouteOptions & TOptions & WithComponents] -> + const TComponents extends Record +>(options: TOptions & CreateRouteOptions & WithComponents & WithParent): +TOptions extends CreateRouteOptions + ? Route< + ToName, + Host<'', {}>, + CombinePath>, + CombineQuery>, + CombineHash>, + CombineMeta, TParent['meta']>, + CombineState, TParent['state']>, + [...TParent['matches'], TOptions & { id: string }] + > + : Route< + TParent['name'], + Host<'', {}>, + TParent['path'], + TParent['query'], + TParent['hash'], + TParent['meta'], + TParent['state'], + [TOptions & { id: string }] + > export function createRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 3f3eaa17..8ee72d26 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -46,14 +46,13 @@ export type WithoutParent = { } export type WithComponent< - TComponent extends Component = Component, - TProps extends (route: ResolvedRoute, context: PropsCallbackContext) => MaybePromise> = (route: ResolvedRoute, context: PropsCallbackContext) => MaybePromise> + TComponent extends Component = Component > = { /** * A Vue component, which can be either synchronous or asynchronous components. */ component: TComponent, - props?: TProps, + props?: (route: ResolvedRoute, context: PropsCallbackContext) => ComponentProps, } export function isWithComponent(options: CreateRouteOptions): options is CreateRouteOptions & WithComponent { From ce39013f16957a950bbf2b7dc6486cc017b47d41 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Thu, 9 Jan 2025 18:41:43 -0600 Subject: [PATCH 11/11] Restore MaybePromise to props. Update WithComponents to not accept a route for now --- src/types/createRouteOptions.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 8ee72d26..6ec92336 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -52,7 +52,7 @@ export type WithComponent< * A Vue component, which can be either synchronous or asynchronous components. */ component: TComponent, - props?: (route: ResolvedRoute, context: PropsCallbackContext) => ComponentProps, + props?: (route: ResolvedRoute, context: PropsCallbackContext) => MaybePromise>, } export function isWithComponent(options: CreateRouteOptions): options is CreateRouteOptions & WithComponent { @@ -60,15 +60,14 @@ export function isWithComponent(options: CreateRouteOptions): options is CreateR } export type WithComponents< - TComponents extends Record = Record, - TRoute extends Route = Route + TComponents extends Record = Record > = { /** * Multiple components for named views, which can be either synchronous or asynchronous components. */ components: TComponents, props?: { - [TKey in keyof TComponents]?: (route: ResolvedRoute, context: PropsCallbackContext) => TComponents[TKey] extends Component ? MaybePromise> : {} + [TKey in keyof TComponents]?: (route: ResolvedRoute, context: PropsCallbackContext) => TComponents[TKey] extends Component ? MaybePromise> : {} }, }