export type {
MatcherQueryParams,
MatcherQueryParamsValue,
-} from './route-resolver/resolver'
+} from './route-resolver/resolver-abstract'
export {
MatcherPatternPathDynamic,
MatcherPatternPathStatic,
MatcherPatternPathStar,
-} from './route-resolver/matcher-pattern'
+} from './route-resolver/matchers/matcher-pattern'
export type {
MatcherPattern,
MatcherPatternHash,
MatcherPatternPath,
MatcherPatternQuery,
-} from './route-resolver/matcher-pattern'
+} from './route-resolver/matchers/matcher-pattern'
+++ /dev/null
-export { createCompiledMatcher } from './resolver'
+++ /dev/null
-import type { LocationQueryRaw } from '../../query'
-import type { RecordName } from './resolver'
-
-// FIXME: rename to ResolverLocation... instead of MatcherLocation... since they are returned by a resolver
-
-/**
- * Generic object of params that can be passed to a matcher.
- */
-export type MatcherParamsFormatted = Record<string, unknown>
-
-/**
- * Empty object in TS.
- */
-export type EmptyParams = Record<PropertyKey, never>
-
-export interface MatcherLocationAsNamed {
- name: RecordName
- // FIXME: should this be optional?
- params: MatcherParamsFormatted
- query?: LocationQueryRaw
- hash?: string
-
- /**
- * @deprecated This is ignored when `name` is provided
- */
- path?: undefined
-}
-
-export interface MatcherLocationAsPathRelative {
- path: string
- query?: LocationQueryRaw
- hash?: string
-
- /**
- * @deprecated This is ignored when `path` is provided
- */
- name?: undefined
- /**
- * @deprecated This is ignored when `path` (instead of `name`) is provided
- */
- params?: undefined
-}
-
-// TODO: does it make sense to support absolute paths objects?
-
-export interface MatcherLocationAsPathAbsolute
- extends MatcherLocationAsPathRelative {
- path: `/${string}`
-}
-
-export interface MatcherLocationAsRelative {
- params?: MatcherParamsFormatted
- query?: LocationQueryRaw
- hash?: string
-
- /**
- * @deprecated This location is relative to the next parameter. This `name` will be ignored.
- */
- name?: undefined
- /**
- * @deprecated This location is relative to the next parameter. This `path` will be ignored.
- */
- path?: undefined
-}
import { describe, expect, it } from 'vitest'
import { defineComponent } from 'vue'
-import { RouteComponent, RouteMeta, RouteRecordRaw } from '../types'
-import { NEW_stringifyURL } from '../location'
-import { mockWarn } from '../../__tests__/vitest-mock-warn'
+import { RouteComponent, RouteMeta, RouteRecordRaw } from '../../types'
+import { NEW_stringifyURL } from '../../location'
+import { mockWarn } from '../../../__tests__/vitest-mock-warn'
import {
- createCompiledMatcher,
type MatcherLocationRaw,
- type NEW_MatcherRecordRaw,
- type NEW_LocationResolved,
+ type ResolverLocationResolved,
type NEW_MatcherRecord,
NO_MATCH_LOCATION,
-} from './resolver'
+} from './resolver-abstract'
+import { type NEW_MatcherRecordRaw } from './resolver-dynamic'
+import { createCompiledMatcher } from './resolver-dynamic'
import { miss } from './matchers/errors'
-import { MatcherPatternPath, MatcherPatternPathStatic } from './matcher-pattern'
-import { EXPERIMENTAL_RouterOptions } from '../experimental/router'
-import { stringifyQuery } from '../query'
-import type {
- MatcherLocationAsNamed,
- MatcherLocationAsPathAbsolute,
-} from './matcher-location'
+import {
+ MatcherPatternPath,
+ MatcherPatternPathStatic,
+} from './matchers/matcher-pattern'
+import { EXPERIMENTAL_RouterOptions } from '../router'
+import { stringifyQuery } from '../../query'
+import type { ResolverLocationAsPathAbsolute } from './resolver-abstract'
+import type { ResolverLocationAsNamed } from './resolver-abstract'
// TODO: should be moved to a different test file
// used to check backward compatible paths
import {
PATH_PARSER_OPTIONS_DEFAULTS,
PathParams,
tokensToParser,
-} from '../matcher/pathParserRanker'
-import { tokenizePath } from '../matcher/pathTokenizer'
-import { mergeOptions } from '../utils'
+} from '../../matcher/pathParserRanker'
+import { tokenizePath } from '../../matcher/pathTokenizer'
+import { mergeOptions } from '../../utils'
// FIXME: this type was removed, it will be a new one once a dynamic resolver is implemented
export interface EXPERIMENTAL_RouteRecordRaw extends NEW_MatcherRecordRaw {
function isMatcherLocationResolved(
location: unknown
- ): location is NEW_LocationResolved<NEW_MatcherRecord> {
+ ): location is ResolverLocationResolved<NEW_MatcherRecord> {
return !!(
location &&
typeof location === 'object' &&
toLocation: Exclude<MatcherLocationRaw, string> | `/${string}`,
expectedLocation: Partial<MatcherResolvedLocation>,
fromLocation:
- | NEW_LocationResolved<NEW_MatcherRecord>
+ | ResolverLocationResolved<NEW_MatcherRecord>
// absolute locations only that can be resolved for convenience
| `/${string}`
- | MatcherLocationAsNamed
- | MatcherLocationAsPathAbsolute = START_LOCATION
+ | ResolverLocationAsNamed
+ | ResolverLocationAsPathAbsolute = START_LOCATION
) {
const records = (Array.isArray(record) ? record : [record]).map(
(record): NEW_MatcherRecordRaw =>
-import { decode, MatcherQueryParams } from './resolver'
-import { EmptyParams, MatcherParamsFormatted } from './matcher-location'
-import { miss } from './matchers/errors'
+import { decode, MatcherQueryParams } from '../resolver-abstract'
+import { miss } from './errors'
/**
* Base interface for matcher patterns that extract params from a URL.
*/
export interface MatcherPatternHash<
TParams extends MatcherParamsFormatted = MatcherParamsFormatted,
-> extends MatcherPattern<string, TParams> {}
+> extends MatcherPattern<string, TParams> {} /**
+ * Generic object of params that can be passed to a matcher.
+ */
+export type MatcherParamsFormatted = Record<string, unknown> /**
+ * Empty object in TS.
+ */
+export type EmptyParams = Record<PropertyKey, never>
-import { EmptyParams } from '../matcher-location'
+import { EmptyParams } from './matcher-pattern'
import {
MatcherPatternPath,
MatcherPatternQuery,
MatcherPatternHash,
-} from '../matcher-pattern'
-import { NEW_MatcherRecord } from '../resolver'
+} from './matcher-pattern'
+import { NEW_MatcherRecord } from '../resolver-abstract'
import { invalid, miss } from './errors'
export const ANY_PATH_PATTERN_MATCHER: MatcherPatternPath<{
--- /dev/null
+import { type LocationQuery, type LocationQueryRaw } from '../../query'
+import { warn } from '../../warning'
+import {
+ encodeQueryValue as _encodeQueryValue,
+ encodeParam,
+} from '../../encoding'
+import type { MatcherParamsFormatted } from './matchers/matcher-pattern'
+import { _RouteRecordProps } from '../../typed-routes'
+import { NEW_MatcherDynamicRecord } from './resolver-dynamic'
+
+/**
+ * Allowed types for a matcher name.
+ */
+export type RecordName = string | symbol
+
+/**
+ * Manage and resolve routes. Also handles the encoding, decoding, parsing and
+ * serialization of params, query, and hash.
+ *
+ * - `TMatcherRecordRaw` represents the raw record type passed to {@link addMatcher}.
+ * - `TMatcherRecord` represents the normalized record type returned by {@link getRecords}.
+ */
+export interface NEW_RouterResolver_Base<TRecord> {
+ /**
+ * Resolves an absolute location (like `/path/to/somewhere`).
+ *
+ * @param absoluteLocation - The absolute location to resolve.
+ * @param currentLocation - This value is ignored and should not be passed if the location is absolute.
+ */
+ resolve(
+ absoluteLocation: `/${string}`,
+ currentLocation?: undefined
+ ): ResolverLocationResolved<TRecord>
+
+ /**
+ * Resolves a string location relative to another location. A relative location can be `./same-folder`,
+ * `../parent-folder`, `same-folder`, or even `?page=2`.
+ */
+ resolve(
+ relativeLocation: string,
+ currentLocation: ResolverLocationResolved<TRecord>
+ ): ResolverLocationResolved<TRecord>
+
+ /**
+ * Resolves a location by its name. Any required params or query must be passed in the `options` argument.
+ */
+ resolve(
+ location: ResolverLocationAsNamed,
+ // TODO: is this useful?
+ currentLocation?: undefined
+ // currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
+ ): ResolverLocationResolved<TRecord>
+
+ /**
+ * Resolves a location by its absolute path (starts with `/`). Any required query must be passed.
+ * @param location - The location to resolve.
+ */
+ resolve(
+ location: ResolverLocationAsPathAbsolute,
+ // TODO: is this useful?
+ currentLocation?: undefined
+ // currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
+ ): ResolverLocationResolved<TRecord>
+
+ resolve(
+ location: ResolverLocationAsPathRelative,
+ currentLocation: ResolverLocationResolved<TRecord>
+ ): ResolverLocationResolved<TRecord>
+
+ // NOTE: in practice, this overload can cause bugs. It's better to use named locations
+
+ /**
+ * Resolves a location relative to another location. It reuses existing properties in the `currentLocation` like
+ * `params`, `query`, and `hash`.
+ */
+ resolve(
+ relativeLocation: ResolverLocationAsRelative,
+ currentLocation: ResolverLocationResolved<TRecord>
+ ): ResolverLocationResolved<TRecord>
+
+ /**
+ * Get a list of all resolver records.
+ * Previously named `getRoutes()`
+ */
+ getRecords(): TRecord[]
+
+ /**
+ * Get a resolver record by its name.
+ * Previously named `getRecordMatcher()`
+ */
+ getRecord(name: RecordName): TRecord | undefined
+}
+
+/**
+ * Allowed location objects to be passed to {@link NEW_RouterResolver['resolve']}
+ */
+export type MatcherLocationRaw =
+ // | `/${string}`
+ | string
+ | ResolverLocationAsNamed
+ | ResolverLocationAsPathAbsolute
+ | ResolverLocationAsPathRelative
+ | ResolverLocationAsRelative
+
+/**
+ * Returned location object by {@link NEW_RouterResolver['resolve']}.
+ * It contains the resolved name, params, query, hash, and matched records.
+ */
+export interface ResolverLocationResolved<TMatched> {
+ name: RecordName
+ params: MatcherParamsFormatted
+
+ fullPath: string
+ path: string
+ query: LocationQuery
+ hash: string
+
+ matched: TMatched[]
+}
+
+export type MatcherPathParamsValue = string | null | string[]
+/**
+ * Params in a string format so they can be encoded/decoded and put into a URL.
+ */
+export type MatcherPathParams = Record<string, MatcherPathParamsValue>
+
+// TODO: move to matcher-pattern
+export type MatcherQueryParamsValue = string | null | Array<string | null>
+export type MatcherQueryParams = Record<string, MatcherQueryParamsValue>
+
+/**
+ * Apply a function to all properties in an object. It's used to encode/decode params and queries.
+ * @internal
+ */
+export function applyFnToObject<R>(
+ fn: (v: string | number | null | undefined) => R,
+ params: MatcherPathParams | LocationQuery | undefined
+): Record<string, R | R[]> {
+ const newParams: Record<string, R | R[]> = {}
+
+ for (const key in params) {
+ const value = params[key]
+ newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value)
+ }
+
+ return newParams
+}
+
+/**
+ * Decode text using `decodeURIComponent`. Returns the original text if it
+ * fails.
+ *
+ * @param text - string to decode
+ * @returns decoded string
+ */
+export function decode(text: string | number): string
+export function decode(text: null | undefined): null
+export function decode(text: string | number | null | undefined): string | null
+export function decode(
+ text: string | number | null | undefined
+): string | null {
+ if (text == null) return null
+ try {
+ return decodeURIComponent('' + text)
+ } catch (err) {
+ __DEV__ && warn(`Error decoding "${text}". Using original value`)
+ }
+ return '' + text
+}
+// TODO: just add the null check to the original function in encoding.ts
+
+interface FnStableNull {
+ (value: null | undefined): null
+ (value: string | number): string
+ // needed for the general case and must be last
+ (value: string | number | null | undefined): string | null
+}
+
+// function encodeParam(text: null | undefined, encodeSlash?: boolean): null
+// function encodeParam(text: string | number, encodeSlash?: boolean): string
+// function encodeParam(
+// text: string | number | null | undefined,
+// encodeSlash?: boolean
+// ): string | null
+// function encodeParam(
+// text: string | number | null | undefined,
+// encodeSlash = true
+// ): string | null {
+// if (text == null) return null
+// text = encodePath(text)
+// return encodeSlash ? text.replace(SLASH_RE, '%2F') : text
+// }
+
+// @ts-expect-error: overload are not correctly identified
+const encodeQueryValue: FnStableNull =
+ // for ts
+ value => (value == null ? null : _encodeQueryValue(value))
+
+// // @ts-expect-error: overload are not correctly identified
+// const encodeQueryKey: FnStableNull =
+// // for ts
+// value => (value == null ? null : _encodeQueryKey(value))
+
+/**
+ * Common properties for a location that couldn't be matched. This ensures
+ * having the same name while having a `path`, `query` and `hash` that change.
+ */
+export const NO_MATCH_LOCATION = {
+ name: __DEV__ ? Symbol('no-match') : Symbol(),
+ params: {},
+ matched: [],
+} satisfies Omit<
+ ResolverLocationResolved<unknown>,
+ 'path' | 'hash' | 'query' | 'fullPath'
+>
+
+/**
+ * Normalized version of a {@link NEW_MatcherRecordRaw} record.
+ */
+export interface NEW_MatcherRecord extends NEW_MatcherDynamicRecord {}
+
+// FIXME: move somewhere else
+/**
+ * Tagged template helper to encode params into a path. Doesn't work with null
+ */
+export function pathEncoded(
+ parts: TemplateStringsArray,
+ ...params: Array<string | number | (string | number)[]>
+): string {
+ return parts.reduce((result, part, i) => {
+ return (
+ result +
+ part +
+ (Array.isArray(params[i])
+ ? params[i].map(encodeParam).join('/')
+ : encodeParam(params[i]))
+ )
+ })
+}
+export interface ResolverLocationAsNamed {
+ name: RecordName
+ // FIXME: should this be optional?
+ params: MatcherParamsFormatted
+ query?: LocationQueryRaw
+ hash?: string
+
+ /**
+ * @deprecated This is ignored when `name` is provided
+ */
+ path?: undefined
+}
+export interface ResolverLocationAsPathRelative {
+ path: string
+ query?: LocationQueryRaw
+ hash?: string
+
+ /**
+ * @deprecated This is ignored when `path` is provided
+ */
+ name?: undefined
+ /**
+ * @deprecated This is ignored when `path` (instead of `name`) is provided
+ */
+ params?: undefined
+} // TODO: does it make sense to support absolute paths objects?
+
+export interface ResolverLocationAsPathAbsolute
+ extends ResolverLocationAsPathRelative {
+ path: `/${string}`
+}
+export interface ResolverLocationAsRelative {
+ params?: MatcherParamsFormatted
+ query?: LocationQueryRaw
+ hash?: string
+
+ /**
+ * @deprecated This location is relative to the next parameter. This `name` will be ignored.
+ */
+ name?: undefined
+ /**
+ * @deprecated This location is relative to the next parameter. This `path` will be ignored.
+ */
+ path?: undefined
+}
import {
- type LocationQuery,
- normalizeQuery,
- parseQuery,
- stringifyQuery,
-} from '../../query'
-import type {
- MatcherPattern,
- MatcherPatternHash,
- MatcherPatternPath,
- MatcherPatternQuery,
-} from './matcher-pattern'
-import { warn } from '../../warning'
-import {
- encodeQueryValue as _encodeQueryValue,
- encodeParam,
-} from '../../encoding'
-import {
- LocationNormalized,
NEW_stringifyURL,
+ LocationNormalized,
parseURL,
resolveRelativePath,
-} from '../../location'
+} from 'src/location'
+import { normalizeQuery, stringifyQuery, parseQuery } from 'src/query'
+import type { MatcherParamsFormatted } from './matchers/matcher-pattern'
+import type { ResolverLocationAsRelative } from './resolver-abstract'
+import type { ResolverLocationAsPathAbsolute } from './resolver-abstract'
+import type { ResolverLocationAsPathRelative } from './resolver-abstract'
+import type { ResolverLocationAsNamed } from './resolver-abstract'
+import {
+ MatcherQueryParams,
+ NEW_RouterResolver_Base,
+ NO_MATCH_LOCATION,
+ RecordName,
+ ResolverLocationResolved,
+} from './resolver-abstract'
+import { comparePathParserScore } from 'src/matcher/pathParserRanker'
+import { warn } from 'src/warning'
import type {
- MatcherLocationAsNamed,
- MatcherLocationAsPathAbsolute,
- MatcherLocationAsPathRelative,
- MatcherLocationAsRelative,
- MatcherParamsFormatted,
-} from './matcher-location'
-import { _RouteRecordProps } from '../../typed-routes'
-import { comparePathParserScore } from '../../matcher/pathParserRanker'
-
-/**
- * Allowed types for a matcher name.
- */
-export type RecordName = string | symbol
+ MatcherPatternPath,
+ MatcherPatternQuery,
+ MatcherPatternHash,
+} from './matchers/matcher-pattern'
/**
* Manage and resolve routes. Also handles the encoding, decoding, parsing and
* - `TMatcherRecordRaw` represents the raw record type passed to {@link addMatcher}.
* - `TMatcherRecord` represents the normalized record type returned by {@link getRecords}.
*/
-export interface NEW_RouterResolver_Base<TRecord> {
- /**
- * Resolves an absolute location (like `/path/to/somewhere`).
- *
- * @param absoluteLocation - The absolute location to resolve.
- * @param currentLocation - This value is ignored and should not be passed if the location is absolute.
- */
- resolve(
- absoluteLocation: `/${string}`,
- currentLocation?: undefined
- ): NEW_LocationResolved<TRecord>
- /**
- * Resolves a string location relative to another location. A relative location can be `./same-folder`,
- * `../parent-folder`, `same-folder`, or even `?page=2`.
- */
- resolve(
- relativeLocation: string,
- currentLocation: NEW_LocationResolved<TRecord>
- ): NEW_LocationResolved<TRecord>
-
- /**
- * Resolves a location by its name. Any required params or query must be passed in the `options` argument.
- */
- resolve(
- location: MatcherLocationAsNamed,
- // TODO: is this useful?
- currentLocation?: undefined
- // currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
- ): NEW_LocationResolved<TRecord>
-
- /**
- * Resolves a location by its absolute path (starts with `/`). Any required query must be passed.
- * @param location - The location to resolve.
- */
- resolve(
- location: MatcherLocationAsPathAbsolute,
- // TODO: is this useful?
- currentLocation?: undefined
- // currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
- ): NEW_LocationResolved<TRecord>
-
- resolve(
- location: MatcherLocationAsPathRelative,
- currentLocation: NEW_LocationResolved<TRecord>
- ): NEW_LocationResolved<TRecord>
-
- // NOTE: in practice, this overload can cause bugs. It's better to use named locations
-
- /**
- * Resolves a location relative to another location. It reuses existing properties in the `currentLocation` like
- * `params`, `query`, and `hash`.
- */
- resolve(
- relativeLocation: MatcherLocationAsRelative,
- currentLocation: NEW_LocationResolved<TRecord>
- ): NEW_LocationResolved<TRecord>
-
- /**
- * Get a list of all resolver records.
- * Previously named `getRoutes()`
- */
- getRecords(): TRecord[]
-
- /**
- * Get a resolver record by its name.
- * Previously named `getRecordMatcher()`
- */
- getRecord(name: RecordName): TRecord | undefined
-}
-
-/**
- * Manage and resolve routes. Also handles the encoding, decoding, parsing and
- * serialization of params, query, and hash.
- *
- * - `TMatcherRecordRaw` represents the raw record type passed to {@link addMatcher}.
- * - `TMatcherRecord` represents the normalized record type returned by {@link getRecords}.
- */
export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord>
extends NEW_RouterResolver_Base<TMatcherRecord> {
/**
*/
clearMatchers(): void
}
-
-/**
- * Allowed location objects to be passed to {@link NEW_RouterResolver['resolve']}
- */
-export type MatcherLocationRaw =
- // | `/${string}`
- | string
- | MatcherLocationAsNamed
- | MatcherLocationAsPathAbsolute
- | MatcherLocationAsPathRelative
- | MatcherLocationAsRelative
-
-// TODO: ResolverLocationResolved
-export interface NEW_LocationResolved<TMatched> {
- name: RecordName
- params: MatcherParamsFormatted
-
- fullPath: string
- path: string
- query: LocationQuery
- hash: string
-
- matched: TMatched[]
-}
-
-export type MatcherPathParamsValue = string | null | string[]
-/**
- * Params in a string format so they can be encoded/decoded and put into a URL.
- */
-export type MatcherPathParams = Record<string, MatcherPathParamsValue>
-
-// TODO: move to matcher-pattern
-export type MatcherQueryParamsValue = string | null | Array<string | null>
-export type MatcherQueryParams = Record<string, MatcherQueryParamsValue>
-
-/**
- * Apply a function to all properties in an object. It's used to encode/decode params and queries.
- * @internal
- */
-export function applyFnToObject<R>(
- fn: (v: string | number | null | undefined) => R,
- params: MatcherPathParams | LocationQuery | undefined
-): Record<string, R | R[]> {
- const newParams: Record<string, R | R[]> = {}
-
- for (const key in params) {
- const value = params[key]
- newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value)
- }
-
- return newParams
-}
-
-/**
- * Decode text using `decodeURIComponent`. Returns the original text if it
- * fails.
- *
- * @param text - string to decode
- * @returns decoded string
- */
-export function decode(text: string | number): string
-export function decode(text: null | undefined): null
-export function decode(text: string | number | null | undefined): string | null
-export function decode(
- text: string | number | null | undefined
-): string | null {
- if (text == null) return null
- try {
- return decodeURIComponent('' + text)
- } catch (err) {
- __DEV__ && warn(`Error decoding "${text}". Using original value`)
- }
- return '' + text
-}
-// TODO: just add the null check to the original function in encoding.ts
-
-interface FnStableNull {
- (value: null | undefined): null
- (value: string | number): string
- // needed for the general case and must be last
- (value: string | number | null | undefined): string | null
-}
-
-// function encodeParam(text: null | undefined, encodeSlash?: boolean): null
-// function encodeParam(text: string | number, encodeSlash?: boolean): string
-// function encodeParam(
-// text: string | number | null | undefined,
-// encodeSlash?: boolean
-// ): string | null
-// function encodeParam(
-// text: string | number | null | undefined,
-// encodeSlash = true
-// ): string | null {
-// if (text == null) return null
-// text = encodePath(text)
-// return encodeSlash ? text.replace(SLASH_RE, '%2F') : text
-// }
-
-// @ts-expect-error: overload are not correctly identified
-const encodeQueryValue: FnStableNull =
- // for ts
- value => (value == null ? null : _encodeQueryValue(value))
-
-// // @ts-expect-error: overload are not correctly identified
-// const encodeQueryKey: FnStableNull =
-// // for ts
-// value => (value == null ? null : _encodeQueryKey(value))
-
-/**
- * Common properties for a location that couldn't be matched. This ensures
- * having the same name while having a `path`, `query` and `hash` that change.
- */
-export const NO_MATCH_LOCATION = {
- name: __DEV__ ? Symbol('no-match') : Symbol(),
- params: {},
- matched: [],
-} satisfies Omit<
- NEW_LocationResolved<unknown>,
- 'path' | 'hash' | 'query' | 'fullPath'
->
-
-// FIXME: later on, the MatcherRecord should be compatible with RouteRecordRaw (which can miss a path, have children, etc)
-
-/**
- * Experimental new matcher record base type.
- *
- * @experimental
- */
-export interface NEW_MatcherRecordRaw {
- path: MatcherPatternPath
- query?: MatcherPatternQuery
- hash?: MatcherPatternHash
-
- // NOTE: matchers do not handle `redirect` the redirect option, the router
- // does. They can still match the correct record but they will let the router
- // retrigger a whole navigation to the new location.
-
- // TODO: probably as `aliasOf`. Maybe a different format with the path, query and has matchers?
- /**
- * Aliases for the record. Allows defining extra paths that will behave like a
- * copy of the record. Allows having paths shorthands like `/users/:id` and
- * `/u/:id`. All `alias` and `path` values must share the same params.
- */
- // alias?: string | string[]
-
- /**
- * Name for the route record. Must be unique. Will be set to `Symbol()` if
- * not set.
- */
- name?: RecordName
-
- /**
- * Array of nested routes.
- */
- children?: NEW_MatcherRecordRaw[]
-
- /**
- * Is this a record that groups children. Cannot be matched
- */
- group?: boolean
-
- score: Array<number[]>
-}
-
-export interface EXPERIMENTAL_ResolverRecord_Base {
- /**
- * Name of the matcher. Unique across all matchers.
- */
- name: RecordName
-
- /**
- * {@link MatcherPattern} for the path section of the URI.
- */
- path: MatcherPatternPath
-
- /**
- * {@link MatcherPattern} for the query section of the URI.
- */
- query?: MatcherPatternQuery
-
- /**
- * {@link MatcherPattern} for the hash section of the URI.
- */
- hash?: MatcherPatternHash
-
- // TODO: here or in router
- // redirect?: RouteRecordRedirectOption
-
- parent?: this
- children: this[]
- aliasOf?: this
-
- /**
- * Is this a record that groups children. Cannot be matched
- */
- group?: boolean
-}
-
-export interface NEW_MatcherDynamicRecord
- extends EXPERIMENTAL_ResolverRecord_Base {
- // TODO: the score shouldn't be always needed, it's only needed with dynamic routing
- score: Array<number[]>
-}
-
-/**
- * Normalized version of a {@link NEW_MatcherRecordRaw} record.
- */
-export interface NEW_MatcherRecord extends NEW_MatcherDynamicRecord {}
-
-/**
- * Tagged template helper to encode params into a path. Doesn't work with null
- */
-export function pathEncoded(
- parts: TemplateStringsArray,
- ...params: Array<string | number | (string | number)[]>
-): string {
- return parts.reduce((result, part, i) => {
- return (
- result +
- part +
- (Array.isArray(params[i])
- ? params[i].map(encodeParam).join('/')
- : encodeParam(params[i]))
- )
- })
-}
-
-// pathEncoded`/users/${1}`
-// TODO:
-// pathEncoded`/users/${null}/end`
-
-// const a: RouteRecordRaw = {} as any
-
-/**
- * Build the `matched` array of a record that includes all parent records from the root to the current one.
- */
-export function buildMatched<T extends EXPERIMENTAL_ResolverRecord_Base>(
- record: T
-): T[] {
- const matched: T[] = []
- let node: T | undefined = record
- while (node) {
- matched.unshift(node)
- node = node.parent
- }
- return matched
-}
-
export function createCompiledMatcher<
TMatcherRecord extends NEW_MatcherDynamicRecord,
>(
// encodeQueryValue
// )
// const decodeQuery = transformObject.bind(null, decode, decode)
-
// NOTE: because of the overloads, we need to manually type the arguments
type MatcherResolveArgs =
| [absoluteLocation: `/${string}`, currentLocation?: undefined]
| [
relativeLocation: string,
- currentLocation: NEW_LocationResolved<TMatcherRecord>,
+ currentLocation: ResolverLocationResolved<TMatcherRecord>,
]
| [
- absoluteLocation: MatcherLocationAsPathAbsolute,
+ absoluteLocation: ResolverLocationAsPathAbsolute,
// Same as above
// currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
currentLocation?: undefined,
]
| [
- relativeLocation: MatcherLocationAsPathRelative,
- currentLocation: NEW_LocationResolved<TMatcherRecord>,
+ relativeLocation: ResolverLocationAsPathRelative,
+ currentLocation: ResolverLocationResolved<TMatcherRecord>,
]
| [
- location: MatcherLocationAsNamed,
+ location: ResolverLocationAsNamed,
// Same as above
// currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
currentLocation?: undefined,
]
| [
- relativeLocation: MatcherLocationAsRelative,
- currentLocation: NEW_LocationResolved<TMatcherRecord>,
+ relativeLocation: ResolverLocationAsRelative,
+ currentLocation: ResolverLocationResolved<TMatcherRecord>,
]
function resolve(
...args: MatcherResolveArgs
- ): NEW_LocationResolved<TMatcherRecord> {
+ ): ResolverLocationResolved<TMatcherRecord> {
const [to, currentLocation] = args
if (typeof to === 'object' && (to.name || to.path == null)) {
}
let matcher: TMatcherRecord | undefined
- let matched: NEW_LocationResolved<TMatcherRecord>['matched'] | undefined
+ let matched:
+ | ResolverLocationResolved<TMatcherRecord>['matched']
+ | undefined
let parsedParams: MatcherParamsFormatted | null | undefined
for (matcher of matchers) {
// for (const matcher of matched) {
// Object.assign(queryParams, matcher.query?.match(url.query))
// }
-
parsedParams = { ...pathParams, ...queryParams, ...hashParams }
// we found our match!
break
getRecord,
getRecords,
}
-}
-
-/**
+} /**
* Performs a binary search to find the correct insertion index for a new matcher.
*
* Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
* @param matcher - new matcher to be inserted
* @param matchers - existing matchers
*/
-function findInsertionIndex<T extends NEW_MatcherDynamicRecord>(
+
+export function findInsertionIndex<T extends NEW_MatcherDynamicRecord>(
matcher: T,
matchers: T[]
) {
return upper
}
-
-function getInsertionAncestor<T extends NEW_MatcherDynamicRecord>(matcher: T) {
+export function getInsertionAncestor<T extends NEW_MatcherDynamicRecord>(
+ matcher: T
+) {
let ancestor: T | undefined = matcher
while ((ancestor = ancestor.parent)) {
}
return
-}
-
-/**
+} /**
* Checks if a record or any of its parent is an alias
* @param record
*/
-function isAliasRecord<T extends NEW_MatcherDynamicRecord>(
+
+export function isAliasRecord<T extends NEW_MatcherDynamicRecord>(
record: T | undefined
): boolean {
while (record) {
}
return false
+} // pathEncoded`/users/${1}`
+// TODO:
+// pathEncoded`/users/${null}/end`
+// const a: RouteRecordRaw = {} as any
+/**
+ * Build the `matched` array of a record that includes all parent records from the root to the current one.
+ */
+
+export function buildMatched<T extends EXPERIMENTAL_ResolverRecord_Base>(
+ record: T
+): T[] {
+ const matched: T[] = []
+ let node: T | undefined = record
+ while (node) {
+ matched.unshift(node)
+ node = node.parent
+ }
+ return matched
+}
+export interface EXPERIMENTAL_ResolverRecord_Base {
+ /**
+ * Name of the matcher. Unique across all matchers.
+ */
+ name: RecordName
+
+ /**
+ * {@link MatcherPattern} for the path section of the URI.
+ */
+ path: MatcherPatternPath
+
+ /**
+ * {@link MatcherPattern} for the query section of the URI.
+ */
+ query?: MatcherPatternQuery
+
+ /**
+ * {@link MatcherPattern} for the hash section of the URI.
+ */
+ hash?: MatcherPatternHash
+
+ // TODO: here or in router
+ // redirect?: RouteRecordRedirectOption
+ parent?: this
+ // FIXME: this property is only needed for dynamic routing
+ children: this[]
+ aliasOf?: this
+
+ /**
+ * Is this a record that groups children. Cannot be matched
+ */
+ group?: boolean
+}
+export interface NEW_MatcherDynamicRecord
+ extends EXPERIMENTAL_ResolverRecord_Base {
+ // TODO: the score shouldn't be always needed, it's only needed with dynamic routing
+ score: Array<number[]>
+} // FIXME: later on, the MatcherRecord should be compatible with RouteRecordRaw (which can miss a path, have children, etc)
+/**
+ * Experimental new matcher record base type.
+ *
+ * @experimental
+ */
+
+export interface NEW_MatcherRecordRaw {
+ path: MatcherPatternPath
+ query?: MatcherPatternQuery
+ hash?: MatcherPatternHash
+
+ // NOTE: matchers do not handle `redirect` the redirect option, the router
+ // does. They can still match the correct record but they will let the router
+ // retrigger a whole navigation to the new location.
+ // TODO: probably as `aliasOf`. Maybe a different format with the path, query and has matchers?
+ /**
+ * Aliases for the record. Allows defining extra paths that will behave like a
+ * copy of the record. Allows having paths shorthands like `/users/:id` and
+ * `/u/:id`. All `alias` and `path` values must share the same params.
+ */
+ // alias?: string | string[]
+ /**
+ * Name for the route record. Must be unique. Will be set to `Symbol()` if
+ * not set.
+ */
+ name?: RecordName
+
+ /**
+ * Array of nested routes.
+ */
+ children?: NEW_MatcherRecordRaw[]
+
+ /**
+ * Is this a record that groups children. Cannot be matched
+ */
+ group?: boolean
+
+ score: Array<number[]>
}
import { describe, expect, it } from 'vitest'
import { createStaticResolver } from './resolver-static'
-import { MatcherQueryParams, NO_MATCH_LOCATION } from './resolver'
+import { MatcherQueryParams, NO_MATCH_LOCATION } from './resolver-abstract'
import {
MatcherPatternQuery,
MatcherPatternPathStatic,
-} from './matcher-pattern'
+} from './matchers/matcher-pattern'
import {
EMPTY_PATH_PATTERN_MATCHER,
USER_ID_PATH_PATTERN_MATCHER,
parseURL,
resolveRelativePath,
} from '../../location'
-import {
- MatcherLocationAsNamed,
- MatcherLocationAsPathAbsolute,
- MatcherLocationAsPathRelative,
- MatcherLocationAsRelative,
- MatcherParamsFormatted,
-} from './matcher-location'
+import { MatcherParamsFormatted } from './matchers/matcher-pattern'
+import { ResolverLocationAsRelative } from './resolver-abstract'
+import { ResolverLocationAsPathAbsolute } from './resolver-abstract'
+import { ResolverLocationAsPathRelative } from './resolver-abstract'
+import { ResolverLocationAsNamed } from './resolver-abstract'
import {
RecordName,
MatcherQueryParams,
- NEW_LocationResolved,
+ ResolverLocationResolved,
NEW_RouterResolver_Base,
NO_MATCH_LOCATION,
-} from './resolver'
+} from './resolver-abstract'
import type {
MatcherPatternPath,
MatcherPatternQuery,
MatcherPatternHash,
-} from './matcher-pattern'
+} from './matchers/matcher-pattern'
// TODO: find a better name than static that doesn't conflict with static params
// maybe fixed or simple
// TODO: here or in router
// redirect?: RouteRecordRedirectOption
- parent?: EXPERIMENTAL_ResolverRecord | null // the parent can be matchable or not
+ parent?: EXPERIMENTAL_ResolverRecord | null // the parend can be matchable or not
// TODO: implement aliases
// aliasOf?: this
}
// NOTE: because of the overloads for `resolve`, we need to manually type the arguments
type _resolveArgs =
| [absoluteLocation: `/${string}`, currentLocation?: undefined]
- | [relativeLocation: string, currentLocation: NEW_LocationResolved<TRecord>]
| [
- absoluteLocation: MatcherLocationAsPathAbsolute,
+ relativeLocation: string,
+ currentLocation: ResolverLocationResolved<TRecord>,
+ ]
+ | [
+ absoluteLocation: ResolverLocationAsPathAbsolute,
// Same as above
// currentLocation?: NEW_LocationResolved<TRecord> | undefined
currentLocation?: undefined,
]
| [
- relativeLocation: MatcherLocationAsPathRelative,
- currentLocation: NEW_LocationResolved<TRecord>,
+ relativeLocation: ResolverLocationAsPathRelative,
+ currentLocation: ResolverLocationResolved<TRecord>,
]
| [
- location: MatcherLocationAsNamed,
+ location: ResolverLocationAsNamed,
// Same as above
// currentLocation?: NEW_LocationResolved<TRecord> | undefined
currentLocation?: undefined,
]
| [
- relativeLocation: MatcherLocationAsRelative,
- currentLocation: NEW_LocationResolved<TRecord>,
+ relativeLocation: ResolverLocationAsRelative,
+ currentLocation: ResolverLocationResolved<TRecord>,
]
function resolve(
...[to, currentLocation]: _resolveArgs
- ): NEW_LocationResolved<TRecord> {
+ ): ResolverLocationResolved<TRecord> {
if (typeof to === 'object' && (to.name || to.path == null)) {
// relative location by path or by name
if (__DEV__ && to.name == null && currentLocation == null) {
}
let record: TRecord | undefined
- let matched: NEW_LocationResolved<TRecord>['matched'] | undefined
+ let matched: ResolverLocationResolved<TRecord>['matched'] | undefined
let parsedParams: MatcherParamsFormatted | null | undefined
for (record of records) {
import { describe, expect, it } from 'vitest'
-import {
- createCompiledMatcher,
- NO_MATCH_LOCATION,
- pathEncoded,
-} from './resolver'
+import { NO_MATCH_LOCATION, pathEncoded } from './resolver-abstract'
+import { createCompiledMatcher } from './resolver-dynamic'
import {
MatcherPatternQuery,
MatcherPatternPathStatic,
MatcherPatternPathDynamic,
-} from './matcher-pattern'
+} from './matchers/matcher-pattern'
import {
EMPTY_PATH_ROUTE,
USER_ID_ROUTE,
import { describe, expectTypeOf, it } from 'vitest'
-import {
- NEW_LocationResolved,
- NEW_MatcherRecordRaw,
- NEW_RouterResolver,
-} from './resolver'
-import { EXPERIMENTAL_RouteRecordNormalized } from '../experimental/router'
+import { ResolverLocationResolved } from './resolver-abstract'
+import { NEW_MatcherRecordRaw } from './resolver-dynamic'
+import { NEW_RouterResolver } from './resolver-dynamic'
+import { EXPERIMENTAL_RouteRecordNormalized } from '../router'
describe('Matcher', () => {
type TMatcherRecordRaw = NEW_MatcherRecordRaw
describe('matcher.resolve()', () => {
it('resolves absolute string locations', () => {
expectTypeOf(matcher.resolve({ path: '/foo' })).toEqualTypeOf<
- NEW_LocationResolved<TMatcherRecord>
+ ResolverLocationResolved<TMatcherRecord>
>()
expectTypeOf(matcher.resolve('/foo')).toEqualTypeOf<
- NEW_LocationResolved<TMatcherRecord>
+ ResolverLocationResolved<TMatcherRecord>
>()
})
expectTypeOf(
matcher.resolve(
{ path: 'foo' },
- {} as NEW_LocationResolved<TMatcherRecord>
+ {} as ResolverLocationResolved<TMatcherRecord>
)
- ).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
+ ).toEqualTypeOf<ResolverLocationResolved<TMatcherRecord>>()
expectTypeOf(
- matcher.resolve('foo', {} as NEW_LocationResolved<TMatcherRecord>)
- ).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
+ matcher.resolve('foo', {} as ResolverLocationResolved<TMatcherRecord>)
+ ).toEqualTypeOf<ResolverLocationResolved<TMatcherRecord>>()
})
it('resolved named locations', () => {
expectTypeOf(matcher.resolve({ name: 'foo', params: {} })).toEqualTypeOf<
- NEW_LocationResolved<TMatcherRecord>
+ ResolverLocationResolved<TMatcherRecord>
>()
})
expectTypeOf(
matcher.resolve(
{ params: { id: 1 } },
- {} as NEW_LocationResolved<TMatcherRecord>
+ {} as ResolverLocationResolved<TMatcherRecord>
)
- ).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
+ ).toEqualTypeOf<ResolverLocationResolved<TMatcherRecord>>()
})
})
// @ts-expect-error: name + currentLocation
{ name: 'a', params: {} },
//
- {} as NEW_LocationResolved<TMatcherRecord>
+ {} as ResolverLocationResolved<TMatcherRecord>
)
})
})
EXPERIMENTAL_ResolverRecord_Matchable,
EXPERIMENTAL_ResolverStatic,
} from './route-resolver/resolver-static'
+import { ResolverLocationResolved } from './route-resolver/resolver-abstract'
/**
* resolve, reject arguments of Promise constructor