sv-router 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,9 +27,15 @@ A feature-rich yet intuitive routing library for Svelte single-page apps.
27
27
  - ðŸŠķ **Lightweight**: Minimal impact on your bundle size.
28
28
  - 🚀 **Made for Svelte 5**: True Svelte 5 implementation with the latest features.
29
29
 
30
- ## Installation
30
+ ## Getting Started
31
31
 
32
- Add it to an existing Svelte project:
32
+ Kickstart a new project:
33
+
34
+ ```bash
35
+ npm create sv-router
36
+ ```
37
+
38
+ ...or add it to an existing project:
33
39
 
34
40
  ```bash
35
41
  npm install sv-router
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sv-router",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Modern Svelte Routing",
5
5
  "keywords": [
6
6
  "svelte",
@@ -36,29 +36,31 @@
36
36
  "esm-env": "^1.2.2"
37
37
  },
38
38
  "devDependencies": {
39
- "@changesets/cli": "^2.28.1",
40
- "@eslint/js": "^9.23.0",
41
- "@types/node": "^22.13.14",
42
- "eslint": "^9.23.0",
43
- "eslint-config-prettier": "^10.1.1",
39
+ "@changesets/cli": "^2.29.4",
40
+ "@eslint/js": "^9.28.0",
41
+ "@types/node": "^22.15.30",
42
+ "eslint": "^9.28.0",
43
+ "eslint-config-prettier": "^10.1.5",
44
44
  "eslint-plugin-simple-import-sort": "^12.1.1",
45
- "eslint-plugin-svelte": "^3.3.3",
46
- "eslint-plugin-unicorn": "^58.0.0",
47
- "globals": "^16.0.0",
45
+ "eslint-plugin-svelte": "^3.9.1",
46
+ "eslint-plugin-unicorn": "^59.0.1",
47
+ "globals": "^16.2.0",
48
48
  "prettier": "^3.5.3",
49
49
  "prettier-plugin-jsdoc": "^1.3.2",
50
- "prettier-plugin-svelte": "^3.3.3",
51
- "svelte-check": "^4.1.5",
50
+ "prettier-plugin-svelte": "^3.4.0",
51
+ "svelte-check": "^4.2.1",
52
52
  "type-testing": "^0.2.0",
53
- "typescript": "^5.8.2",
54
- "typescript-eslint": "^8.28.0",
55
- "vite": "^6.2.3",
56
- "vitest": "^3.0.9"
53
+ "typescript": "^5.8.3",
54
+ "typescript-eslint": "^8.33.1",
55
+ "vite": "^6.3.5",
56
+ "vitest": "^3.2.2"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "svelte": "^5"
60
60
  },
61
61
  "scripts": {
62
+ "create:dev": "pnpm --filter create-sv-router dev",
63
+ "create:build": "pnpm --filter create-sv-router build",
62
64
  "docs:dev": "pnpm --filter docs dev",
63
65
  "docs:build": "pnpm --filter docs build",
64
66
  "docs:preview": "pnpm --filter docs preview",
@@ -1,7 +1,7 @@
1
1
  import { BROWSER, DEV } from 'esm-env';
2
2
  import { isActive } from './helpers/is-active.js';
3
3
  import { matchRoute } from './helpers/match-route.js';
4
- import { preloadOnHover } from './helpers/preload-on-hover.js';
4
+ import { preload, preloadOnHover } from './helpers/preload.js';
5
5
  import { constructPath, join, resolveRouteComponents } from './helpers/utils.js';
6
6
  import { syncSearchParams } from './search-params.svelte.js';
7
7
 
@@ -16,6 +16,8 @@ export let params = $state({ value: {} });
16
16
 
17
17
  export let location = $state(updatedLocation());
18
18
 
19
+ let meta = $state({ value: {} });
20
+
19
21
  let navigationIndex = 0;
20
22
  let pendingNavigationIndex = 0;
21
23
 
@@ -44,10 +46,19 @@ export function createRouter(r) {
44
46
  p: constructPath,
45
47
  navigate,
46
48
  isActive,
49
+ async preload(pathname) {
50
+ await preload(routes, pathname);
51
+ },
47
52
  route: {
48
53
  get params() {
49
54
  return params.value;
50
55
  },
56
+ getParams(pathname) {
57
+ if (!isActive(pathname)) {
58
+ throw new Error(`\`${pathname}\` does not match the current route`);
59
+ }
60
+ return params.value;
61
+ },
51
62
  get pathname() {
52
63
  return /** @type {import('./index.d.ts').Path<T>} */ (location.pathname);
53
64
  },
@@ -60,6 +71,9 @@ export function createRouter(r) {
60
71
  get hash() {
61
72
  return location.hash;
62
73
  },
74
+ get meta() {
75
+ return meta.value;
76
+ },
63
77
  },
64
78
  };
65
79
  }
@@ -101,7 +115,7 @@ export async function onNavigate(path, options = {}) {
101
115
  if (base.name && matchPath.startsWith(base.name)) {
102
116
  matchPath = matchPath.slice(base.name.length) || '/';
103
117
  }
104
- const { match, layouts, hooks, params: newParams } = matchRoute(matchPath, routes);
118
+ const { match, layouts, hooks, meta: newMeta, params: newParams } = matchRoute(matchPath, routes);
105
119
 
106
120
  for (const { beforeLoad } of hooks) {
107
121
  try {
@@ -137,7 +151,8 @@ export async function onNavigate(path, options = {}) {
137
151
  } else {
138
152
  componentTree.value = routeComponents;
139
153
  }
140
- params.value = newParams || {};
154
+ params.value = newParams;
155
+ meta.value = newMeta;
141
156
  syncSearchParams();
142
157
  Object.assign(location, updatedLocation());
143
158
 
@@ -14,6 +14,7 @@ const PARAM_FILENAME_REGEX = /(?<=[/.]|^)\(?\[([\w-]+)\]\)?(\.lazy)?\.svelte$/;
14
14
  const CATCH_ALL_FILENAME_REGEX = /(?<=[/.]|^)\(?\[\.\.\.([\w-]+)\]\)?(\.lazy)?\.svelte$/; // [...any].svelte, [...any].lazy.svelte, ([...any]).svelte
15
15
  const OUT_OF_LAYOUT_FILENAME_REGEX = /(?<=[/.]|^)\(\[\.?\.?\.?([\w-]+)\]\)(\.lazy)?\.svelte$/; // ([any]).svelte, ([...any]).lazy.svelte
16
16
  const HOOKS_FILENAME_REGEX = /(?<=[/.]|^)(hooks)(\.svelte)?\.(js|ts)$/; // hooks.js, hooks.svelte.js, hooks.ts, hooks.svelte.ts
17
+ const META_FILENAME_REGEX = /(?<=[/.]|^)(meta)(\.svelte)?\.(js|ts)$/; // meta.js, meta.svelte.js, meta.ts, meta.svelte.ts
17
18
 
18
19
  /**
19
20
  * @param {string} routesPath
@@ -44,7 +45,11 @@ export function buildFileTree(routesPath) {
44
45
  tree.push({ name: entry, tree: buildFileTree(path.join(routesPath, entry)) });
45
46
  continue;
46
47
  }
47
- if (!entry.endsWith('.svelte') && !HOOKS_FILENAME_REGEX.test(entry)) {
48
+ if (
49
+ !entry.endsWith('.svelte') &&
50
+ !HOOKS_FILENAME_REGEX.test(entry) &&
51
+ !META_FILENAME_REGEX.test(entry)
52
+ ) {
48
53
  continue;
49
54
  }
50
55
  tree.push(entry);
@@ -67,6 +72,10 @@ export function createRouteMap(fileTree, prefix = '') {
67
72
  result['hooks'] = prefix + entry;
68
73
  continue;
69
74
  }
75
+ if (META_FILENAME_REGEX.test(entry)) {
76
+ result['meta'] = prefix + entry;
77
+ continue;
78
+ }
70
79
  continue;
71
80
  }
72
81
 
@@ -101,7 +110,8 @@ export function createRouteMap(fileTree, prefix = '') {
101
110
  result['/' + filePathToRoute(entry.replace('.svelte', ''))] = prefix + entry;
102
111
  } else {
103
112
  const entryName = filePathToRoute(entry.name);
104
- result['/' + entryName] = createRouteMap(entry.tree, prefix + entryName + '/');
113
+ const paramFolder = entryName.replace(/^\[(.*)\]$/, ':$1');
114
+ result['/' + paramFolder] = createRouteMap(entry.tree, prefix + entryName + '/');
105
115
  }
106
116
  }
107
117
  return result;
@@ -137,7 +147,11 @@ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
137
147
  for (const [key, value] of Object.entries(routes)) {
138
148
  if (typeof value === 'object') {
139
149
  result[key] = handleImports(value, routesPath);
140
- } else if (key === 'hooks' || (!value.endsWith('.lazy.svelte') && !allLazy)) {
150
+ } else if (
151
+ key === 'hooks' ||
152
+ key === 'meta' ||
153
+ (!value.endsWith('.lazy.svelte') && !allLazy)
154
+ ) {
141
155
  const variableName = pathToCorrectCasing(value);
142
156
  importsMap.set(variableName, routesPath + value);
143
157
  result[key] = variableName;
@@ -163,7 +177,7 @@ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
163
177
  `import { createRouter } from 'sv-router';`,
164
178
  ...imports,
165
179
  '',
166
- `export const { p, navigate, isActive, route } = createRouter(${stringifiedRoutes});`,
180
+ `export const { p, navigate, isActive, preload, route } = createRouter(${stringifiedRoutes});`,
167
181
  ].join('\n');
168
182
  }
169
183
 
@@ -189,6 +203,7 @@ export function pathToCorrectCasing(value) {
189
203
  extractLastPart(CATCH_ALL_FILENAME_REGEX) ||
190
204
  extractLastPart(PARAM_FILENAME_REGEX) ||
191
205
  extractLastPart(HOOKS_FILENAME_REGEX) ||
206
+ extractLastPart(META_FILENAME_REGEX) ||
192
207
  extractLastPart(FILENAME_REGEX);
193
208
  if (!lastPart) {
194
209
  throw new Error(`Invalid filename: ${value}`);
@@ -196,7 +211,8 @@ export function pathToCorrectCasing(value) {
196
211
  parts.push(...lastPart.split('-'));
197
212
 
198
213
  const uppercased = parts.map((part, index) => {
199
- if (index === 0 && lastPart === 'hooks') return part;
214
+ if (index === 0 && (lastPart === 'hooks' || lastPart === 'meta')) return part;
215
+ part = part.replace(/^\[(.*)\]$/, '$1');
200
216
  return part.charAt(0).toUpperCase() + part.slice(1);
201
217
  });
202
218
  return uppercased.join('');
@@ -38,10 +38,12 @@ function compare(compareFn, pathname, params) {
38
38
  const routeParts = location.pathname.split('/').slice(1);
39
39
  for (const [index, pathPart] of pathParts.entries()) {
40
40
  const routePart = routeParts[index];
41
- if (routePart.startsWith(':')) {
41
+ if (pathPart.startsWith(':')) {
42
42
  continue;
43
43
  }
44
- return compareFn(pathPart, routePart);
44
+ if (pathPart !== routePart) {
45
+ return false;
46
+ }
45
47
  }
46
- return false;
48
+ return true;
47
49
  }
@@ -6,6 +6,8 @@
6
6
  * @typedef {import('../index.d.ts').Hooks} Hooks
7
7
  *
8
8
  * @typedef {import('../index.d.ts').Routes} Routes
9
+ *
10
+ * @typedef {import('../index.d.ts').RouteMeta} RouteMeta
9
11
  */
10
12
 
11
13
  /**
@@ -15,6 +17,7 @@
15
17
  * match: RouteComponent | undefined;
16
18
  * layouts: LayoutComponent[];
17
19
  * hooks: Hooks[];
20
+ * meta: RouteMeta;
18
21
  * params: Record<string, string>;
19
22
  * breakFromLayouts: boolean;
20
23
  * }}
@@ -39,6 +42,9 @@ export function matchRoute(pathname, routes) {
39
42
  /** @type {Record<string, string>} */
40
43
  let params = {};
41
44
 
45
+ /** @type {RouteMeta} */
46
+ let meta = {};
47
+
42
48
  let breakFromLayouts = false;
43
49
 
44
50
  outer: for (const route of allRoutes) {
@@ -69,7 +75,7 @@ export function matchRoute(pathname, routes) {
69
75
  );
70
76
  match = /** @type {RouteComponent} */ (routes[resolvedPath]);
71
77
  break outer;
72
- } else if (routePart !== pathPart) {
78
+ } else if (routePart !== pathPart?.toLowerCase()) {
73
79
  break;
74
80
  }
75
81
 
@@ -89,6 +95,10 @@ export function matchRoute(pathname, routes) {
89
95
  hooks.push(routes.hooks);
90
96
  }
91
97
 
98
+ if ('meta' in routes && routes.meta) {
99
+ meta = { ...meta, ...routes.meta };
100
+ }
101
+
92
102
  if (typeof routeMatch === 'function') {
93
103
  if (routeParts.length === pathParts.length) {
94
104
  match = routeMatch;
@@ -103,6 +113,7 @@ export function matchRoute(pathname, routes) {
103
113
  match = result.match;
104
114
  params = { ...params, ...result.params };
105
115
  hooks.push(...result.hooks);
116
+ meta = { ...meta, ...result.meta };
106
117
  if (result.breakFromLayouts) {
107
118
  layouts = [];
108
119
  } else {
@@ -113,7 +124,7 @@ export function matchRoute(pathname, routes) {
113
124
  }
114
125
  }
115
126
 
116
- return { match, layouts, hooks, params, breakFromLayouts };
127
+ return { match, layouts, hooks, params, meta, breakFromLayouts };
117
128
  }
118
129
 
119
130
  /**
@@ -1,9 +1,21 @@
1
1
  import { matchRoute } from './match-route.js';
2
2
  import { resolveRouteComponents } from './utils.js';
3
3
 
4
+ /**
5
+ * @param {import('../index.js').Routes} routes
6
+ * @param {string} path
7
+ */
8
+ export async function preload(routes, path) {
9
+ const { match, layouts, hooks } = matchRoute(path, routes);
10
+ for (const { onPreload } of hooks) {
11
+ onPreload?.();
12
+ }
13
+ await resolveRouteComponents(match ? [...layouts, match] : layouts);
14
+ }
15
+
4
16
  const linkSet = new Set();
5
17
 
6
- /** @param {import('../index.d.ts').Routes} routes */
18
+ /** @param {import('../index.js').Routes} routes */
7
19
  export function preloadOnHover(routes) {
8
20
  const observer = new MutationObserver(() => {
9
21
  const links = document.querySelectorAll('a[data-preload]');
@@ -15,8 +27,7 @@ export function preloadOnHover(routes) {
15
27
  link.removeEventListener('mouseenter', callback);
16
28
  const href = link.getAttribute('href');
17
29
  if (!href) return;
18
- const { match, layouts } = matchRoute(href, routes);
19
- resolveRouteComponents(match ? [...layouts, match] : layouts);
30
+ preload(routes, href);
20
31
  });
21
32
  }
22
33
  });
@@ -25,7 +25,7 @@ export function resolveRouteComponents(input) {
25
25
  * @param {import('../index.d.ts').RouteComponent} input
26
26
  * @returns {Promise<import('svelte').Component>}
27
27
  */
28
- export function resolveRouteComponent(input) {
28
+ function resolveRouteComponent(input) {
29
29
  return new Promise((resolve) => {
30
30
  if (isLazyImport(input)) {
31
31
  Promise.resolve(input()).then((module) => {
@@ -26,9 +26,12 @@ export function validateRoutes(routes) {
26
26
  export function getRoutePaths(routes) {
27
27
  const paths = [];
28
28
  for (const [key, value] of Object.entries(routes)) {
29
+ if (['layout', 'hooks', 'meta'].includes(key)) {
30
+ continue;
31
+ }
29
32
  if (typeof value === 'object') {
30
33
  paths.push(
31
- ...getRoutePaths(value).map((path) => {
34
+ ...getRoutePaths(/** @type {import('../index.d.ts').Routes} */ (value)).map((path) => {
32
35
  if (path === '*') {
33
36
  return key + '/*';
34
37
  }
package/src/index.d.ts CHANGED
@@ -7,10 +7,11 @@ import type { Action } from 'svelte/action';
7
7
  * to `is-active`.
8
8
  *
9
9
  * ```svelte
10
- * <a href="/about" use:isActiveLink={{ className: 'active-link' }}>
10
+ * <a href={p('/about')} use:isActiveLink={{ className: 'active-link' }}>
11
11
  * ```
12
12
  */
13
13
  export const isActiveLink: IsActiveLink;
14
+
14
15
  /**
15
16
  * Setup a new router instance with the given routes.
16
17
  *
@@ -23,11 +24,13 @@ export const isActiveLink: IsActiveLink;
23
24
  * ```
24
25
  */
25
26
  export function createRouter<T extends Routes>(r: T): RouterApi<T>;
27
+
26
28
  /**
27
29
  * The component that will render the current route. You can pass a `base` prop to set the base path
28
30
  * that is prepended to every url.
29
31
  */
30
32
  export const Router: Component<{ base?: string }>;
33
+
31
34
  /**
32
35
  * The reactive search params of the URL. It is just a wrapper around `SvelteURLSearchParam` that
33
36
  * will update the url on change.
@@ -62,6 +65,8 @@ export type Hooks = {
62
65
  beforeLoad?(): void | Promise<void>;
63
66
  /** A function that will be called after the route is loaded. */
64
67
  afterLoad?(): void;
68
+ /** A function that will be called when the route is preloaded. */
69
+ onPreload?(): void;
65
70
  };
66
71
 
67
72
  export type Routes = {
@@ -69,6 +74,7 @@ export type Routes = {
69
74
  [_: `*${string}` | `(*${string})`]: RouteComponent | undefined;
70
75
  layout?: LayoutComponent;
71
76
  hooks?: Hooks;
77
+ meta?: RouteMeta;
72
78
  };
73
79
 
74
80
  export type IsActiveLink = Action<
@@ -76,6 +82,20 @@ export type IsActiveLink = Action<
76
82
  { className?: string; startsWith?: boolean } | undefined
77
83
  >;
78
84
 
85
+ /**
86
+ * Route metadata that can be extended via module augmentation.
87
+ *
88
+ * @example
89
+ * declare module 'sv-router' {
90
+ * interface RouteMeta {
91
+ * public?: boolean;
92
+ * requiresAuth?: boolean;
93
+ * }
94
+ * }
95
+ */
96
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
97
+ export interface RouteMeta {}
98
+
79
99
  export type RouterApi<T extends Routes> = {
80
100
  /**
81
101
  * Construct a path while ensuring type safety.
@@ -90,8 +110,9 @@ export type RouterApi<T extends Routes> = {
90
110
  * @param params The parameters to replace in the route.
91
111
  */
92
112
  p<U extends Path<T>>(...args: ConstructPathArgs<U>): string;
113
+
93
114
  /**
94
- * Navigate programatically to a route.
115
+ * Navigate programmatically to a route.
95
116
  *
96
117
  * ```js
97
118
  * navigate('/users');
@@ -124,6 +145,14 @@ export type RouterApi<T extends Routes> = {
124
145
  <U extends Path<T>>(...args: IsActiveArgs<U>): boolean;
125
146
  startsWith<U extends Path<T>>(...args: IsActiveArgs<U>): boolean;
126
147
  };
148
+
149
+ /**
150
+ * Preloads the given route.
151
+ *
152
+ * @param path The route to preload.
153
+ */
154
+ preload<U extends Path<T>>(path: U): Promise<void>;
155
+
127
156
  route: {
128
157
  /**
129
158
  * An object containing the parameters of the current route.
@@ -133,19 +162,32 @@ export type RouterApi<T extends Routes> = {
133
162
  * 'hello-world', commentId: '123' }`.
134
163
  */
135
164
  params: AllParams<T>;
165
+ /**
166
+ * Extract parameters from the given pathname. Will throw if the pathname does not match the
167
+ * current route.
168
+ *
169
+ * ```ts
170
+ * route.getParams('/posts/:slug').slug;
171
+ * ```
172
+ *
173
+ * @param pathname
174
+ */
175
+ getParams<U extends Path<T>>(pathname: U): Record<ExtractParams<U>, string>;
136
176
  /** The reactive pathname of the URL. */
137
- pathname: Path<T>;
177
+ pathname: (Path<T, true> & {}) | (string & {});
138
178
  /** The reactive query string part of the URL. */
139
179
  search: string;
140
180
  /** The reactive history state that can be passed to the `navigate` function. */
141
181
  state: unknown;
142
182
  /** The reactive hash part of the URL. */
143
183
  hash: string;
184
+ /** Arbitrary metadata associated with the route. */
185
+ meta: RouteMeta;
144
186
  };
145
187
  };
146
188
 
147
- export type Path<T extends Routes> = RemoveParenthesis<
148
- RemoveLastSlash<RecursiveKeys<StripNonRoutes<T>>>
189
+ export type Path<T extends Routes, AnyParam extends boolean = false> = RemoveParenthesis<
190
+ RemoveLastSlash<RecursiveKeys<StripNonRoutes<T>, '', AnyParam>>
149
191
  >;
150
192
 
151
193
  export type ConstructPathArgs<T extends string> =
@@ -155,9 +197,13 @@ export type IsActiveArgs<T extends string> =
155
197
  PathParams<T> extends never ? [T] : [T] | [T, PathParams<T>];
156
198
 
157
199
  export type PathParams<T extends string> =
158
- ExtractParams<T> extends never ? never : Record<ExtractParams<T>, string>;
200
+ ExtractParams<RemoveParenthesis<T>> extends never
201
+ ? never
202
+ : Record<ExtractParams<RemoveParenthesis<T>>, string>;
159
203
 
160
- export type AllParams<T extends Routes> = Partial<Record<ExtractParams<RecursiveKeys<T>>, string>>;
204
+ export type AllParams<T extends Routes> = Partial<
205
+ Record<ExtractParams<RemoveParenthesis<RecursiveKeys<T>>>, string>
206
+ >;
161
207
 
162
208
  export type NavigateOptions =
163
209
  | {
@@ -192,17 +238,33 @@ type StripNonRoutes<T extends Routes> = {
192
238
  ? never
193
239
  : K extends 'hooks'
194
240
  ? never
195
- : K]: T[K] extends Routes ? StripNonRoutes<T[K]> : T[K];
241
+ : K extends 'meta'
242
+ ? never
243
+ : K]: T[K] extends Routes ? StripNonRoutes<T[K]> : T[K];
196
244
  };
197
245
 
198
- type RecursiveKeys<T extends Routes, Prefix extends string = ''> = {
246
+ type RecursiveKeys<
247
+ T extends Routes,
248
+ Prefix extends string = '',
249
+ AnyParam extends boolean = false,
250
+ > = {
199
251
  [K in keyof T]: K extends string
200
252
  ? T[K] extends Routes
201
- ? RecursiveKeys<T[K], `${Prefix}${K}`>
202
- : `${Prefix}${K}`
253
+ ? RecursiveKeys<
254
+ T[K],
255
+ `${Prefix}${AnyParam extends true ? ReplaceParamWithString<K> : K}`,
256
+ AnyParam
257
+ >
258
+ : `${Prefix}${AnyParam extends true ? ReplaceParamWithString<K> : K}`
203
259
  : never;
204
260
  }[keyof T];
205
261
 
262
+ type ReplaceParamWithString<T extends string> = T extends `/:${string}`
263
+ ? `/${string}`
264
+ : T extends `/(:${string})`
265
+ ? `/${string}`
266
+ : T;
267
+
206
268
  type RemoveLastSlash<T extends string> = T extends '/' ? T : T extends `${infer R}/` ? R : T;
207
269
 
208
270
  type RemoveParenthesis<T extends string> = T extends `${infer A}(${infer B})${infer C}`
@@ -211,14 +273,10 @@ type RemoveParenthesis<T extends string> = T extends `${infer A}(${infer B})${in
211
273
 
212
274
  type ExtractParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}`
213
275
  ? Param | ExtractParams<`/${Rest}`>
214
- : T extends `${string}(:${infer Param})`
276
+ : T extends `${string}:${infer Param}`
215
277
  ? Param
216
- : T extends `${string}:${infer Param}`
217
- ? Param
218
- : T extends `${string}(*${infer Param})`
219
- ? Param
220
- : T extends `${string}*${infer Param}`
221
- ? Param extends ''
222
- ? never
223
- : Param
224
- : never;
278
+ : T extends `${string}*${infer Param}`
279
+ ? Param extends ''
280
+ ? never
281
+ : Param
282
+ : never;