* type P = ParamsFromPath<'/:id/b/:c*'> // { id: string; c?: string[] }
* ```
*/
-export type ParamsFromPath<P extends string = string> = string extends P
- ? RouteParams // Generic version
- : _ExtractParamsPath<_RemoveRegexpFromParam<P>, false> extends Record<
- any,
- never
- >
- ? Record<any, never>
- : _ExtractParamsPath<_RemoveRegexpFromParam<P>, false>
-
-export type ParamsRawFromPath<P extends string = string> = string extends P
- ? RouteParamsRaw // Generic version
- : _ExtractParamsPath<_RemoveRegexpFromParam<P>, true> extends Record<
- any,
- never
- >
- ? Record<any, never>
- : _ExtractParamsPath<_RemoveRegexpFromParam<P>, true>
+export type ParamsFromPath<P extends string = string> =
+ P extends `${string}:${string}`
+ ? Simplify<_ExtractParamsOfPath<P, false>>
+ : Record<any, never>
+
+export type ParamsRawFromPath<P extends string = string> =
+ P extends `${string}:${string}`
+ ? Simplify<_ExtractParamsOfPath<P, true>>
+ : Record<any, never>
/**
* Possible param modifiers.
| '@'
| '['
| ']'
+ | '$'
| _ParamModifier
/**
_ExtractParamsPath<Rest, isRaw>
: {}
+type _PathParam<P extends string, Rest extends string = ''> =
+ | `${string}:${P}`
+ | `${string}:${P}${_ParamModifier}${Rest}`
+
+type b = '/' extends _PathParam<infer P> ? P : never
+type c = '/home' extends _PathParam<infer P> ? P : never
+type d = '/user/:id' extends _PathParam<infer P, infer Rest> ? [P, Rest] : never
+type e = '/user/:id+' extends _PathParam<infer P> ? P : never
+
+export type _ExtractParamsOfPath<
+ P extends string,
+ isRaw extends boolean
+> = P extends `${string}:${infer HasParam}`
+ ? _ParamName<HasParam> extends _ParamExtractResult<
+ infer ParamName,
+ infer Rest
+ >
+ ? // ParamName is delimited by something eg: /:id/b/:c
+ // let's first remove the regex if there is one then extract the modifier
+ _ExtractModifier<_StripRegex<Rest>> extends _ModifierExtracTResult<
+ infer Modifier,
+ infer Rest2
+ >
+ ? _ParamToObject<ParamName, Modifier, isRaw> &
+ _ExtractParamsOfPath<Rest2, isRaw>
+ : {
+ NO: 1 // this should never happen as the modifier can be empty
+ }
+ : // Nothing after the param: /:id, we are done
+ _ParamToObject<HasParam, '', isRaw>
+ : {
+ // EMPTY: 1
+ }
+
+type a1 = _ExtractParamsOfPath<'/', false>
+type a2 = _ExtractParamsOfPath<'/:id', false>
+type a3 = _ExtractParamsOfPath<'/:id/:b', false>
+type a4 = _ExtractParamsOfPath<'/:id(.*)', false>
+type a5 = _ExtractParamsOfPath<'/:id(.*)/other', false>
+type a6 = _ExtractParamsOfPath<'/:id(.*)+', false>
+type a7 = _ExtractParamsOfPath<'/:id(.*)+/other', false>
+type a8 = _ExtractParamsOfPath<'/:id(.*)+/other/:b/:c/:d', false>
+
+// TODO: perf test this to see if worth because it's way more readable
+// also move to utils
+export type Simplify<T> = { [K in keyof T]: T[K] }
+
+type test1 =
+ '/:id/:b' extends `${string}:${infer P}${_ParamDelimiter}${infer Rest}`
+ ? [P, Rest]
+ : never
+
+type _ParamName_OLD<P extends string> =
+ P extends `${_AlphaNumeric}${infer Rest}`
+ ? P extends `${infer C}${Rest}`
+ ? // Keep extracting other alphanumeric chars
+ `${C}${_ParamName_OLD<Rest>}`
+ : never // ERR
+ : // add the rest to the end after a % which is invalid in a path so it can be used as a delimiter
+ ` % ${P}`
+
+interface _ParamExtractResult<P extends string, Rest extends string> {
+ param: P
+ rest: Rest
+}
+
+type _ParamName<
+ Tail extends string,
+ Head extends string = ''
+> = Tail extends `${_AlphaNumeric}${infer Rest}`
+ ? Tail extends `${infer C}${Rest}`
+ ? // Keep extracting other alphanumeric chars
+ _ParamName<Rest, `${Head}${C}`>
+ : never // ERR
+ : // add the rest to the end after a % which is invalid in a path so it can be used as a delimiter
+ _ParamExtractResult<Head, Tail>
+
+type p1 = _ParamName<'id'>
+type p2 = _ParamName<'abc+/dos'>
+type p3 = _ParamName<'abc/:dos)'>
+
+/**
+ * We consider a what comes after a param, e.g. For `/:id(\\d+)+/edit`, it would be `(\\d+)+/edit`. This should output
+ * everything after the regex while handling escaped `)`: `+/edit`.
+ */
+export type _StripRegex<S extends string> =
+ // do we have an escaped closing parenthesis?
+ S extends `${infer A}\\)${infer Rest}`
+ ? // the actual regexp finished before, A has no escaped )
+ A extends `${string})${infer Rest2}`
+ ? // get the modifier if there is one
+ `${Rest2}\\)${Rest}` // job done
+ : _RemoveUntilClosingPar<Rest> // we keep removing
+ : // simple case with no escaping
+ S extends `${string})${infer Rest}`
+ ? // extract the modifier if there is one
+ Rest
+ : // nothing to remove
+ S
+
+type r1 = _StripRegex<'(\\d+)+/edit/:other(.*)*'>
+type r3 = _StripRegex<'(.*)*'>
+type r4 = _StripRegex<'?/rest'>
+type r5 = _StripRegex<'*'>
+type r6 = _StripRegex<'-other-stuff'>
+type r7 = _StripRegex<'/edit'>
+
+export interface _ModifierExtracTResult<
+ M extends _ParamModifier | '',
+ Rest extends string
+> {
+ modifier: M
+ rest: Rest
+}
+
+export type _ExtractModifier<P extends string> =
+ P extends `${_ParamModifier}${infer Rest}`
+ ? P extends `${infer M}${Rest}`
+ ? M extends _ParamModifier
+ ? _ModifierExtracTResult<M, Rest>
+ : // impossible case
+ never
+ : // impossible case
+ never
+ : // No modifier present
+ _ModifierExtracTResult<'', P>
+
+type m1 = _ExtractModifier<''>
+type m2 = _ExtractModifier<'-rest'>
+type m3 = _ExtractModifier<'edit'>
+type m4 = _ExtractModifier<'+'>
+type m5 = _ExtractModifier<'+/edit'>
+
+export type _StripModifierAndRegex_OLD<S extends string> =
+ // do we have an escaped closing parenthesis?
+ S extends `${infer A}\\)${infer Rest}`
+ ? // the actual regexp finished before, A has no escaped )
+ A extends `${string})${infer Rest2}`
+ ? // get the modifier if there is one
+ Rest2 extends `${_ParamModifier}${infer Rest3}`
+ ? Rest2 extends `${infer M}${Rest3}`
+ ? { mod: M; rest: `${Rest3}\\)${Rest}` }
+ : never
+ : // No modifier
+ { mod: ''; rest: `${Rest2}\\)${Rest}` } // job done
+ : _RemoveUntilClosingPar<Rest> // we keep removing
+ : // simple case with no escaping
+ S extends `${string})${infer Rest}`
+ ? // extract the modifier if there is one
+ Rest extends `${_ParamModifier}${infer Rest2}`
+ ? Rest extends `${infer M}${Rest2}`
+ ? { mod: M; rest: Rest2 }
+ : never
+ : // no modifier
+ { mod: ''; rest: Rest }
+ : // nothing to remove
+ { mod: ''; rest: S }
+
/**
* Gets the possible type of a param based on its modifier M.
*
* @internal
*/
export type _RemoveUntilClosingPar<S extends string> =
+ // do we have an escaped closing parenthesis?
S extends `${infer A}\\)${infer Rest}`
- ? A extends `${string})${infer Rest2}` // the actual regexp finished before, AA has no escaped )
+ ? // the actual regexp finished before, A has no escaped )
+ A extends `${string})${infer Rest2}`
? Rest2 extends `${_ParamModifier}${infer Rest3}`
? Rest2 extends `${infer M}${Rest3}`
? `${M}}${Rest3}\\)${Rest}`
: `}${Rest}`
: never // nothing to remove, should not have been called, easier to spot bugs
+type r = _RemoveUntilClosingPar<`aouest)/end`>
+type r2 = _RemoveUntilClosingPar<`aouest`>
+
/**
* Reformats a path string `/:id(custom-regex)/:other+` by wrapping params with
* `{}` and removing custom regexps to make them easier to parse.
/**
* Builds a path string type from a path definition and an object of params.
+ *
* @example
* ```ts
* type url = PathFromParams<'/users/:id', { id: 'posva' }> -> '/users/posva'
: '' extends Prefix
? never
: `${Prefix}${Prefix extends `${string}/` ? '' : '/'}${Path}`
+
+/**
+ * @internal
+ */
+type _AlphaNumeric =
+ | 'a'
+ | 'A'
+ | 'b'
+ | 'B'
+ | 'c'
+ | 'C'
+ | 'd'
+ | 'D'
+ | 'e'
+ | 'E'
+ | 'f'
+ | 'F'
+ | 'g'
+ | 'G'
+ | 'h'
+ | 'H'
+ | 'i'
+ | 'I'
+ | 'j'
+ | 'J'
+ | 'k'
+ | 'K'
+ | 'l'
+ | 'L'
+ | 'm'
+ | 'M'
+ | 'n'
+ | 'N'
+ | 'o'
+ | 'O'
+ | 'p'
+ | 'P'
+ | 'q'
+ | 'Q'
+ | 'r'
+ | 'R'
+ | 's'
+ | 'S'
+ | 't'
+ | 'T'
+ | 'u'
+ | 'U'
+ | 'v'
+ | 'V'
+ | 'w'
+ | 'W'
+ | 'x'
+ | 'X'
+ | 'y'
+ | 'Y'
+ | '0'
+ | '1'
+ | '2'
+ | '3'
+ | '4'
+ | '5'
+ | '6'
+ | '7'
+ | '8'
+ | '9'
+ | '_'
--- /dev/null
+import {
+ RouteRecordRaw,
+ RouteNamedMap,
+ RouteStaticPathMap,
+ RouteLocationNamedRaw,
+} from '.'
+import { defineComponent } from 'vue'
+
+const Home = defineComponent({})
+const User = defineComponent({})
+const LongView = defineComponent({})
+const component = defineComponent({})
+
+function defineRoutes<R extends Readonly<RouteRecordRaw[]>>(routes: R): R {
+ return routes
+}
+
+const routes = [
+ { path: '/home', redirect: '/' },
+ {
+ path: '/',
+ components: { default: Home, other: component },
+ },
+ {
+ path: '/always-redirect',
+ component,
+ },
+ { path: '/users/:id', name: 'user', component: User, props: true },
+ { path: '/documents/:id', name: 'docs', component: User, props: true },
+ { path: '/optional/:id?', name: 'optional', component: User, props: true },
+ { path: encodeURI('/n/€'), name: 'euro', component },
+ { path: '/n/:n', name: 'increment', component },
+ { path: '/multiple/:a/:b', name: 'multiple', component },
+ { path: '/long-:n', name: 'long', component: LongView },
+ {
+ path: '/lazy',
+ component,
+ },
+ {
+ path: '/with-guard/:n',
+ name: 'guarded',
+ component,
+ },
+ { path: '/cant-leave', component },
+ {
+ path: '/children',
+ name: 'WithChildren',
+ component,
+ children: [
+ { path: '', alias: 'alias', name: 'default-child', component },
+ { path: 'a', name: 'a-child', component },
+ {
+ path: 'b',
+ name: 'WithChildrenB',
+ component,
+ children: [
+ {
+ path: '',
+ name: 'b-child',
+ component,
+ },
+ { path: 'a2', component },
+ { path: 'b2', component },
+ ],
+ },
+ ],
+ },
+ { path: '/with-data', component, name: 'WithData' },
+ { path: '/rep/:a*', component, name: 'repeat' },
+ { path: '/:data(.*)', component, name: 'NotFound' },
+ {
+ path: '/nested',
+ alias: '/anidado',
+ component,
+ name: 'Nested',
+ children: [
+ {
+ path: 'nested',
+ alias: 'a',
+ name: 'NestedNested',
+ component,
+ children: [
+ {
+ name: 'NestedNestedNested',
+ path: 'nested',
+ component,
+ },
+ ],
+ },
+ {
+ path: 'other',
+ alias: 'otherAlias',
+ component,
+ name: 'NestedOther',
+ },
+ {
+ path: 'also-as-absolute',
+ alias: '/absolute',
+ name: 'absolute-child',
+ component,
+ },
+ ],
+ },
+
+ {
+ path: '/parent/:id',
+ name: 'parent',
+ component,
+ props: true,
+ alias: '/p/:id',
+ children: [
+ // empty child
+ { path: '', name: 'child-id', component },
+ // child with absolute path. we need to add an `id` because the parent needs it
+ { path: '/p_:id/absolute-a', alias: 'as-absolute-a', component },
+ // same as above but the alias is absolute
+ { path: 'as-absolute-b', alias: '/p_:id/absolute-b', component },
+ ],
+ },
+ {
+ path: '/dynamic',
+ name: 'dynamic',
+ component,
+ end: false,
+ strict: true,
+ },
+
+ {
+ path: '/admin',
+ children: [
+ { path: '', component },
+ { path: 'dashboard', component },
+ { path: 'settings', component },
+ ],
+ },
+] as const
+
+function pushStr(route: keyof RouteStaticPathMap<typeof routes>) {}
+pushStr('')
+
+// function push1(
+// route: RouteNamedMap<typeof routes>[keyof RouteNamedMap<typeof routes>]
+// ) {}
+// push1({ })
+function pushEnd(route: keyof RouteNamedMap<typeof routes>) {}
+
+pushEnd('NotFound')
+
+function push(
+ route:
+ | keyof RouteStaticPathMap<typeof routes>
+ | {
+ name: keyof RouteNamedMap<typeof routes>
+ }
+) {}