angular-typed-router 0.1.1-3 → 1.0.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 +8 -12
- package/fesm2022/angular-typed-router.mjs +19 -8
- package/fesm2022/angular-typed-router.mjs.map +1 -1
- package/package.json +22 -5
- package/types/angular-typed-router.d.ts +195 -0
- package/index.d.ts +0 -68
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# angular-typed-router
|
|
2
2
|
|
|
3
|
-
## Attention: Still in experimental phase
|
|
4
|
-
|
|
5
3
|
Type-safe ergonomic primitives on top of Angular's standalone router. Automatically derive a compile‑time union of valid route URL strings (`Path`) and strongly typed navigation command tuples (`Commands`) from the consumer application's `Routes` definition – without generating code or adding runtime weight.
|
|
6
4
|
|
|
7
5
|
## Why
|
|
@@ -16,7 +14,7 @@ No decorators, no custom builders, no code generation – just TypeScript type i
|
|
|
16
14
|
|
|
17
15
|
## Installation
|
|
18
16
|
|
|
19
|
-
`ng add angular-
|
|
17
|
+
`ng add angular-typed-router` will set up the package and create a declaration file for you.
|
|
20
18
|
|
|
21
19
|
Or install manually:
|
|
22
20
|
|
|
@@ -119,14 +117,13 @@ import { TypedRouter, TypedRouterLink, Path, Commands, UserTypedRoutes, RoutePar
|
|
|
119
117
|
- `Path` – Union of every reachable concrete URL path produced from your route tree (includes parameterized expansions with `string` in place of `:param` segments).
|
|
120
118
|
- `Commands` – Union of tuple command arrays representing valid `Router.navigate()` inputs (each static segment as a literal, each parameter position as `string`).
|
|
121
119
|
- `UserTypedRoutes` – Empty interface you augment with your `routes` reference.
|
|
122
|
-
- `ExtractPathsFromRoutes<Routes>` – Utility type if you need to compute from an arbitrary `Routes` array manually.
|
|
123
120
|
- `RouteParamTypes` – Interface you can augment to specify types for route parameters by name (e.g. `id: ${number}`).
|
|
124
121
|
|
|
125
122
|
## How It Works
|
|
126
123
|
|
|
127
124
|
1. You augment `UserTypedRoutes` with the literal `const` route array.
|
|
128
125
|
2. Type utilities recursively walk the route tree (including lazily loaded routes via `loadChildren` returning `Route[]` or `{ routes }`).
|
|
129
|
-
3. Each navigable route (component / loadComponent / redirectTo) contributes a path string. Param segments (`:id`)
|
|
126
|
+
3. Each navigable route (component / loadComponent / redirectTo) contributes a path string. Param segments (e.g. `:id`) can be typed through declaration merging of `RouteParamTypes`.
|
|
130
127
|
4. Child paths are joined with parents to form final concrete path unions.
|
|
131
128
|
5. A tuple transformation creates the `Commands` variants.
|
|
132
129
|
|
|
@@ -161,7 +158,7 @@ and a `Commands` tuple like:
|
|
|
161
158
|
['projects', IdParamType, 'details', SectionParamType]
|
|
162
159
|
```
|
|
163
160
|
|
|
164
|
-
You pass real runtime values for the `IdParamType` and `SectionParamType` positions.
|
|
161
|
+
You pass real runtime values for the `IdParamType` and `SectionParamType` positions.
|
|
165
162
|
|
|
166
163
|
## Usage Patterns
|
|
167
164
|
|
|
@@ -195,17 +192,16 @@ Keep the augmentation in a `.d.ts` that is included by `tsconfig.app.json` (`inc
|
|
|
195
192
|
|
|
196
193
|
- The augmentation file is included.
|
|
197
194
|
- The `routes` constant is `as const satisfies Routes`.
|
|
198
|
-
- No circular import (augmentation file should only import the routes, nothing else runtime-heavy).
|
|
199
195
|
|
|
200
|
-
##
|
|
196
|
+
## Tradeoffs
|
|
201
197
|
|
|
202
|
-
| Concern | Status / Rationale
|
|
203
|
-
| ----------------------------------
|
|
204
|
-
| `relativeTo` (relative navigation) | Not supported – all inferred `Path` / `Commands` are absolute. Use absolute commands. |
|
|
198
|
+
| Concern | Status / Rationale |
|
|
199
|
+
| ---------------------------------- |-------------------------------------------------------------------------------------------------|
|
|
200
|
+
| `relativeTo` (relative navigation) | Not supported – all inferred `Path` / `Commands` are absolute. Use absolute paths and commands. |
|
|
205
201
|
|
|
206
202
|
## ESLint Recommendation (Optional)
|
|
207
203
|
|
|
208
|
-
You can use `angular-typed-router-eslint` plugin to forbid untyped navigation calls
|
|
204
|
+
You can use `angular-typed-router-eslint` plugin to forbid untyped navigation calls and usage of `relativeTo`.
|
|
209
205
|
|
|
210
206
|
## Troubleshooting
|
|
211
207
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, Input, Directive } from '@angular/core';
|
|
2
|
+
import { Injectable, forwardRef, Input, Directive } from '@angular/core';
|
|
3
3
|
import { Router, RouterLink } from '@angular/router';
|
|
4
4
|
|
|
5
5
|
class TypedRouter extends Router {
|
|
@@ -12,10 +12,10 @@ class TypedRouter extends Router {
|
|
|
12
12
|
createUrlTree(commands, navigationExtras) {
|
|
13
13
|
return super.createUrlTree(commands, navigationExtras);
|
|
14
14
|
}
|
|
15
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
16
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
15
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TypedRouter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
16
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TypedRouter, providedIn: 'root' });
|
|
17
17
|
}
|
|
18
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
18
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TypedRouter, decorators: [{
|
|
19
19
|
type: Injectable,
|
|
20
20
|
args: [{
|
|
21
21
|
providedIn: 'root',
|
|
@@ -26,13 +26,24 @@ class TypedRouterLink extends RouterLink {
|
|
|
26
26
|
set routerLink(commandsOrUrlTree) {
|
|
27
27
|
super.routerLink = commandsOrUrlTree;
|
|
28
28
|
}
|
|
29
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
30
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TypedRouterLink, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
30
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: TypedRouterLink, isStandalone: true, selector: "[routerLink]", inputs: { routerLink: "routerLink" }, providers: [
|
|
31
|
+
{
|
|
32
|
+
provide: RouterLink,
|
|
33
|
+
useExisting: forwardRef(() => TypedRouterLink),
|
|
34
|
+
}
|
|
35
|
+
], usesInheritance: true, ngImport: i0 });
|
|
31
36
|
}
|
|
32
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
37
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TypedRouterLink, decorators: [{
|
|
33
38
|
type: Directive,
|
|
34
39
|
args: [{
|
|
35
|
-
selector: '[routerLink]'
|
|
40
|
+
selector: '[routerLink]',
|
|
41
|
+
providers: [
|
|
42
|
+
{
|
|
43
|
+
provide: RouterLink,
|
|
44
|
+
useExisting: forwardRef(() => TypedRouterLink),
|
|
45
|
+
}
|
|
46
|
+
]
|
|
36
47
|
}]
|
|
37
48
|
}], propDecorators: { routerLink: [{
|
|
38
49
|
type: Input
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-typed-router.mjs","sources":["
|
|
1
|
+
{"version":3,"file":"angular-typed-router.mjs","sources":["../../../../libs/typed-router/src/lib/router.ts","../../../../libs/typed-router/src/lib/router-link.ts","../../../../libs/typed-router/src/angular-typed-router.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport {\n NavigationBehaviorOptions,\n NavigationExtras,\n Router,\n UrlCreationOptions,\n UrlTree,\n} from '@angular/router';\nimport { Commands, Path } from './typed-routes';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class TypedRouter extends Router {\n override navigate(\n commands: Commands,\n extras?: NavigationExtras\n ): Promise<boolean> {\n return super.navigate(commands, extras);\n }\n\n override navigateByUrl(\n url: Path,\n extras?: NavigationBehaviorOptions\n ): Promise<boolean> {\n return super.navigateByUrl(url, extras);\n }\n\n override createUrlTree(\n commands: Commands,\n navigationExtras?: UrlCreationOptions\n ): UrlTree {\n return super.createUrlTree(commands, navigationExtras);\n }\n}\n","import { Directive, forwardRef, Input } from '@angular/core';\nimport { RouterLink, UrlTree } from '@angular/router';\nimport { Commands, Path } from './typed-routes';\n\n@Directive({\n selector: '[routerLink]',\n providers: [\n {\n provide: RouterLink,\n useExisting: forwardRef(() => TypedRouterLink),\n }\n ]\n})\nexport class TypedRouterLink extends RouterLink {\n @Input()\n override set routerLink(commandsOrUrlTree: Commands | Path | UrlTree | null | undefined) {\n super.routerLink = commandsOrUrlTree;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAaM,MAAO,WAAY,SAAQ,MAAM,CAAA;IAC5B,QAAQ,CACf,QAAkB,EAClB,MAAyB,EAAA;QAEzB,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IACzC;IAES,aAAa,CACpB,GAAS,EACT,MAAkC,EAAA;QAElC,OAAO,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC;IACzC;IAES,aAAa,CACpB,QAAkB,EAClB,gBAAqC,EAAA;QAErC,OAAO,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACxD;uGApBW,WAAW,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAX,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAW,cAFV,MAAM,EAAA,CAAA;;2FAEP,WAAW,EAAA,UAAA,EAAA,CAAA;kBAHvB,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE,MAAM;AACnB,iBAAA;;;ACCK,MAAO,eAAgB,SAAQ,UAAU,CAAA;IAC7C,IACa,UAAU,CAAC,iBAA+D,EAAA;AACrF,QAAA,KAAK,CAAC,UAAU,GAAG,iBAAiB;IACtC;uGAJW,eAAe,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAf,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,eAAe,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,EAAA,UAAA,EAAA,YAAA,EAAA,EAAA,SAAA,EAPf;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,UAAU;AACnB,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,eAAe,CAAC;AAC/C;AACF,SAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAEU,eAAe,EAAA,UAAA,EAAA,CAAA;kBAT3B,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,cAAc;AACxB,oBAAA,SAAS,EAAE;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,UAAU;AACnB,4BAAA,WAAW,EAAE,UAAU,CAAC,qBAAqB,CAAC;AAC/C;AACF;AACF,iBAAA;;sBAEE;;;ACdH;;AAEG;;;;"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "angular-typed-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Type-safe Angular Router. Compile-time Path union and typed navigation commands inferred from your Routes array. Zero runtime cost.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"router",
|
|
8
|
+
"typescript",
|
|
9
|
+
"type-safe",
|
|
10
|
+
"typed",
|
|
11
|
+
"routing",
|
|
12
|
+
"angular-router"
|
|
13
|
+
],
|
|
14
|
+
"author": "Dominic Bachmann",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"homepage": "https://github.com/dominicbachmann/angular-typed-router#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/dominicbachmann/angular-typed-router/issues"
|
|
19
|
+
},
|
|
4
20
|
"peerDependencies": {
|
|
5
|
-
"@angular/common": "^
|
|
6
|
-
"@angular/core": "^
|
|
21
|
+
"@angular/common": "^21.0.0",
|
|
22
|
+
"@angular/core": "^21.0.0",
|
|
23
|
+
"@angular/router": "^21.0.0"
|
|
7
24
|
},
|
|
8
25
|
"repository": {
|
|
9
26
|
"type": "git",
|
|
@@ -16,13 +33,13 @@
|
|
|
16
33
|
"save": "dependencies"
|
|
17
34
|
},
|
|
18
35
|
"module": "fesm2022/angular-typed-router.mjs",
|
|
19
|
-
"typings": "
|
|
36
|
+
"typings": "types/angular-typed-router.d.ts",
|
|
20
37
|
"exports": {
|
|
21
38
|
"./package.json": {
|
|
22
39
|
"default": "./package.json"
|
|
23
40
|
},
|
|
24
41
|
".": {
|
|
25
|
-
"types": "./
|
|
42
|
+
"types": "./types/angular-typed-router.d.ts",
|
|
26
43
|
"default": "./fesm2022/angular-typed-router.mjs"
|
|
27
44
|
}
|
|
28
45
|
},
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Route, Router, NavigationExtras, NavigationBehaviorOptions, UrlCreationOptions, UrlTree, RouterLink } from '@angular/router';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* True if a Route is navigable on its own — i.e. it can be reached by a URL
|
|
6
|
+
* because it renders a component or redirects.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* IsNavigable<{ path: 'a', component: C }> // true
|
|
10
|
+
* IsNavigable<{ path: 'a', loadComponent: () => Promise<C> }> // true
|
|
11
|
+
* IsNavigable<{ path: 'a', redirectTo: 'b' }> // true
|
|
12
|
+
* IsNavigable<{ path: 'a', children: [...] }> // false
|
|
13
|
+
*/
|
|
14
|
+
type IsNavigable<R extends Route> = R extends {
|
|
15
|
+
component: any;
|
|
16
|
+
} ? true : R extends {
|
|
17
|
+
loadComponent: any;
|
|
18
|
+
} ? true : R extends {
|
|
19
|
+
redirectTo: any;
|
|
20
|
+
} ? true : false;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reads a Route's `path` property if it's a string, otherwise returns the
|
|
24
|
+
* empty string. Used so a path-less Route (allowed by Angular) contributes
|
|
25
|
+
* no segment when joined with its parents or children.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* RoutePathOrEmpty<{ path: 'home' }> // 'home'
|
|
29
|
+
* RoutePathOrEmpty<{ component: C }> // ''
|
|
30
|
+
*/
|
|
31
|
+
type RoutePathOrEmpty<R extends Route> = R['path'] extends string ? R['path'] : '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unwraps a Route's `loadChildren` to its underlying `Route[]`.
|
|
35
|
+
*
|
|
36
|
+
* Returns `[]` if `loadChildren` is missing or returns an unrecognized shape.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ExtractLazyChildRoutes<{
|
|
40
|
+
* path: 'lazy',
|
|
41
|
+
* loadChildren: () => Promise<[{ path: 'sub', component: C }]>
|
|
42
|
+
* }>
|
|
43
|
+
* // [{ path: 'sub', component: C }]
|
|
44
|
+
*/
|
|
45
|
+
type ExtractLazyChildRoutes<R extends Route> = R['loadChildren'] extends (...args: any) => Promise<infer M> ? M extends readonly Route[] ? M : M extends {
|
|
46
|
+
routes: readonly Route[];
|
|
47
|
+
} ? M['routes'] : [] : [];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Joins two path segments with `/`, treating an empty segment as a no-op.
|
|
51
|
+
* Prevents leading/trailing/double slashes when a route has no path or sits
|
|
52
|
+
* at the root.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* JoinPathSegments<'a', 'b'> // 'a/b'
|
|
56
|
+
* JoinPathSegments<'', 'b'> // 'b'
|
|
57
|
+
* JoinPathSegments<'a', ''> // 'a'
|
|
58
|
+
* JoinPathSegments<'', ''> // ''
|
|
59
|
+
*/
|
|
60
|
+
type JoinPathSegments<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Emits every navigable path reachable from a single Route, with `:paramName`
|
|
64
|
+
* markers preserved. A Route contributes:
|
|
65
|
+
* - itself, if navigable (has component / loadComponent / redirectTo)
|
|
66
|
+
* - each of its children, prefixed with its own path
|
|
67
|
+
* - each of its lazy-loaded children, prefixed with its own path
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ExtractRawPathsFromRoute<{
|
|
71
|
+
* path: 'parent', component: C,
|
|
72
|
+
* children: [{ path: 'child', component: C }]
|
|
73
|
+
* }>
|
|
74
|
+
* // 'parent' | 'parent/child'
|
|
75
|
+
*/
|
|
76
|
+
type ExtractRawPathsFromRoute<R extends Route, Prefix extends string = ''> = (IsNavigable<R> extends true ? JoinPathSegments<Prefix, RoutePathOrEmpty<R>> : never) | (R['children'] extends readonly Route[] ? ExtractRawPaths<R['children'], JoinPathSegments<Prefix, RoutePathOrEmpty<R>>> : never) | (R['loadChildren'] extends () => Promise<any> ? ExtractRawPaths<ExtractLazyChildRoutes<R>, JoinPathSegments<Prefix, RoutePathOrEmpty<R>>> : never);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Distributes over a Routes array, emitting every navigable path with
|
|
80
|
+
* `:paramName` markers intact. `Prefix` accumulates the parent path on
|
|
81
|
+
* each recursion step.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ExtractRawPaths<[
|
|
85
|
+
* { path: 'home', component: C },
|
|
86
|
+
* { path: 'user/:id', component: C }
|
|
87
|
+
* ]>
|
|
88
|
+
* // 'home' | 'user/:id'
|
|
89
|
+
*/
|
|
90
|
+
type ExtractRawPaths<Routes extends readonly Route[], Prefix extends string = ''> = Routes[number] extends infer R ? R extends Route ? ExtractRawPathsFromRoute<R, Prefix> : never : never;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Augmentation target for declaring concrete value types per route parameter
|
|
94
|
+
* name. Consumers augment via `'angular-typed-router'`:
|
|
95
|
+
*
|
|
96
|
+
* declare module 'angular-typed-router' {
|
|
97
|
+
* interface RouteParamTypes {
|
|
98
|
+
* 'org-id': OrgId;
|
|
99
|
+
* 'project-id': ProjectId;
|
|
100
|
+
* }
|
|
101
|
+
* }
|
|
102
|
+
*
|
|
103
|
+
* Each key matches a `:paramName` segment in the consumer's Routes; each value
|
|
104
|
+
* is the type that the param resolves to in `Path` and `Commands`.
|
|
105
|
+
*/
|
|
106
|
+
interface RouteParamTypes {
|
|
107
|
+
}
|
|
108
|
+
declare const __rootCatchAll: unique symbol;
|
|
109
|
+
/**
|
|
110
|
+
* Brand for the bare-root `**` catch-all path. Distinguishes a route declared
|
|
111
|
+
* as `path: '**'` (matches anything from the root) from an arbitrary `string`.
|
|
112
|
+
*/
|
|
113
|
+
type RootCatchAll = string & {
|
|
114
|
+
readonly [__rootCatchAll]: true;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Resolves a single `:paramName` to its declared value type.
|
|
119
|
+
*
|
|
120
|
+
* Returns `never` for undeclared params, so a route with an undeclared
|
|
121
|
+
* `:param` disappears from both Path and Commands.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* declare module './route-param-types' {
|
|
125
|
+
* interface RouteParamTypes { id: UserId }
|
|
126
|
+
* }
|
|
127
|
+
* type X = ResolveParam<'id'> // UserId
|
|
128
|
+
* type Y = ResolveParam<'unknown'> // never
|
|
129
|
+
*/
|
|
130
|
+
type ResolveParam<Name extends string> = Name extends keyof RouteParamTypes ? RouteParamTypes[Name] : never;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Substitutes `:paramName` markers with their resolved types, producing a
|
|
134
|
+
* URL-string template-literal type. Brand types survive as type-level
|
|
135
|
+
* placeholders (`/user/${UserId}`).
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* RawPathToUrl<'user/:id'> // `user/${UserId}` (assuming id: UserId)
|
|
139
|
+
* RawPathToUrl<'home'> // 'home'
|
|
140
|
+
* RawPathToUrl<'**'> // RootCatchAll
|
|
141
|
+
*/
|
|
142
|
+
type RawPathToUrl<S extends string> = S extends `${infer Start}:${infer Param}/${infer Rest}` ? `${Start}${ResolveParam<Param>}/${RawPathToUrl<Rest>}` : S extends `${infer Start}:${infer Param}` ? `${Start}${ResolveParam<Param>}` : S extends `${infer Start}**/${infer Rest}` ? `${Start}${string}/${RawPathToUrl<Rest>}` : S extends `${infer Start}**` ? Start extends '' ? RootCatchAll : `${Start}${string}` : S;
|
|
143
|
+
|
|
144
|
+
/** Collapses a non-literal `string` input to `string` to prevent recursion artifacts. */
|
|
145
|
+
type Normalize<S extends string> = string extends S ? string : S;
|
|
146
|
+
/** Resolves one segment: `:param` → ResolveParam<Param>, literal → itself. */
|
|
147
|
+
type ResolveSegment<S extends string> = S extends `:${infer Name}` ? ResolveParam<Name> : Normalize<S>;
|
|
148
|
+
/**
|
|
149
|
+
* Splits a raw path on `/` and resolves each `:paramName` segment directly.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* RawPathToCommands<'user/:id'> // ['user', UserId]
|
|
153
|
+
* RawPathToCommands<'home'> // ['home']
|
|
154
|
+
* RawPathToCommands<''> // []
|
|
155
|
+
*/
|
|
156
|
+
type RawPathToCommands<P extends string> = P extends string ? P extends `${infer Head}/${infer Rest}` ? ResolveSegment<Head> extends infer H ? H extends string | number ? [H, ...RawPathToCommands<Rest>] : never : never : P extends '' ? [] : ResolveSegment<P> extends infer H ? H extends string | number ? [H] : never : never : never;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Strips a trailing `/` from a string, with one exception: a bare `'/'` (the
|
|
160
|
+
* root URL) is preserved.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* RemoveTrailingSlash<'/home/'> // '/home'
|
|
164
|
+
* RemoveTrailingSlash<'/'> // '/'
|
|
165
|
+
* RemoveTrailingSlash<'/home'> // '/home'
|
|
166
|
+
*/
|
|
167
|
+
type RemoveTrailingSlash<S extends string> = S extends '/' ? S : S extends `${infer T}/` ? RemoveTrailingSlash<T> : S;
|
|
168
|
+
|
|
169
|
+
/** Augmentation target. Consumers declare their routes here:
|
|
170
|
+
* `declare module 'angular-typed-router' { interface UserTypedRoutes { routes: typeof routes } }` */
|
|
171
|
+
interface UserTypedRoutes {
|
|
172
|
+
}
|
|
173
|
+
type ProvidedRoutes = UserTypedRoutes extends {
|
|
174
|
+
routes: readonly Route[];
|
|
175
|
+
} ? UserTypedRoutes['routes'] : [];
|
|
176
|
+
type RawPaths = ExtractRawPaths<ProvidedRoutes>;
|
|
177
|
+
type Path = RemoveTrailingSlash<`/${RawPathToUrl<RawPaths>}`>;
|
|
178
|
+
type Commands = readonly ['/', ...RawPathToCommands<RawPaths>];
|
|
179
|
+
|
|
180
|
+
declare class TypedRouter extends Router {
|
|
181
|
+
navigate(commands: Commands, extras?: NavigationExtras): Promise<boolean>;
|
|
182
|
+
navigateByUrl(url: Path, extras?: NavigationBehaviorOptions): Promise<boolean>;
|
|
183
|
+
createUrlTree(commands: Commands, navigationExtras?: UrlCreationOptions): UrlTree;
|
|
184
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouter, never>;
|
|
185
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<TypedRouter>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare class TypedRouterLink extends RouterLink {
|
|
189
|
+
set routerLink(commandsOrUrlTree: Commands | Path | UrlTree | null | undefined);
|
|
190
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouterLink, never>;
|
|
191
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<TypedRouterLink, "[routerLink]", never, { "routerLink": { "alias": "routerLink"; "required": false; }; }, {}, never, never, true, never>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { TypedRouter, TypedRouterLink };
|
|
195
|
+
export type { Commands, Path, RouteParamTypes, UserTypedRoutes };
|
package/index.d.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Route, Router, NavigationExtras, NavigationBehaviorOptions, UrlCreationOptions, UrlTree, RouterLink } from '@angular/router';
|
|
2
|
-
import * as i0 from '@angular/core';
|
|
3
|
-
|
|
4
|
-
type _Normalize<S extends string> = string extends S ? string : S;
|
|
5
|
-
type PathToTuple<P extends string> = P extends `${infer Head}/${infer Rest}` ? [_Normalize<Head>, ...PathToTuple<Rest>] : P extends '' ? [] : [_Normalize<P>];
|
|
6
|
-
|
|
7
|
-
type IsNavigable<R extends Route> = R extends {
|
|
8
|
-
component: any;
|
|
9
|
-
} ? true : R extends {
|
|
10
|
-
loadComponent: any;
|
|
11
|
-
} ? true : R extends {
|
|
12
|
-
redirectTo: any;
|
|
13
|
-
} ? true : false;
|
|
14
|
-
|
|
15
|
-
declare const __rootCatchAll: unique symbol;
|
|
16
|
-
type RootCatchAll = string & {
|
|
17
|
-
readonly [__rootCatchAll]: true;
|
|
18
|
-
};
|
|
19
|
-
interface RouteParamTypes {
|
|
20
|
-
}
|
|
21
|
-
type ParamValueType<Name extends string> = Name extends keyof RouteParamTypes ? RouteParamTypes[Name] : never;
|
|
22
|
-
type _ReplaceParams<S extends string> = S extends `${infer Start}:${infer Param}/${infer Rest}` ? `${Start}${ParamValueType<Param>}/${_ReplaceParams<Rest>}` : S extends `${infer Start}:${infer Param}` ? `${Start}${ParamValueType<Param>}` : S extends `${infer Start}**/${infer Rest}` ? `${Start}${string}/${_ReplaceParams<Rest>}` : S extends `${infer Start}**` ? Start extends '' ? RootCatchAll : `${Start}${string}` : S;
|
|
23
|
-
type ReplaceParams<S extends string> = _ReplaceParams<S>;
|
|
24
|
-
|
|
25
|
-
type PathOrEmptyString<R extends Route> = R['path'] extends string ? R['path'] : '';
|
|
26
|
-
|
|
27
|
-
type ExtractLazyChildRoutes<R extends Route> = R['loadChildren'] extends (...args: any) => Promise<infer M> ? M extends readonly Route[] ? M : M extends {
|
|
28
|
-
routes: readonly Route[];
|
|
29
|
-
} ? M['routes'] : [] : [];
|
|
30
|
-
|
|
31
|
-
type JoinPathSegments<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
|
|
32
|
-
|
|
33
|
-
type ExtractChildren<Routes extends readonly Route[], Prefix extends string> = Routes[number] extends infer R ? R extends Route ? ExtractPathsFromRoute<R, Prefix> : never : never;
|
|
34
|
-
type ExtractPathsFromRoute<R extends Route, Prefix extends string = ''> = (IsNavigable<R> extends true ? ReplaceParams<JoinPathSegments<Prefix, PathOrEmptyString<R>>> : never) | (R['children'] extends readonly Route[] ? ExtractChildren<R['children'], ReplaceParams<JoinPathSegments<Prefix, PathOrEmptyString<R>>>> : never) | (R['loadChildren'] extends () => Promise<any> ? ExtractChildren<ExtractLazyChildRoutes<R>, ReplaceParams<JoinPathSegments<Prefix, PathOrEmptyString<R>>>> : never);
|
|
35
|
-
|
|
36
|
-
type ExtractPathsFromRoutes<Routes extends readonly Route[], Prefix extends string = ''> = Routes[number] extends infer R ? R extends Route ? ExtractPathsFromRoute<R, Prefix> : never : never;
|
|
37
|
-
|
|
38
|
-
type RemoveTrailingSlash<S extends string> = S extends '/' ? S : S extends `${infer T}/` ? RemoveTrailingSlash<T> : S;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Merge target. Consumer augments this interface:
|
|
42
|
-
* declare module 'angular-typed-router' { interface UserTypedRoutes { routes: typeof routes; } }`
|
|
43
|
-
*/
|
|
44
|
-
interface UserTypedRoutes {
|
|
45
|
-
}
|
|
46
|
-
type ProvidedRoutes = UserTypedRoutes extends {
|
|
47
|
-
routes: readonly Route[];
|
|
48
|
-
} ? UserTypedRoutes['routes'] : [];
|
|
49
|
-
type Path = RemoveTrailingSlash<`/${ExtractPathsFromRoutes<ProvidedRoutes>}`>;
|
|
50
|
-
type CommandPath = ExtractPathsFromRoutes<ProvidedRoutes>;
|
|
51
|
-
type Commands = ['/', ...PathToTuple<CommandPath>];
|
|
52
|
-
|
|
53
|
-
declare class TypedRouter extends Router {
|
|
54
|
-
navigate(commands: Commands, extras?: NavigationExtras): Promise<boolean>;
|
|
55
|
-
navigateByUrl(url: Path, extras?: NavigationBehaviorOptions): Promise<boolean>;
|
|
56
|
-
createUrlTree(commands: Commands, navigationExtras?: UrlCreationOptions): UrlTree;
|
|
57
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouter, never>;
|
|
58
|
-
static ɵprov: i0.ɵɵInjectableDeclaration<TypedRouter>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
declare class TypedRouterLink extends RouterLink {
|
|
62
|
-
set routerLink(commandsOrUrlTree: Commands | Path | UrlTree | null | undefined);
|
|
63
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouterLink, never>;
|
|
64
|
-
static ɵdir: i0.ɵɵDirectiveDeclaration<TypedRouterLink, "[routerLink]", never, { "routerLink": { "alias": "routerLink"; "required": false; }; }, {}, never, never, true, never>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export { TypedRouter, TypedRouterLink };
|
|
68
|
-
export type { Commands, ExtractPathsFromRoutes, Path, RouteParamTypes, UserTypedRoutes };
|