]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
refactor: migrate guard types
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 10 Jun 2024 14:40:53 +0000 (16:40 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Mon, 10 Jun 2024 14:40:53 +0000 (16:40 +0200)
17 files changed:
packages/router/__tests__/errors.spec.ts
packages/router/__tests__/guards/beforeRouteEnter.spec.ts
packages/router/__tests__/guards/extractComponentsGuards.spec.ts
packages/router/__tests__/utils.ts
packages/router/src/RouterLink.ts
packages/router/src/RouterView.ts
packages/router/src/globalExtensions.ts
packages/router/src/index.ts
packages/router/src/matcher/types.ts
packages/router/src/navigationGuards.ts
packages/router/src/router.ts
packages/router/src/typed-routes/index.ts
packages/router/src/typed-routes/navigation-guards.ts [new file with mode: 0644]
packages/router/src/typed-routes/params.ts
packages/router/src/types/index.ts
packages/router/src/types/typeGuards.ts
packages/router/src/useApi.ts

index c176832eb1077cec2e1968a84fe3d20aec69a46e..952059ad436df514a8815322ce1613720d39c24b 100644 (file)
@@ -8,7 +8,7 @@ import {
   ErrorTypes,
 } from '../src/errors'
 import { components, tick } from './utils'
-import { RouteRecordRaw, NavigationGuard } from '../src/types'
+import type { RouteRecordRaw, NavigationGuard } from '../src'
 import type {
   RouteLocationRaw,
   RouteLocationNormalized,
index 82e05418c05dc83bc0e1bcca0fe5fd478b721fa4..0af22f070f0f9971843d3c7e1c741a9bd8c1454a 100644 (file)
@@ -1,6 +1,6 @@
 import fakePromise from 'faked-promise'
 import { createDom, noGuard, newRouter as createRouter } from '../utils'
-import { RouteRecordRaw, NavigationGuard } from '../../src/types'
+import type { RouteRecordRaw, NavigationGuard } from '../../src'
 
 const Home = { template: `<div>Home</div>` }
 const Foo = { template: `<div>Foo</div>` }
index a8bfb4d870213916ecf29c3f84a93ed439d190e1..dee5c7587f4b638a208f64e5c062a8943e8ffb7b 100644 (file)
@@ -1,9 +1,8 @@
 import { extractComponentsGuards } from '../../src/navigationGuards'
-import { RouteRecordRaw } from '../../src/types'
+import type { RouteRecordRaw, RouteRecordNormalized } from '../../src'
 import { START_LOCATION_NORMALIZED } from '../../src/location'
 import { components } from '../utils'
 import { normalizeRouteRecord } from '../../src/matcher'
-import { RouteRecordNormalized } from 'src/matcher/types'
 import { mockWarn } from 'jest-mock-warn'
 
 const beforeRouteEnter = jest.fn()
index 620757ce7fd800b5b02770982932d7932c58c61b..62f258b1ea1887ded24f109f1e63a0b49a8d0c4d 100644 (file)
@@ -1,9 +1,7 @@
 import { JSDOM, ConstructorOptions } from 'jsdom'
 import {
-  NavigationGuard,
   RouteRecordMultipleViews,
   MatcherLocation,
-  RouteLocationNormalized,
   RouteComponent,
   RouteRecordRaw,
   RouteRecordName,
@@ -17,6 +15,8 @@ import {
   Router,
   RouterView,
   RouteRecordNormalized,
+  NavigationGuard,
+  RouteLocationNormalized,
 } from '../src'
 
 export const tick = (time?: number) =>
index d2e66fa28b411d9b73c6260167acacb2b8118df7..6db1f23548fb0b98b6ec0fccc50b756a136ecfee 100644 (file)
@@ -26,19 +26,18 @@ import {
   // @ts-ignore
   ComponentOptionsMixin,
 } from 'vue'
-import {
-  isRouteLocation,
-  RouteLocationRaw,
-  VueUseOptions,
-  RouteLocation,
-  RouteLocationNormalized,
-} from './types'
+import { isRouteLocation, type VueUseOptions } from './types'
 import { isSameRouteLocationParams, isSameRouteRecord } from './location'
 import { routerKey, routeLocationKey } from './injectionSymbols'
 import { RouteRecord } from './matcher/types'
 import { NavigationFailure } from './errors'
 import { isArray, isBrowser, noop } from './utils'
 import { warn } from './warning'
+import {
+  RouteLocation,
+  RouteLocationNormalized,
+  RouteLocationRaw,
+} from './typed-routes'
 
 export interface RouterLinkOptions {
   /**
@@ -82,6 +81,7 @@ export interface RouterLinkProps extends RouterLinkOptions {
 }
 
 export interface UseLinkDevtoolsContext {
+  // TODO: loaded type ?
   route: RouteLocationNormalized & { href: string }
   isActive: boolean
   isExactActive: boolean
@@ -127,7 +127,10 @@ export function useLink(props: UseLinkOptions) {
       hasPrevious = true
     }
 
-    return router.resolve(to)
+    return router.resolve(
+      // @ts-expect-error: FIXME: errors on the name because of typed routes
+      to
+    )
   })
 
   const activeRecordIndex = computed<number>(() => {
index ad4d8f720a5004c9ad8d079ea0d236555bfe48d6..33a09b5073c5ffba7bc2ba28a7261fc0db86e3c0 100644 (file)
@@ -17,11 +17,11 @@ import {
   VNode,
   Component,
 } from 'vue'
-import {
+import type {
   RouteLocationNormalized,
   RouteLocationNormalizedLoaded,
-  RouteLocationMatched,
-} from './types'
+} from './typed-routes'
+import type { RouteLocationMatched } from './types'
 import {
   matchedRouteKey,
   viewDepthKey,
index 3689785782811364cf4e454338c0802a1a371ac8..7aa983d62c52309b8049afcec0ce2278e1d55046 100644 (file)
@@ -2,9 +2,9 @@ import type {
   NavigationGuardWithThis,
   NavigationGuard,
   RouteLocationNormalizedLoaded,
-} from './types'
-import { RouterView } from './RouterView'
-import { RouterLink } from './RouterLink'
+} from './typed-routes'
+import type { RouterView } from './RouterView'
+import type { RouterLink } from './RouterLink'
 import type { Router } from './router'
 import type { TypesConfig } from './config'
 
index 140a7318351d416d4a2c19b75d3b0de5732d25be..d93edc49fbef984fe86ab1dbbbf24df42a24ae79 100644 (file)
@@ -4,8 +4,6 @@ export { createWebHashHistory } from './history/hash'
 export { createRouterMatcher } from './matcher'
 export type { RouterMatcher } from './matcher'
 
-export type * from './typed-routes'
-
 export { parseQuery, stringifyQuery } from './query'
 export type {
   LocationQuery,
@@ -38,21 +36,22 @@ export type {
   MatcherLocationAsPath,
   LocationAsRelativeRaw,
   RouteQueryAndHash,
-  RouteLocationRaw,
-  RouteLocation,
-  RouteLocationNormalized,
-  RouteLocationNormalizedLoaded,
-  RouteParams,
-  RouteParamsRaw,
+
+  // route params
   RouteParamValue,
   RouteParamValueRaw,
+
+  // Partial route location
   RouteLocationNamedRaw,
+  // exported for backwards compat for old RouteLocationRaw
   RouteLocationPathRaw,
   RouteLocationMatched,
+
+  // extra options when navigating
   RouteLocationOptions,
+
   // route records
   _RouteRecordBase,
-  RouteRecordName,
   RouteRecordRaw,
   RouteRecordSingleView,
   RouteRecordSingleViewWithChildren,
@@ -62,11 +61,38 @@ export type {
   RouteMeta,
   RouteComponent,
   // RawRouteComponent,
-  NavigationGuard,
   NavigationGuardNext,
+} from './types'
+
+// Experimental Type Safe API
+export type {
+  // route location
+  RouteLocationRaw,
+  RouteLocation,
+  RouteLocationNormalized,
+  RouteLocationNormalizedLoaded,
+  RouteLocationResolved,
+  RouteLocationAsRelativePath,
+
+  // route records
+  RouteRecordInfo,
+  RouteRecordName,
+  RouteRecordRedirectOption,
+
+  // params
+  RouteParams,
+  RouteParamsRaw,
+  ParamValue,
+  ParamValueOneOrMore,
+  ParamValueZeroOrMore,
+  ParamValueZeroOrOne,
+
+  // navigation guards
+  NavigationGuard,
   NavigationGuardWithThis,
   NavigationHookAfter,
-} from './types'
+  NavigationGuardReturn,
+} from './typed-routes'
 
 export { createRouter } from './router'
 export type { Router, RouterOptions, RouterScrollBehavior } from './router'
index 6693a3d75f31f53b63ad0c1b7da6de6b2d13e049..774c79d48d23b95edbadc1a658b26bd0e9574e12 100644 (file)
@@ -1,6 +1,6 @@
+import type { NavigationGuard } from '../typed-routes'
 import {
   RouteRecordMultipleViews,
-  NavigationGuard,
   _RouteRecordBase,
   _RouteRecordProps,
   NavigationGuardNextCallback,
index 4bfd504b2834a3d8ae7d299085715d8e4564b795..2c981e1bd15cd59f4b97740a2bebc6086187567c 100644 (file)
@@ -1,16 +1,19 @@
 import {
-  NavigationGuard,
   NavigationGuardNext,
   NavigationGuardNextCallback,
   isRouteLocation,
   Lazy,
   RouteComponent,
   RawRouteComponent,
+
+  // NOTE: Still need to use some old types while migrating
+  RouteLocationRaw as RouteLocationRaw_OLD,
 } from './types'
+
 import type {
-  RouteLocationRaw,
   RouteLocationNormalized,
   RouteLocationNormalizedLoaded,
+  NavigationGuard,
 } from './typed-routes'
 
 import {
@@ -139,7 +142,12 @@ export function guardToPromiseFn(
   return () =>
     new Promise((resolve, reject) => {
       const next: NavigationGuardNext = (
-        valid?: boolean | RouteLocationRaw | NavigationGuardNextCallback | Error
+        valid?:
+          | boolean
+          // TODO: remove
+          | RouteLocationRaw_OLD
+          | NavigationGuardNextCallback
+          | Error
       ) => {
         if (valid === false) {
           reject(
index c7b1467f00041a35c8f55bfbc2f8f64c20169097..3be4a7ff65f681fecdae893c998cd8a29596c386 100644 (file)
@@ -1,10 +1,8 @@
 import {
   RouteRecordRaw,
-  NavigationHookAfter,
   Lazy,
   isRouteLocation,
   isRouteName,
-  NavigationGuardWithThis,
   RouteLocationOptions,
   MatcherLocationRaw,
 } from './types'
@@ -15,6 +13,9 @@ import type {
   RouteParams,
   RouteLocationNormalized,
   RouteLocationNormalizedLoaded,
+  NavigationGuardWithThis,
+  NavigationHookAfter,
+  RouteLocationResolved,
 } from './typed-routes'
 import { RouterHistory, HistoryState, NavigationType } from './history/common'
 import {
@@ -63,6 +64,12 @@ import {
 } from './injectionSymbols'
 import { addDevtools } from './devtools'
 import { _LiteralUnion } from './types/utils'
+import {
+  RouteLocationAsPathTyped,
+  RouteLocationAsRelativeTyped,
+  RouteLocationAsString,
+} from './typed-routes/route-location'
+import { RouteMap } from './typed-routes/route-map'
 
 /**
  * Internal type to define an ErrorHandler
@@ -195,7 +202,7 @@ export interface Router {
   readonly options: RouterOptions
 
   /**
-   * Allows turning off the listening of history events. This is a low level api for micro-frontends.
+   * Allows turning off the listening of history events. This is a low level api for micro-frontend.
    */
   listening: boolean
 
@@ -238,10 +245,13 @@ export interface Router {
    * @param to - Raw route location to resolve
    * @param currentLocation - Optional current location to resolve against
    */
-  resolve(
-    to: RouteLocationRaw,
+  resolve<Name extends keyof RouteMap = keyof RouteMap>(
+    to:
+      | RouteLocationAsString<RouteMap>
+      | RouteLocationAsRelativeTyped<RouteMap, Name>
+      | RouteLocationAsPathTyped<RouteMap, Name>,
     currentLocation?: RouteLocationNormalizedLoaded
-  ): RouteLocation & { href: string }
+  ): RouteLocationResolved<Name>
 
   /**
    * Programmatically navigate to a new URL by pushing an entry in the history
@@ -435,9 +445,11 @@ export function createRouter(options: RouterOptions): Router {
   }
 
   function resolve(
+    // NOTE: it's easier to by pass the type generics which are just for type inference in the resolved route
     rawLocation: RouteLocationRaw,
     currentLocation?: RouteLocationNormalizedLoaded
-  ): RouteLocation & { href: string } {
+  ): RouteLocationResolved {
+    // const resolve: Router['resolve'] = (rawLocation: RouteLocationRaw, currentLocation) => {
     // const objectLocation = routerLocationAsObject(rawLocation)
     // we create a copy to modify it later
     currentLocation = assign({}, currentLocation || currentRoute.value)
@@ -655,7 +667,7 @@ export function createRouter(options: RouterOptions): Router {
   }
 
   function pushWithRedirect(
-    to: RouteLocationRaw | RouteLocation,
+    to: RouteLocationRaw,
     redirectedFrom?: RouteLocation
   ): Promise<NavigationFailure | void | undefined> {
     const targetLocation: RouteLocation = (pendingLocation = resolve(to))
@@ -1213,6 +1225,7 @@ export function createRouter(options: RouterOptions): Router {
     removeRoute,
     hasRoute,
     getRoutes,
+    // @ts-expect-error: FIXME: types do not match
     resolve,
     options,
 
index 96fd6ac158ee4d476f30c157a73f364cb33205bc..b88c0d4d0d2becc3346572748f7476e694547535 100644 (file)
@@ -20,3 +20,10 @@ export type {
 } from './route-location'
 
 export type { RouteRecordRedirectOption } from './route-records'
+
+export type {
+  NavigationGuard,
+  NavigationGuardReturn,
+  NavigationHookAfter,
+  NavigationGuardWithThis,
+} from './navigation-guards'
diff --git a/packages/router/src/typed-routes/navigation-guards.ts b/packages/router/src/typed-routes/navigation-guards.ts
new file mode 100644 (file)
index 0000000..b4dac11
--- /dev/null
@@ -0,0 +1,110 @@
+import type { _Awaitable } from '../types/utils'
+import type { NavigationGuardNext } from '../types'
+import type {
+  _RouteLocationNormalizedLoaded,
+  RouteLocationNormalizedTypedList,
+  RouteLocationNormalizedLoadedTypedList,
+  RouteLocationAsString,
+  RouteLocationAsRelativeTypedList,
+  RouteLocationAsPathTypedList,
+  _RouteLocationNormalized,
+} from './route-location'
+import type { _RouteMapGeneric, RouteMap } from './route-map'
+import type { NavigationFailure } from '../errors'
+
+/**
+ * Return types for a Navigation Guard. Accepts a type param for the RouteMap.
+ */
+type NavigationGuardReturnTyped<RouteMap extends _RouteMapGeneric> =
+  | void
+  | Error
+  | boolean
+  | RouteLocationAsString<RouteMap>
+  | RouteLocationAsRelativeTypedList<RouteMap>[keyof RouteMap]
+  | RouteLocationAsPathTypedList<RouteMap>[keyof RouteMap]
+
+/**
+ * Return types for a Navigation Guard. Based on `TypesConfig`
+ *
+ * @see {@link TypesConfig}
+ * @see {@link NavigationGuardReturnTyped}
+ */
+export type NavigationGuardReturn = NavigationGuardReturnTyped<RouteMap>
+
+/**
+ * Typed Navigation Guard with a type parameter for `this` and another for the route map.
+ */
+export interface NavigationGuardWithThisTyped<
+  T,
+  RouteMap extends _RouteMapGeneric
+> {
+  (
+    this: T,
+    to: RouteLocationNormalizedTypedList<RouteMap>[keyof RouteMap],
+    from: RouteLocationNormalizedLoadedTypedList<RouteMap>[keyof RouteMap],
+    // intentionally not typed to make people use the return
+    next: NavigationGuardNext
+  ): _Awaitable<NavigationGuardReturnTyped<RouteMap>>
+}
+
+/**
+ * Typed Navigation Guard with a type parameter for `this`. Based on `TypesConfig`
+ * @see {@link TypesConfig}
+ * @see {@link NavigationGuardWithThisTyped}
+ */
+export interface NavigationGuardWithThis<T>
+  extends NavigationGuardWithThisTyped<T, RouteMap> {}
+
+/**
+ * In `router.beforeResolve((to) => {})`, the `to` is typed as `RouteLocationNormalizedLoaded`, not
+ * `RouteLocationNormalized` like in `router.beforeEach()`. In practice it doesn't change much as users do not rely on
+ * the difference between them but if we update the type in vue-router, we will have to update this type too.
+ * @internal
+ */
+export interface _NavigationGuardResolved {
+  (
+    this: undefined,
+    to: _RouteLocationNormalizedLoaded,
+    from: _RouteLocationNormalizedLoaded,
+    // intentionally not typed to make people use the return
+    next: NavigationGuardNext
+  ): _Awaitable<NavigationGuardReturn>
+}
+
+/**
+ * Typed Navigation Guard. Accepts a type param for the RouteMap.
+ */
+export interface NavigationGuardTyped<RouteMap extends _RouteMapGeneric> {
+  (
+    to: _RouteLocationNormalized,
+    from: _RouteLocationNormalizedLoaded,
+    // intentionally not typed to make people use the return
+    next: NavigationGuardNext
+  ): _Awaitable<NavigationGuardReturnTyped<RouteMap>>
+}
+
+/**
+ * Typed Navigation Guard. Based on `TypesConfig`.
+ * @see {@link TypesConfig}
+ * @see {@link NavigationGuardWithThisTyped}
+ */
+export type NavigationGuard = NavigationGuardTyped<RouteMap>
+
+/**
+ * Typed Navigation Hook After. Accepts a type param for the RouteMap.
+ */
+export interface NavigationHookAfterTyped<RouteMap extends _RouteMapGeneric> {
+  (
+    to: RouteLocationNormalizedTypedList<RouteMap>[keyof RouteMap],
+    from: RouteLocationNormalizedLoadedTypedList<RouteMap>[keyof RouteMap],
+    failure?: NavigationFailure | void
+  ): unknown
+}
+
+/**
+ * Typed Navigation Hook After. Based on `TypesConfig`.
+ * @see {@link TypesConfig}
+ * @see {@link NavigationHookAfterTyped}
+ */
+export interface NavigationHookAfter
+  extends NavigationHookAfterTyped<RouteMap> {}
index 15b04388567ae27a7ba231ff110e391289761ed3..4192ed4533f8bed0b2daa87079878803edb3b5ae 100644 (file)
@@ -1,5 +1,3 @@
-// TODO: refactor to ParamValueRaw and ParamValue ?
-
 /**
  * Utility type for raw and non raw params like :id+
  *
@@ -32,3 +30,8 @@ export type ParamValueZeroOrOne<isRaw extends boolean> = true extends isRaw
 export type ParamValue<isRaw extends boolean> = true extends isRaw
   ? string | number
   : string
+
+// TODO: finish this refactor
+// export type ParamValueOneOrMoreRaw = [ParamValueRaw, ...ParamValueRaw[]]
+// export type ParamValue =  string
+// export type ParamValueRaw = string | number
index 8b1d2be6861c6489c5d2a0bf002820b3ac15acd6..9bcc131d49d103ed621fde6a5ead975861349c27 100644 (file)
@@ -4,7 +4,11 @@ import { Ref, ComponentPublicInstance, Component, DefineComponent } from 'vue'
 import { RouteRecord, RouteRecordNormalized } from '../matcher/types'
 import { HistoryState } from '../history/common'
 import { NavigationFailure } from '../errors'
-import { RouteRecordRedirectOption } from '../typed-routes'
+import {
+  NavigationGuardWithThis,
+  RouteRecordRedirectOption,
+} from '../typed-routes'
+import { _Awaitable } from './utils'
 
 export type Lazy<T> = () => Promise<T>
 export type Override<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U
@@ -142,6 +146,7 @@ export interface RouteLocationPathRaw
     MatcherLocationAsPath,
     RouteLocationOptions {}
 
+// TODO: rename in next major
 export interface RouteLocationMatched extends RouteRecordNormalized {
   // components cannot be Lazy<RouteComponent>
   components: Record<string, RouteComponent> | null | undefined
@@ -484,22 +489,10 @@ export interface NavigationGuard {
   (
     // TODO: we could maybe add extra information like replace: true/false
     to: RouteLocationNormalized,
-    from: RouteLocationNormalized,
+    from: RouteLocationNormalizedLoaded,
     next: NavigationGuardNext
     // FIXME: this one shouldn't allow returning () => ...
-  ): NavigationGuardReturn | Promise<NavigationGuardReturn>
-}
-
-/**
- * {@inheritDoc NavigationGuard}
- */
-export interface NavigationGuardWithThis<T> {
-  (
-    this: T,
-    to: RouteLocationNormalized,
-    from: RouteLocationNormalized,
-    next: NavigationGuardNext
-  ): NavigationGuardReturn | Promise<NavigationGuardReturn>
+  ): _Awaitable<NavigationGuardReturn>
 }
 
 export interface NavigationHookAfter {
index 7d1f06baaca4110d18051b6f9f465550e83bacb0..97d552810a4a7807aab3d55726f643eb537a17bb 100644 (file)
@@ -1,4 +1,4 @@
-import { RouteLocationRaw, RouteRecordName } from '../typed-routes'
+import type { RouteLocationRaw } from '../typed-routes'
 
 export function isRouteLocation(route: any): route is RouteLocationRaw {
   return typeof route === 'string' || (route && typeof route === 'object')
index 853100b6004b6a8be61eb9fd381db2d44bf8b358..e473c87bd5d5f4e6e907e0971673a3afa7fc7145 100644 (file)
@@ -2,7 +2,7 @@ import { inject } from 'vue'
 import { routerKey, routeLocationKey } from './injectionSymbols'
 import { Router } from './router'
 import { RouteMap } from './typed-routes/route-map'
-import { RouteLocationNormalized } from './typed-routes'
+import { RouteLocationNormalizedLoaded } from './typed-routes'
 
 /**
  * Returns the router instance. Equivalent to using `$router` inside
@@ -18,6 +18,7 @@ export function useRouter(): Router {
  */
 export function useRoute<Name extends keyof RouteMap = keyof RouteMap>(
   _name?: Name
-): RouteLocationNormalized<Name> {
+): RouteLocationNormalizedLoaded<Name> {
+  // @ts-expect-error: FIXME: name mismatch issue
   return inject(routeLocationKey)!
 }