RouteLocationNormalized,
} from '../src/types'
-const routes: RouteRecordRaw[] = [
+const routes: Readonly<RouteRecordRaw>[] = [
{ path: '/', component: components.Home },
{ path: '/redirect', redirect: '/' },
{ path: '/foo', component: components.Foo, name: 'Foo' },
throw new Error('not handled')
} else {
// use one single record
+ // @ts-expect-error: One way or the other it failse
if (!resolved.matched) resolved.matched = record.map(normalizeRouteRecord)
// allow passing an expect.any(Array)
else if (Array.isArray(resolved.matched))
+ // @ts-expect-error: same as above
resolved.matched = resolved.matched.map(m => ({
...normalizeRouteRecord(m as any),
aliasOf: m.aliasOf,
const startCopy: MatcherLocation = {
...start,
matched: start.matched.map(m => ({
+ // @ts-expect-error: okay...
...normalizeRouteRecord(m),
aliasOf: m.aliasOf,
})) as MatcherLocation['matched'],
routeLocationKey,
routerViewLocationKey,
} from '../src/injectionSymbols'
+import { RouteLocationNormalized } from 'src'
-export function createMockedRoute(initialValue: RouteLocationNormalizedLoose) {
+export function createMockedRoute(
+ initialValue: RouteLocationNormalizedLoose | RouteLocationNormalized
+) {
const route = {} as {
[k in keyof RouteLocationNormalizedLoose]: ComputedRef<
RouteLocationNormalizedLoose[k]
>
}
- const routeRef = shallowRef(initialValue)
+ const routeRef = shallowRef<
+ RouteLocationNormalized | RouteLocationNormalizedLoose
+ >(initialValue)
- function set(newRoute: RouteLocationNormalizedLoose) {
+ function set(
+ newRoute: RouteLocationNormalizedLoose | RouteLocationNormalized
+ ) {
routeRef.value = newRoute
return nextTick()
}
createRouter,
Router,
RouterView,
+ RouteRecordNormalized,
} from '../src'
export const tick = (time?: number) =>
instances: Record<string, any>
enterCallbacks: Record<string, Function[]>
props: Record<string, _RouteRecordProps>
- aliasOf: RouteRecordViewLoose | undefined
+ aliasOf: RouteRecordNormalized | RouteRecordViewLoose | undefined
children?: RouteRecordRaw[]
components: Record<string, RouteComponent> | null | undefined
}
_RemoveUntilClosingPar,
} from './types/paths'
export type { RouteNamedMap } from './types/named'
+export type { Config, RouterTyped } from './typedRouter'
export { createRouter } from './router'
export type { Router, RouterOptions, RouterScrollBehavior } from './router'
}
function isSameRouteLocationParamsValue(
- a: RouteParamValue | RouteParamValue[],
- b: RouteParamValue | RouteParamValue[]
+ a: RouteParamValue | readonly RouteParamValue[],
+ b: RouteParamValue | readonly RouteParamValue[]
): boolean {
return Array.isArray(a)
? isEquivalentArray(a, b)
import { Token, TokenType } from './pathTokenizer'
import { assign } from '../utils'
-export type PathParams = Record<string, string | string[]>
+export type PathParams = Record<string, string | readonly string[]>
/**
* A param in a url like `/users/:id`
path += token.value
} else if (token.type === TokenType.Param) {
const { value, repeatable, optional } = token
- const param: string | string[] = value in params ? params[value] : ''
+ const param: string | readonly string[] =
+ value in params ? params[value] : ''
if (Array.isArray(param) && !repeatable)
throw new Error(
`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`
)
- const text: string = Array.isArray(param) ? param.join('/') : param
+ const text: string = Array.isArray(param)
+ ? (param as string[]).join('/')
+ : (param as string)
if (!text) {
if (optional) {
// if we have more than one optional param like /:a?-static and there are more segments, we don't need to
RouteLocationOptions,
MatcherLocationRaw,
RouteParams,
+ RouteLocationNamedRaw,
} from './types'
import { RouterHistory, HistoryState, NavigationType } from './history/common'
import {
routerViewLocationKey,
} from './injectionSymbols'
import { addDevtools } from './devtools'
+import { RouteNamedMap, RouteNamedMapGeneric } from './types/named'
/**
* Internal type to define an ErrorHandler
* Returns the {@link RouteLocation normalized version} of a
* {@link RouteLocationRaw route location}. Also includes an `href` property
* that includes any existing `base`. By default the `currentLocation` used is
- * `route.currentRoute` and should only be overriden in advanced use cases.
+ * `route.currentRoute` and should only be overridden in advanced use cases.
*
* @param to - Raw route location to resolve
* @param currentLocation - Optional current location to resolve against
*
* @param to - Route location to navigate to
*/
- push(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>
+ push<
+ RouteMap extends RouteNamedMapGeneric = RouteNamedMap<Options['routes']>,
+ Name extends keyof RouteMap = keyof RouteNamedMap<Options['routes']>
+ >(
+ to: RouteNamedMapGeneric extends RouteMap
+ ? RouteLocationRaw
+ : RouteLocationNamedRaw<RouteMap, Name>
+ ): Promise<NavigationFailure | void | undefined>
/**
* Programmatically navigate to a new URL by replacing the current entry in
*
* @param to - Route location to navigate to
*/
- replace(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>
+ replace<
+ RouteMap extends RouteNamedMapGeneric = RouteNamedMap<Options['routes']>,
+ Name extends keyof RouteMap = keyof RouteNamedMap<Options['routes']>
+ >(
+ to: RouteNamedMapGeneric extends RouteMap
+ ? RouteLocationRaw
+ : RouteLocationNamedRaw<RouteMap, Name>
+ ): Promise<NavigationFailure | void | undefined>
+
/**
* Go back in history if possible by calling `history.back()`. Equivalent to
* `router.go(-1)`.
}
}
- function push(to: RouteLocationRaw | RouteLocation) {
+ function push(to: RouteLocationRaw) {
return pushWithRedirect(to)
}
- function replace(to: RouteLocationRaw | RouteLocationNormalized) {
+ function replace(to: RouteLocationRaw) {
return push(assign(locationAsObject(to), { replace: true }))
}
resolve,
options,
+ // @ts-expect-error: FIXME: can't type this one correctly without too much hussle
push,
+ // @ts-expect-error: same
replace,
go,
back: () => go(-1),
import { RouteRecord, RouteRecordNormalized } from '../matcher/types'
import { HistoryState } from '../history/common'
import { NavigationFailure } from '../errors'
-import { NamedLocationMap } from './named'
+import { RouteNamedMapGeneric } from './named'
export { NamedLocationMap, ExtractNamedRoutes, ExtractRoutes } from './named'
* @internal
*/
export type RouteParamValueRaw = RouteParamValue | number | null | undefined
-export type RouteParams = Record<string, RouteParamValue | RouteParamValue[]>
+export type RouteParams = Record<
+ string,
+ RouteParamValue | readonly RouteParamValue[]
+>
export type RouteParamsRaw = Record<
string,
- RouteParamValueRaw | Exclude<RouteParamValueRaw, null | undefined>[]
+ RouteParamValueRaw | readonly Exclude<RouteParamValueRaw, null | undefined>[]
>
/**
* @internal
*/
export interface LocationAsRelativeRaw<
- T extends keyof NamedLocationMap = keyof NamedLocationMap
+ RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
+ Name extends keyof RouteMap = keyof RouteMap
> {
- name?: {} extends NamedLocationMap ? RouteRecordName : T
- params?: {} extends NamedLocationMap ? RouteParamsRaw : NamedLocationMap[T]
+ name?: RouteNamedMapGeneric extends RouteMap ? RouteRecordName : Name
+ params?: RouteNamedMapGeneric extends RouteMap
+ ? RouteParamsRaw
+ : RouteMap[Name]['paramsRaw']
}
-export interface LocationAsRelative {
- params?: RouteParams
+export interface LocationAsRelative<
+ RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
+ Name extends keyof RouteMap = keyof RouteMap
+> {
+ params?: RouteNamedMapGeneric extends RouteMap
+ ? RouteParams
+ : RouteMap[Name]['params']
}
export interface RouteLocationOptions {
*/
export type RouteLocationRaw =
| string
- | (RouteQueryAndHash & LocationAsPath & RouteLocationOptions)
- | (RouteQueryAndHash & LocationAsRelativeRaw & RouteLocationOptions)
+ | RouteLocationPathRaw
+ | RouteLocationNamedRaw
+
+export type RouteLocationNamedRaw<
+ RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
+ Name extends keyof RouteMap = keyof RouteMap
+> = RouteQueryAndHash &
+ LocationAsRelativeRaw<RouteMap, Name> &
+ RouteLocationOptions
+
+export type RouteLocationPathRaw =
+ | RouteQueryAndHash & LocationAsPath & RouteLocationOptions
export interface RouteLocationMatched extends RouteRecordNormalized {
// components cannot be Lazy<RouteComponent>
* Common properties among all kind of {@link RouteRecordRaw}
* @internal
*/
-export interface _RouteRecordBase<Path extends string = string>
- extends PathParserOptions {
+export interface _RouteRecordBase extends PathParserOptions {
/**
* Path of the record. Should start with `/` unless the record is the child of
* another record.
*
* @example `/users/:id` matches `/users/1` as well as `/users/posva`.
*/
- path: Path
+ path: string
/**
* Where to redirect if the route is directly matched. The redirection happens
/**
* Route Record defining one single component with the `component` option.
*/
-export interface RouteRecordSingleView<Path extends string = string>
- extends _RouteRecordBase<Path> {
+export interface RouteRecordSingleView extends _RouteRecordBase {
/**
* Component to display when the URL matches this route.
*/
/**
* Route Record defining one single component with a nested view.
*/
-export interface RouteRecordSingleViewWithChildren<Path extends string = string>
- extends _RouteRecordBase<Path> {
+export interface RouteRecordSingleViewWithChildren extends _RouteRecordBase {
/**
* Component to display when the URL matches this route.
*/
/**
* Route Record defining multiple named components with the `components` option.
*/
-export interface RouteRecordMultipleViews<Path extends string = string>
- extends _RouteRecordBase<Path> {
+export interface RouteRecordMultipleViews extends _RouteRecordBase {
/**
* Components to display when the URL matches this route. Allow using named views.
*/
/**
* Route Record defining multiple named components with the `components` option and children.
*/
-export interface RouteRecordMultipleViewsWithChildren<
- Path extends string = string
-> extends _RouteRecordBase<Path> {
+export interface RouteRecordMultipleViewsWithChildren extends _RouteRecordBase {
/**
* Components to display when the URL matches this route. Allow using named views.
*/
components?: Record<string, RawRouteComponent> | null | undefined
component?: never
- children: RouteRecordRaw[]
+ children: Readonly<RouteRecordRaw>[]
/**
* Allow passing down params as props to the component rendered by
* Route Record that defines a redirect. Cannot have `component` or `components`
* as it is never rendered.
*/
-export interface RouteRecordRedirect<Path extends string = string>
- extends _RouteRecordBase<Path> {
+export interface RouteRecordRedirect extends _RouteRecordBase {
redirect: RouteRecordRedirectOption
component?: never
components?: never
}
-export type RouteRecordRaw<Path extends string = string> =
- | RouteRecordSingleView<Path>
- | RouteRecordSingleViewWithChildren<Path>
- | RouteRecordMultipleViews<Path>
- | RouteRecordMultipleViewsWithChildren<Path>
- | RouteRecordRedirect<Path>
+export type RouteRecordRaw =
+ | RouteRecordSingleView
+ | RouteRecordSingleViewWithChildren
+ | RouteRecordMultipleViews
+ | RouteRecordMultipleViewsWithChildren
+ | RouteRecordRedirect
/**
* Initial route location where the router is. Can be used in navigation guards
-import type { RouteRecordRaw } from '.'
+import type { RouteParams, RouteParamsRaw, RouteRecordRaw } from '.'
import type { Router } from '../router'
-import { JoinPath, ParamsFromPath } from './paths'
+import type { JoinPath, ParamsFromPath, ParamsRawFromPath } from './paths'
/**
* This will flat the routes into an object with `key` === `router.name`
children?: infer Children
}
? Path extends string
- ? (Name extends string
+ ? (Name extends string | symbol
? {
- [N in Name]: ParamsFromPath<JoinPath<Prefix, Path>>
+ [N in Name]: {
+ // name: N
+ params: ParamsFromPath<JoinPath<Prefix, Path>>
+ // TODO: ParamsRawFromPath
+ paramsRaw: ParamsRawFromPath<JoinPath<Prefix, Path>>
+ path: JoinPath<Prefix, Path>
+ }
}
: {
// NO_NAME: 1
}) &
+ // Recurse children
(Children extends Readonly<RouteRecordRaw[]>
? RouteNamedMap<Children, JoinPath<Prefix, Path>>
: {
: {
// EMPTY: 1
}) &
- RouteNamedMap<Rest>
+ RouteNamedMap<Rest, Prefix>
: never // R must be a valid route record
: {
// END: 1
}
+
+export type RouteNamedMapGeneric = Record<
+ string | symbol | number,
+ // TODO: use RouteParams, RouteParamRaw
+ {
+ params: RouteParams
+ paramsRaw: RouteParamsRaw
+ path: string
+ }
+>
+import { RouteParams, RouteParamsRaw, RouteParamValueRaw } from '.'
+
/**
* Extract an object of params given a path like `/users/:id`.
*
* ```
*/
export type ParamsFromPath<P extends string = string> = string extends P
- ? PathParams // Generic version
- : _ExtractParamsPath<_RemoveRegexpFromParam<P>>
+ ? RouteParams // Generic version
+ : _ExtractParamsPath<_RemoveRegexpFromParam<P>, false>
-/**
- * Generic possible params from a path (after parsing).
- */
-export type PathParams = Record<
- string,
- string | readonly string[] | undefined | null
->
+export type ParamsRawFromPath<P extends string = string> = string extends P
+ ? RouteParamsRaw // Generic version
+ : _ExtractParamsPath<_RemoveRegexpFromParam<P>, true>
/**
* Possible param modifiers.
*
* @internal
*/
-export type _ExtractParamsPath<P extends string> =
- P extends `${string}{${infer PP}}${infer Rest}`
- ? (PP extends `${infer N}${_ParamModifier}`
- ? PP extends `${N}${infer M}`
- ? M extends _ParamModifier
- ? _ParamToObject<N, M>
- : never
+export type _ExtractParamsPath<
+ P extends string,
+ isRaw extends boolean
+> = P extends `${string}{${infer PP}}${infer Rest}`
+ ? (PP extends `${infer N}${_ParamModifier}`
+ ? PP extends `${N}${infer M}`
+ ? M extends _ParamModifier
+ ? _ParamToObject<N, M, isRaw>
: never
- : _ParamToObject<PP, ''>) &
- _ExtractParamsPath<Rest>
- : {}
+ : never
+ : _ParamToObject<PP, '', isRaw>) &
+ _ExtractParamsPath<Rest, isRaw>
+ : {}
/**
* Gets the possible type of a param based on its modifier M.
* @internal
*/
export type _ModifierParamValue<
- M extends _ParamModifier | '' = _ParamModifier | ''
+ M extends _ParamModifier | '' = _ParamModifier | '',
+ isRaw extends boolean = false
> = '' extends M
- ? string
+ ? _ParamValue<isRaw>
: '+' extends M
- ? readonly [string, ...string[]]
+ ? _ParamValueOneOrMore<isRaw>
: '*' extends M
- ? readonly string[] | undefined | null
+ ? _ParamValueZeroOrMore<isRaw>
: '?' extends M
- ? string | undefined | null
+ ? _ParamValueZeroOrOne<isRaw>
: never
+export type _ParamValueOneOrMore<isRaw extends boolean> = true extends isRaw
+ ? readonly [string | number, ...(string | number)[]]
+ : readonly [string, ...string[]]
+
+export type _ParamValueZeroOrMore<isRaw extends boolean> = true extends isRaw
+ ? readonly (string | number)[] | undefined | null
+ : readonly string[] | undefined | null
+
+export type _ParamValueZeroOrOne<isRaw extends boolean> = true extends isRaw
+ ? RouteParamValueRaw
+ : string
+
+export type _ParamValue<isRaw extends boolean> = true extends isRaw
+ ? string | number
+ : string
+
/**
* Given a param name N and its modifier M, creates a param object for the pair.
*
*/
export type _ParamToObject<
N extends string,
- M extends _ParamModifier | ''
+ M extends _ParamModifier | '',
+ isRaw extends boolean
> = M extends '?' | '*'
? {
- [K in N]?: _ModifierParamValue<M>
+ [K in N]?: _ModifierParamValue<M, isRaw>
}
: {
- [K in N]: _ModifierParamValue<M>
+ [K in N]: _ModifierParamValue<M, isRaw>
}
/**
-import { RouteParams, RouteComponent, RouteParamsRaw } from '../types'
+import {
+ RouteParams,
+ RouteComponent,
+ RouteParamsRaw,
+ RouteParamValueRaw,
+} from '../types'
export * from './env'
for (const key in params) {
const value = params[key]
- newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value)
+ newParams[key] = Array.isArray(value)
+ ? value.map(fn)
+ : fn(value as Exclude<RouteParamValueRaw, any[]>)
}
return newParams
-export * from '../dist/vue-router'
+// export * from '../dist/vue-router'
+export * from '../src'
export function describe(_name: string, _fn: () => void): void
export function expectType<T>(value: T): void
import {
- ExtractNamedRoutes,
- Router,
- ExtractRoutes,
createRouter,
createWebHistory,
RouteRecordRaw,
expectType,
RouteNamedMap,
+ RouterTyped,
+ Router,
+ RouteLocationRaw,
} from './index'
import { DefineComponent } from 'vue'
import { JoinPath } from 'src/types/paths'
-declare const Comp: DefineComponent
declare const component: DefineComponent
declare const components: { default: DefineComponent }
-const routes = [
- {
- path: 'my-path',
- name: 'test',
- component: Comp,
- },
- {
- path: 'my-path',
- name: 'my-other-path',
- component: Comp,
- },
- {
- path: 'random',
- name: 'tt',
- children: [
- {
- path: 'random-child',
- name: 'random-child',
- component: Comp,
- },
- ],
- },
- {
- name: '1',
- children: [
- {
- name: '2',
- children: [
- {
- name: '3',
- children: [{ name: '4' }, { path: '', children: [{ name: '5' }] }],
- },
- ],
- },
- ],
- },
-] as const
-
-export function defineRoutes<
- Path extends string,
- Routes extends Readonly<RouteRecordRaw<Path>[]>
->(routes: Routes): Routes {
- return routes
-}
+const routeName = Symbol()
const r2 = createRouter({
history: createWebHistory(),
routes: [
{ path: '/users/:id', name: 'UserDetails', component },
- { path: '/no-name', /* no name */ component },
+ { path: '/no-name', /* no name */ components },
{
path: '/nested',
name: 'nested',
},
],
},
+ { path: ':opt?', name: 'optional', component },
+ // still skipped
+ { path: 'other', name: routeName, component },
],
},
] as const,
})
+const methods = ['push', 'replace'] as const
+for (const method of methods) {
+ r2.push({ name: 'UserDetails' })
+ r2.replace({ name: 'UserDetails' })
+
+ // accepts missing params because of relative locations is valid
+ r2[method]({ name: 'UserDetails' })
+ // @ts-expect-error: but expects a correct id
+ r2[method]({ name: 'UserDetails', params: {} })
+ // @ts-expect-error: no invalid params
+ r2[method]({ name: 'UserDetails', params: { id: '2', nope: 'oops' } })
+ // other options are valid
+ r2[method]({ name: 'UserDetails', query: { valid: 'true' }, replace: true })
+ r2[method]({ name: 'UserDetails', params: { id: '2' } })
+ // accepts numbers
+ r2[method]({ name: 'UserDetails', params: { id: 2 } })
+ // @ts-expect-error: fails an null
+ r2[method]({ name: 'UserDetails', params: { id: null } })
+ // @ts-expect-error: and undefined
+ r2[method]({ name: 'UserDetails', params: { id: undefined } })
+ // nested params work too
+ r2[method]({ name: 'nested-c', params: { a: '2', c: '3' } })
+ r2[method]({ name: 'optional' })
+ // optional params are more flexible
+ r2[method]({ name: 'optional', params: {} })
+ r2[method]({ name: 'optional', params: { opt: 'hey' } })
+ r2[method]({ name: 'optional', params: { opt: null } })
+ r2[method]({ name: 'optional', params: { opt: undefined } })
+ // works with symbols too
+ r2[method]({ name: routeName })
+ // @ts-expect-error: but not other symbols
+ r2[method]({ name: Symbol() })
+ // any path is still valid
+ r2[method]('/path')
+ // relative push can have any of the params
+ r2[method]({ params: { a: 2 } })
+ r2[method]({ params: {} })
+ r2[method]({ params: { opt: 'hey' } })
+}
+
+r2.push({} as unknown as RouteLocationRaw)
+r2.replace({} as unknown as RouteLocationRaw)
+
+// createMap(r2.options.routes, true).
+
function joinPath<A extends string, B extends string>(
prefix: A,
path: B
expectType<'/:a'>(joinPath('/nested', '/:a'))
expectType<{
- UserDetails: { id: string }
- nested: {}
- 'nested-a': { a: string }
- 'nested-c': { a: string; c: string }
+ UserDetails: { params: { id: string }; path: '/users/:id' }
+ nested: { params: {}; path: '/nested' }
+ 'nested-a': { params: { a: string }; path: '/nested/:a' }
+ 'nested-c': { params: { a: string; c: string }; path: '/nested/:a/b/:c' }
}>(createMap(r2.options.routes))
expectType<{
- UserDetails: { nope: string }
+ UserDetails: { params: { nope: string } }
// @ts-expect-error
}>(createMap(r2.options.routes))
expectType<{
- 'nested-c': { a: string; d: string }
+ UserDetails: { path: '/users' }
// @ts-expect-error
}>(createMap(r2.options.routes))
expectType<{
- nope: {}
+ 'nested-c': { path: '/' }
+ // @ts-expect-error
+}>(createMap(r2.options.routes))
+expectType<{
+ 'nested-c': { params: { a: string; d: string } }
+ // @ts-expect-error
+}>(createMap(r2.options.routes))
+expectType<{
+ nope: { params: {} }
// @ts-expect-error
}>(createMap(r2.options.routes))
-declare const typed: ExtractNamedRoutes<typeof routes>
-
-typed['my-other-path']
-typed['random-child']
-typed.test
-typed.tt
-typed[1]
-typed[2]
-typed[3]
-typed[4]
-typed[5]
-//@ts-expect-error
-typed['non-existing']
-
-declare module './index' {
- interface NamedLocationMap {
- 'my-other-path': {
- id: string
- }
+declare module '../src' {
+ // declare module '../dist/vue-router' {
+ export interface Config {
+ Router: typeof r2
}
}
-declare const router: Router
-
-router.push({
- name: 'my-other-path',
- params: {
- id: '222',
- // @ts-expect-error does not exist
- nonExistent: '22',
- },
-})
-
-router.push({
- // @ts-expect-error location name does not exist
- name: 'random-location',
-})
-
-const otherRouter = createRouter({
- history: {} as any,
- routes: [{ path: 'e', name: 'test', component: Comp }] as const,
-})
-
-declare const otherRoutes: ExtractRoutes<typeof otherRouter>
+function getTypedRouter(): RouterTyped {
+ return {} as any
+}
-otherRoutes.test
+const typedRouter = getTypedRouter()
+// this one is true if we comment out the line with Router: typeof r2
+// expectType<Router>(typedRouter)
+expectType<typeof r2>(typedRouter)
+typedRouter.push({ name: 'UserDetails' })
// @ts-expect-error
-otherRoutes.test2
+typedRouter.push({ name: 'nope' })