Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matched strongly typed #407

Closed
wants to merge 12 commits into from
6 changes: 3 additions & 3 deletions src/guards/routes.spec-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ test('router route can be narrowed', () => {
expectTypeOf<typeof route.name>().toMatchTypeOf<'parentA' | 'childA'>()
}

if (isRoute(route, 'parentB', { exact: false })) {
expectTypeOf<typeof route.name>().toMatchTypeOf<'parentB'>()
}
// if (isRoute(route, 'parentB', { exact: false })) {
// expectTypeOf<typeof route.name>().toMatchTypeOf<'parentB'>()
// }

if (isRoute(route, 'parentA')) {
expectTypeOf<typeof route.name>().toMatchTypeOf<'parentA' | 'childA'>()
Expand Down
47 changes: 28 additions & 19 deletions src/services/createExternalRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +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 { 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 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<TName, TPath, TQuery> & WithHost<THost> & WithoutParent):
Route<ToName<TName>, ToHost<THost>, ToPath<TPath>, ToQuery<TQuery>, ToHash<THash>, TMeta>
const TOptions extends CreateRouteOptions & WithHost<THost> & WithoutParent
>(options: TOptions):
Route<
ToName<TOptions['name']>,
ToHost<THost>,
ToPath<TOptions['path']>,
ToQuery<TOptions['query']>,
ToHash<TOptions['hash']>,
ToMeta<TOptions['meta']>,
{},
[CreatedRouteOptions & TOptions]
>

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<TName, TPath, TQuery> & WithoutHost & WithParent<TParent>):
Route<ToName<TName>, Host<'', {}>, CombinePath<TParent['path'], ToPath<TPath>>, CombineQuery<TParent['query'], ToQuery<TQuery>>, CombineHash<TParent['hash'], ToHash<THash>>, CombineMeta<TMeta, TParent['meta']>>
const TOptions extends CreateRouteOptions & WithoutHost & WithParent<TParent>
>(options: TOptions):
Route<
ToName<TOptions['name']>,
Host<'', {}>,
CombinePath<TParent['path'], ToPath<TOptions['path']>>,
CombineQuery<TParent['query'], ToQuery<TOptions['query']>>,
CombineHash<TParent['hash'], ToHash<TOptions['hash']>>,
CombineMeta<ToMeta<TOptions['meta']>, TParent['meta']>,
{},
[CreatedRouteOptions & TOptions]
>

export function createExternalRoute(options: CreateRouteOptions): Route {
const id = createRouteId()
Expand Down
29 changes: 29 additions & 0 deletions src/services/createRoute.spec-d.ts
Original file line number Diff line number Diff line change
@@ -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({

Check failure on line 7 in src/services/createRoute.spec-d.ts

View workflow job for this annotation

GitHub Actions / Types

'parent' is declared but its value is never read.

Check failure on line 7 in src/services/createRoute.spec-d.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

src/services/createRoute.spec-d.ts > prop type is preserved

TypeCheckError: 'parent' is declared but its value is never read. ❯ src/services/createRoute.spec-d.ts:7:9
component: echo,
})

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const route = createRoute({
path: '/[id]',
component: echo,
props: (route) => ({ value: 'value', hello: 'world' }),

Check failure on line 15 in src/services/createRoute.spec-d.ts

View workflow job for this annotation

GitHub Actions / Types

'route' is declared but its value is never read.

Check failure on line 15 in src/services/createRoute.spec-d.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

src/services/createRoute.spec-d.ts > prop type is preserved

TypeCheckError: 'route' is declared but its value is never read. ❯ src/services/createRoute.spec-d.ts:15:13
})

type Matched = typeof route.matched
type Props = Matched['props']

type Component = Matched['component']

Check failure on line 21 in src/services/createRoute.spec-d.ts

View workflow job for this annotation

GitHub Actions / Types

'Component' is declared but never used.

Check failure on line 21 in src/services/createRoute.spec-d.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

src/services/createRoute.spec-d.ts

TypeCheckError: 'Component' is declared but never used. ❯ src/services/createRoute.spec-d.ts:21:8

type Expected = {
value: string,
hello: string,
}

expectTypeOf<ReturnType<Props>>().toEqualTypeOf<Expected>()
})
235 changes: 149 additions & 86 deletions src/services/createRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,108 +6,171 @@ 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 { 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 { Route } from '@/types/route'
import { ToPath, toPath } from '@/types/path'
import { ToQuery, toQuery } from '@/types/query'
import { Route, ToMeta, ToState } from '@/types/route'
import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys'
import { WithHooks } from '@/types/hooks'

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<string, Param> = Record<string, Param>
>(options: CreateRouteOptions<TName, TPath, TQuery, THash, TMeta>
& WithHooks
& WithoutComponents
& WithoutParent
& (WithState<TState> | WithoutState)):
Route<ToName<TName>, Host<'', {}>, ToPath<TPath>, ToQuery<TQuery>, ToHash<THash>, TMeta, TState, TName>
const TOptions
>(options: TOptions & CreateRouteOptions & WithoutComponents & WithoutParent):
TOptions extends CreateRouteOptions
? Route<
ToName<TOptions['name']>,
Host<'', {}>,
ToPath<TOptions['path']>,
ToQuery<TOptions['query']>,
ToHash<TOptions['hash']>,
ToMeta<TOptions['meta']>,
ToState<TOptions['state']>,
[TOptions & { id: string }]
>
: Route<
ToName<undefined>,
Host<'', {}>,
ToPath<undefined>,
ToQuery<undefined>,
ToHash<undefined>,
ToMeta<undefined>,
ToState<undefined>,
[TOptions & { id: string }]
>

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<string, Param> = Record<string, Param>
>(options: CreateRouteOptions<TName, TPath, TQuery, THash, TMeta>
& WithHooks
& WithoutComponents
& WithParent<TParent>
& (WithState<TState> | WithoutState)):
Route<ToName<TName>, Host<'', {}>, CombinePath<TParent['path'], ToPath<TPath>>, CombineQuery<TParent['query'], ToQuery<TQuery>>, CombineHash<TParent['hash'], ToHash<THash>>, CombineMeta<TMeta, TParent['meta']>, CombineState<TState, TParent['state']>, TName | TParent['matches'][number]['name']>
const TOptions,
const TParent extends Route
>(options: TOptions & CreateRouteOptions & WithoutComponents & WithParent<TParent>):
TOptions extends CreateRouteOptions
? Route<
ToName<TOptions['name']>,
Host<'', {}>,
CombinePath<TParent['path'], ToPath<TOptions['path']>>,
CombineQuery<TParent['query'], ToQuery<TOptions['query']>>,
CombineHash<TParent['hash'], ToHash<TOptions['hash']>>,
CombineMeta<ToMeta<TOptions['meta']>, TParent['meta']>,
CombineState<ToState<TOptions['state']>, 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<
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<string, Param> = Record<string, Param>
>(options: CreateRouteOptions<TName, TPath, TQuery, THash, TMeta>
& WithHooks
& WithComponent<TComponent, Route<ToName<TName>, Host<'', {}>, ToPath<TPath>, ToQuery<TQuery>, ToHash<THash>, TMeta, TState, TName>>
& WithoutParent
& (WithState<TState> | WithoutState)):
Route<ToName<TName>, Host<'', {}>, ToPath<TPath>, ToQuery<TQuery>, ToHash<THash>, TMeta, TState, TName>
const TOptions,
TComponent extends Component
>(options: TOptions & CreateRouteOptions & WithoutParent & WithComponent<TComponent>):
TOptions extends CreateRouteOptions
? Route<
ToName<TOptions['name']>,
Host<'', {}>,
ToPath<TOptions['path']>,
ToQuery<TOptions['query']>,
ToHash<TOptions['hash']>,
ToMeta<TOptions['meta']>,
ToState<TOptions['state']>,
[TOptions & { id: string }]
>
: Route<
ToName<undefined>,
Host<'', {}>,
ToPath<undefined>,
ToQuery<undefined>,
ToHash<undefined>,
ToMeta<undefined>,
ToState<undefined>,
[TOptions & { id: string }]
>

export function createRoute<
TComponent extends Component,
const TOptions,
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<string, Param> = Record<string, Param>
>(options: CreateRouteOptions<TName, TPath, TQuery, THash, TMeta>
& WithHooks
& WithComponent<TComponent, Route<ToName<TName>, Host<'', {}>, CombinePath<TParent['path'], ToPath<TPath>>, CombineQuery<TParent['query'], ToQuery<TQuery>>, CombineHash<TParent['hash'], ToHash<THash>>, CombineMeta<TMeta, TParent['meta']>, CombineState<TState, TParent['state']>, TName | TParent['matches'][number]['name']>>
& WithParent<TParent>
& (WithState<TState> | WithoutState)):
Route<ToName<TName>, Host<'', {}>, CombinePath<TParent['path'], ToPath<TPath>>, CombineQuery<TParent['query'], ToQuery<TQuery>>, CombineHash<TParent['hash'], ToHash<THash>>, CombineMeta<TMeta, TParent['meta']>, CombineState<TState, TParent['state']>, TName | TParent['matches'][number]['name']>
const TComponent extends Component
>(options: TOptions & CreateRouteOptions & WithComponent<TComponent> & WithParent<TParent>):
TOptions extends CreateRouteOptions
? Route<
ToName<TOptions['name']>,
Host<'', {}>,
CombinePath<TParent['path'], ToPath<TOptions['path']>>,
CombineQuery<TParent['query'], ToQuery<TOptions['query']>>,
CombineHash<TParent['hash'], ToHash<TOptions['hash']>>,
CombineMeta<ToMeta<TOptions['meta']>, TParent['meta']>,
CombineState<ToState<TOptions['state']>, 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<
TComponents extends Record<string, 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<string, Param> = Record<string, Param>
>(options: CreateRouteOptions<TName, TPath, TQuery, THash, TMeta>
& WithHooks
& WithComponents<TComponents, Route<ToName<TName>, Host<'', {}>, ToPath<TPath>, ToQuery<TQuery>, ToHash<THash>, TMeta, TState, TName>>
& WithoutParent
& (WithState<TState> | WithoutState)):
Route<ToName<TName>, Host<'', {}>, ToPath<TPath>, ToQuery<TQuery>, ToHash<THash>, TMeta, TState, TName>
const TOptions,
const TComponents extends Record<string, Component>
>(options: TOptions & CreateRouteOptions & WithComponents<TComponents> & WithoutParent):
TOptions extends CreateRouteOptions
? Route<
ToName<TOptions['name']>,
Host<'', {}>,
ToPath<TOptions['path']>,
ToQuery<TOptions['query']>,
ToHash<TOptions['hash']>,
ToMeta<TOptions['meta']>,
ToState<TOptions['state']>,
[TOptions & { id: string }]
>
: Route<
ToName<undefined>,
Host<'', {}>,
ToPath<undefined>,
ToQuery<undefined>,
ToHash<undefined>,
ToMeta<undefined>,
ToState<undefined>,
[TOptions & { id: string }]
>

export function createRoute<
TComponents extends Record<string, Component>,
const TOptions,
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<string, Param> = Record<string, Param>
>(options: CreateRouteOptions<TName, TPath, TQuery, THash, TMeta>
& WithHooks
& WithComponents<TComponents, Route<ToName<TName>, Host<'', {}>, CombinePath<TParent['path'], ToPath<TPath>>, CombineQuery<TParent['query'], ToQuery<TQuery>>, CombineHash<TParent['hash'], ToHash<THash>>, CombineMeta<TMeta, TParent['meta']>, CombineState<TState, TParent['state']>, TName | TParent['matches'][number]['name']>>
& WithParent<TParent>
& (WithState<TState> | WithoutState)):
Route<ToName<TName>, Host<'', {}>, CombinePath<TParent['path'], ToPath<TPath>>, CombineQuery<TParent['query'], ToQuery<TQuery>>, CombineHash<TParent['hash'], ToHash<THash>>, CombineMeta<TMeta, TParent['meta']>, CombineState<TState, TParent['state']>, TName | TParent['matches'][number]['name']>
const TComponents extends Record<string, Component>
>(options: TOptions & CreateRouteOptions & WithComponents<TComponents> & WithParent<TParent>):
TOptions extends CreateRouteOptions
? Route<
ToName<TOptions['name']>,
Host<'', {}>,
CombinePath<TParent['path'], ToPath<TOptions['path']>>,
CombineQuery<TParent['query'], ToQuery<TOptions['query']>>,
CombineHash<TParent['hash'], ToHash<TOptions['hash']>>,
CombineMeta<ToMeta<TOptions['meta']>, TParent['meta']>,
CombineState<ToState<TOptions['state']>, 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()
Expand All @@ -116,7 +179,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 = {
Expand Down
Loading
Loading