NEW_LocationResolved,
NEW_MatcherRecord,
NEW_MatcherRecordRaw,
- NEW_RouterMatcher,
-} from '../new-route-resolver/matcher'
+ NEW_RouterResolver,
+} from '../new-route-resolver/resolver'
import {
parseQuery as originalParseQuery,
stringifyQuery as originalStringifyQuery,
RouteLocationAsRelative,
RouteLocationAsRelativeTyped,
RouteLocationAsString,
- RouteLocationGeneric,
RouteLocationNormalized,
RouteLocationNormalizedLoaded,
RouteLocationRaw,
* Matcher to use to resolve routes.
* @experimental
*/
- matcher: NEW_RouterMatcher<NEW_MatcherRecordRaw, TMatcherRecord>
+ matcher: NEW_RouterResolver<NEW_MatcherRecordRaw, TMatcherRecord>
}
/**
// TODO: is it worth to have 2 types for the undefined values?
export interface EXPERIMENTAL_RouteRecordNormalized extends NEW_MatcherRecord {
+ /**
+ * Arbitrary data attached to the record.
+ */
meta: RouteMeta
}
| EXPERIMENTAL_RouteRecordRaw,
route?: EXPERIMENTAL_RouteRecordRaw
) {
- let parent: Parameters<(typeof matcher)['addRoute']>[1] | undefined
+ let parent: Parameters<(typeof matcher)['addMatcher']>[1] | undefined
let rawRecord: EXPERIMENTAL_RouteRecordRaw
if (isRouteName(parentOrRoute)) {
rawRecord = parentOrRoute
}
- const addedRecord = matcher.addRoute(
+ const addedRecord = matcher.addMatcher(
normalizeRouteRecord(rawRecord),
parent
)
return () => {
- matcher.removeRoute(addedRecord)
+ matcher.removeMatcher(addedRecord)
}
}
function removeRoute(name: NonNullable<RouteRecordNameGeneric>) {
const recordMatcher = matcher.getMatcher(name)
if (recordMatcher) {
- matcher.removeRoute(recordMatcher)
+ matcher.removeMatcher(recordMatcher)
} else if (__DEV__) {
warn(`Cannot remove non-existent route "${String(name)}"`)
}
addRoute,
removeRoute,
- clearRoutes: matcher.clearRoutes,
+ clearRoutes: matcher.clearMatchers,
hasRoute,
getRoutes,
resolve,
_PathParserOptions,
} from './pathParserRanker'
-import { comparePathParserScore } from './pathParserRanker'
+import {
+ comparePathParserScore,
+ PATH_PARSER_OPTIONS_DEFAULTS,
+} from './pathParserRanker'
import { warn } from '../warning'
-import { assign, noop } from '../utils'
+import { assign, mergeOptions, noop } from '../utils'
import type { RouteRecordNameGeneric, _RouteRecordProps } from '../typed-routes'
/**
NonNullable<RouteRecordNameGeneric>,
RouteRecordMatcher
>()
- globalOptions = mergeOptions(
- { strict: false, end: true, sensitive: false } as PathParserOptions,
+ globalOptions = mergeOptions<PathParserOptions>(
+ PATH_PARSER_OPTIONS_DEFAULTS,
globalOptions
)
* components. Also accept a boolean for components.
* @param record
*/
-function normalizeRecordProps(
+export function normalizeRecordProps(
record: RouteRecordRaw
): Record<string, _RouteRecordProps> {
const propsObject = {} as Record<string, _RouteRecordProps>
)
}
-function mergeOptions<T extends object>(
- defaults: T,
- partialOptions: Partial<T>
-): T {
- const options = {} as T
- for (const key in defaults) {
- options[key] = key in partialOptions ? partialOptions[key]! : defaults[key]
- }
-
- return options
-}
-
type ParamKey = RouteRecordMatcher['keys'][number]
function isSameParam(a: ParamKey, b: ParamKey): boolean {
* @param mainNormalizedRecord - RouteRecordNormalized
* @param parent - RouteRecordMatcher
*/
-function checkChildMissingNameWithEmptyPath(
+export function checkChildMissingNameWithEmptyPath(
mainNormalizedRecord: RouteRecordNormalized,
parent?: RouteRecordMatcher
) {
const last = score[score.length - 1]
return score.length > 0 && last[last.length - 1] < 0
}
+export const PATH_PARSER_OPTIONS_DEFAULTS: PathParserOptions = {
+ strict: false,
+ end: true,
+ sensitive: false,
+}
-export { createCompiledMatcher } from './matcher'
+export { createCompiledMatcher } from './resolver'
import type { LocationQueryRaw } from '../query'
-import type { MatcherName } from './matcher'
+import type { MatcherName } from './resolver'
/**
* Generic object of params that can be passed to a matcher.
-import { decode, MatcherQueryParams } from './matcher'
+import { decode, MatcherQueryParams } from './resolver'
import { EmptyParams, MatcherParamsFormatted } from './matcher-location'
import { miss } from './matchers/errors'
import { createRouterMatcher, normalizeRouteRecord } from '../matcher'
import { RouteComponent, RouteRecordRaw, MatcherLocation } from '../types'
-import { MatcherLocationNormalizedLoose } from '../../__tests__/utils'
import { defineComponent } from 'vue'
import { START_LOCATION_NORMALIZED } from '../location'
import { describe, expect, it } from 'vitest'
MatcherLocationRaw,
NEW_MatcherRecordRaw,
NEW_LocationResolved,
-} from './matcher'
+ NEW_MatcherRecord,
+} from './resolver'
import { PathParams, tokensToParser } from '../matcher/pathParserRanker'
import { tokenizePath } from '../matcher/pathTokenizer'
import { miss } from './matchers/errors'
describe('RouterMatcher.resolve', () => {
mockWarn()
- type Matcher = ReturnType<typeof createRouterMatcher>
+ type Matcher = ReturnType<typeof createCompiledMatcher>
type MatcherResolvedLocation = ReturnType<Matcher['resolve']>
- const START_LOCATION: NEW_LocationResolved = {
+ const START_LOCATION: MatcherResolvedLocation = {
name: Symbol('START'),
- fullPath: '/',
- path: '/',
params: {},
+ path: '/',
+ fullPath: '/',
query: {},
hash: '',
matched: [],
+ // meta: {},
}
function isMatcherLocationResolved(
location: unknown
- ): location is NEW_LocationResolved {
+ ): location is NEW_LocationResolved<NEW_MatcherRecord> {
return !!(
location &&
typeof location === 'object' &&
toLocation: MatcherLocationRaw,
expectedLocation: Partial<MatcherResolvedLocation>,
fromLocation:
- | NEW_LocationResolved
+ | NEW_LocationResolved<NEW_MatcherRecord>
| Exclude<MatcherLocationRaw, string>
| `/${string}` = START_LOCATION
) {
const records = (Array.isArray(record) ? record : [record]).map(
(record): NEW_MatcherRecordRaw => compileRouteRecord(record)
)
- const matcher = createCompiledMatcher()
+ const matcher = createCompiledMatcher<NEW_MatcherRecord>()
for (const record of records) {
- matcher.addRoute(record)
+ matcher.addMatcher(record)
}
const resolved: MatcherResolvedLocation = {
})
}
- function _assertRecordMatch(
- record: RouteRecordRaw | RouteRecordRaw[],
- location: MatcherLocationRaw,
- resolved: Partial<MatcherLocationNormalizedLoose>,
- start: MatcherLocation = START_LOCATION_NORMALIZED
- ) {
- record = Array.isArray(record) ? record : [record]
- const matcher = createRouterMatcher(record, {})
-
- if (!('meta' in resolved)) {
- resolved.meta = record[0].meta || {}
- }
-
- if (!('name' in resolved)) {
- resolved.name = undefined
- }
-
- // add location if provided as it should be the same value
- if ('path' in location && !('path' in resolved)) {
- resolved.path = location.path
- }
-
- if ('redirect' in record) {
- throw new Error('not handled')
- } else {
- // use one single record
- if (!resolved.matched) resolved.matched = record.map(normalizeRouteRecord)
- // allow passing an expect.any(Array)
- else if (Array.isArray(resolved.matched))
- resolved.matched = resolved.matched.map(m => ({
- ...normalizeRouteRecord(m as any),
- aliasOf: m.aliasOf,
- }))
- }
-
- // allows not passing params
- resolved.params =
- resolved.params || ('params' in location ? location.params : {})
-
- const startCopy: MatcherLocation = {
- ...start,
- matched: start.matched.map(m => ({
- ...normalizeRouteRecord(m),
- aliasOf: m.aliasOf,
- })) as MatcherLocation['matched'],
- }
-
- // make matched non enumerable
- Object.defineProperty(startCopy, 'matched', { enumerable: false })
-
- const result = matcher.resolve(location, startCopy)
- expect(result).toEqual(resolved)
- }
-
/**
*
* @param record - Record or records we are testing the matcher against
createCompiledMatcher,
NO_MATCH_LOCATION,
pathEncoded,
-} from './matcher'
+} from './resolver'
import {
MatcherPatternParams_Base,
MatcherPatternPath,
MatcherPatternPathStatic,
MatcherPatternPathDynamic,
} from './matcher-pattern'
-import { NEW_MatcherRecord } from './matcher'
+import { NEW_MatcherRecord } from './resolver'
import { miss } from './matchers/errors'
import { EmptyParams } from './matcher-location'
describe('adding and removing', () => {
it('add static path', () => {
const matcher = createCompiledMatcher()
- matcher.addRoute(EMPTY_PATH_ROUTE)
+ matcher.addMatcher(EMPTY_PATH_ROUTE)
})
it('adds dynamic path', () => {
const matcher = createCompiledMatcher()
- matcher.addRoute(USER_ID_ROUTE)
+ matcher.addMatcher(USER_ID_ROUTE)
})
it('removes static path', () => {
const matcher = createCompiledMatcher()
- matcher.addRoute(EMPTY_PATH_ROUTE)
- matcher.removeRoute(EMPTY_PATH_ROUTE)
+ matcher.addMatcher(EMPTY_PATH_ROUTE)
+ matcher.removeMatcher(EMPTY_PATH_ROUTE)
// Add assertions to verify the route was removed
})
it('removes dynamic path', () => {
const matcher = createCompiledMatcher()
- matcher.addRoute(USER_ID_ROUTE)
- matcher.removeRoute(USER_ID_ROUTE)
+ matcher.addMatcher(USER_ID_ROUTE)
+ matcher.removeMatcher(USER_ID_ROUTE)
// Add assertions to verify the route was removed
})
})
import {
NEW_LocationResolved,
NEW_MatcherRecordRaw,
- NEW_RouterMatcher,
-} from './matcher'
+ NEW_RouterResolver,
+} from './resolver'
import { EXPERIMENTAL_RouteRecordNormalized } from '../experimental/router'
describe('Matcher', () => {
type TMatcherRecordRaw = NEW_MatcherRecordRaw
type TMatcherRecord = EXPERIMENTAL_RouteRecordNormalized
- const matcher: NEW_RouterMatcher<TMatcherRecordRaw, TMatcherRecord> =
+ const matcher: NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> =
{} as any
describe('matcher.resolve()', () => {
MatcherPatternQuery,
MatcherPatternParams_Base,
} from '../matcher-pattern'
-import { NEW_MatcherRecord } from '../matcher'
+import { NEW_MatcherRecord } from '../resolver'
import { miss } from './errors'
export const ANY_PATH_PATTERN_MATCHER: MatcherPatternPath<{
export type MatcherName = 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 addRoute}.
- * `TMatcherRecord` represents the normalized record type.
+ * 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 getMatchers}.
*/
-export interface NEW_RouterMatcher<TMatcherRecordRaw, TMatcherRecord> {
+export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
/**
* Resolves an absolute location (like `/path/to/somewhere`).
*/
currentLocation: NEW_LocationResolved<TMatcherRecord>
): NEW_LocationResolved<TMatcherRecord>
- addRoute(matcher: TMatcherRecordRaw, parent?: TMatcherRecord): TMatcherRecord
- removeRoute(matcher: TMatcherRecord): void
- clearRoutes(): void
+ /**
+ * Add a matcher record. Previously named `addRoute()`.
+ * @param matcher - The matcher record to add.
+ * @param parent - The parent matcher record if this is a child.
+ */
+ addMatcher(
+ matcher: TMatcherRecordRaw,
+ parent?: TMatcherRecord
+ ): TMatcherRecord
+
+ /**
+ * Remove a matcher by its name. Previously named `removeRoute()`.
+ * @param matcher - The matcher (returned by {@link addMatcher}) to remove.
+ */
+ removeMatcher(matcher: TMatcherRecord): void
+
+ /**
+ * Remove all matcher records. Prevoisly named `clearRoutes()`.
+ */
+ clearMatchers(): void
/**
* Get a list of all matchers.
}
/**
- * Allowed location objects to be passed to {@link NEW_RouterMatcher['resolve']}
+ * Allowed location objects to be passed to {@link NEW_RouterResolver['resolve']}
*/
export type MatcherLocationRaw =
| `/${string}`
| MatcherLocationAsPathRelative
| MatcherLocationAsRelative
-/**
- * Matcher capable of adding and removing routes at runtime.
- */
-export interface NEW_Matcher_Dynamic {
- addRoute(record: TODO, parent?: TODO): () => void
-
- removeRoute(record: TODO): void
- removeRoute(name: MatcherName): void
-
- clearRoutes(): void
-}
-
-type TODO = any
-
export interface NEW_LocationResolved<TMatched> {
// FIXME: remove `undefined`
name: MatcherName | undefined
// FIXME: later on, the MatcherRecord should be compatible with RouteRecordRaw (which can miss a path, have children, etc)
/**
- * Experiment new matcher record base type.
+ * Experimental new matcher record base type.
*
* @experimental
*/
children?: NEW_MatcherRecordRaw[]
}
-/**
- * Normalized version of a {@link NEW_MatcherRecordRaw} record.
- */
-export interface NEW_MatcherRecord {
+export interface NEW_MatcherRecordBase<T> {
/**
* Name of the matcher. Unique across all matchers.
*/
query?: MatcherPatternQuery
hash?: MatcherPatternHash
- parent?: NEW_MatcherRecord
+ parent?: T
}
+/**
+ * Normalized version of a {@link NEW_MatcherRecordRaw} record.
+ */
+export interface NEW_MatcherRecord
+ extends NEW_MatcherRecordBase<NEW_MatcherRecord> {}
+
/**
* Tagged template helper to encode params into a path. Doesn't work with null
*/
/**
* Build the `matched` array of a record that includes all parent records from the root to the current one.
*/
-function buildMatched(record: NEW_MatcherRecord): NEW_MatcherRecord[] {
- const matched: NEW_MatcherRecord[] = []
- let node: NEW_MatcherRecord | undefined = record
+function buildMatched<T extends NEW_MatcherRecordBase<T>>(record: T): T[] {
+ const matched: T[] = []
+ let node: T | undefined = record
while (node) {
matched.unshift(node)
node = node.parent
return matched
}
-export function createCompiledMatcher(
+export function createCompiledMatcher<
+ TMatcherRecord extends NEW_MatcherRecordBase<TMatcherRecord>
+>(
records: NEW_MatcherRecordRaw[] = []
-): NEW_RouterMatcher<NEW_MatcherRecordRaw, NEW_MatcherRecord> {
+): NEW_RouterResolver<NEW_MatcherRecordRaw, TMatcherRecord> {
// TODO: we also need an array that has the correct order
- const matchers = new Map<MatcherName, NEW_MatcherRecord>()
+ const matchers = new Map<MatcherName, TMatcherRecord>()
// TODO: allow custom encode/decode functions
// const encodeParams = applyToParams.bind(null, encodeParam)
type MatcherResolveArgs =
| [
absoluteLocation: `/${string}`,
- currentLocation?: undefined | NEW_LocationResolved<NEW_MatcherRecord>
+ currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
]
| [
relativeLocation: string,
- currentLocation: NEW_LocationResolved<NEW_MatcherRecord>
+ currentLocation: NEW_LocationResolved<TMatcherRecord>
]
| [absoluteLocation: MatcherLocationAsPathAbsolute]
| [
relativeLocation: MatcherLocationAsPathRelative,
- currentLocation: NEW_LocationResolved<NEW_MatcherRecord>
+ currentLocation: NEW_LocationResolved<TMatcherRecord>
]
| [location: MatcherLocationAsNamed]
| [
relativeLocation: MatcherLocationAsRelative,
- currentLocation: NEW_LocationResolved<NEW_MatcherRecord>
+ currentLocation: NEW_LocationResolved<TMatcherRecord>
]
function resolve(
...args: MatcherResolveArgs
- ): NEW_LocationResolved<NEW_MatcherRecord> {
+ ): NEW_LocationResolved<TMatcherRecord> {
const [location, currentLocation] = args
// string location, e.g. '/foo', '../bar', 'baz', '?page=1'
// parseURL handles relative paths
const url = parseURL(parseQuery, location, currentLocation?.path)
- let matcher: NEW_MatcherRecord | undefined
- let matched:
- | NEW_LocationResolved<NEW_MatcherRecord>['matched']
- | undefined
+ let matcher: TMatcherRecord | undefined
+ let matched: NEW_LocationResolved<TMatcherRecord>['matched'] | undefined
let parsedParams: MatcherParamsFormatted | null | undefined
for (matcher of matchers.values()) {
}
}
- function addRoute(record: NEW_MatcherRecordRaw, parent?: NEW_MatcherRecord) {
+ function addRoute(record: NEW_MatcherRecordRaw, parent?: TMatcherRecord) {
const name = record.name ?? (__DEV__ ? Symbol('unnamed-route') : Symbol())
// FIXME: proper normalization of the record
- const normalizedRecord: NEW_MatcherRecord = {
+ // @ts-expect-error: we are not properly normalizing the record yet
+ const normalizedRecord: TMatcherRecord = {
...record,
name,
parent,
addRoute(record)
}
- function removeRoute(matcher: NEW_MatcherRecord) {
+ function removeRoute(matcher: TMatcherRecord) {
matchers.delete(matcher.name)
// TODO: delete children and aliases
}
return {
resolve,
- addRoute,
- removeRoute,
- clearRoutes,
+ addMatcher: addRoute,
+ removeMatcher: removeRoute,
+ clearMatchers: clearRoutes,
getMatcher,
getMatchers,
}
*/
export const isArray: (arg: ArrayLike<any> | any) => arg is ReadonlyArray<any> =
Array.isArray
+
+export function mergeOptions<T extends object>(
+ defaults: T,
+ partialOptions: Partial<T>
+): T {
+ const options = {} as T
+ for (const key in defaults) {
+ options[key] = key in partialOptions ? partialOptions[key]! : defaults[key]
+ }
+
+ return options
+}