angular-typed-router 0.1.1-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 ADDED
@@ -0,0 +1,208 @@
1
+ # angular-typed-router
2
+
3
+ ## Attention: Still in experimental phase
4
+
5
+ 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
+
7
+ ## Why
8
+
9
+ Angular's router is powerful but untyped for URL literals – a misspelled path or an outdated segment only fails at runtime. This library lets your application declare routes once, then:
10
+ - Navigate with `TypedRouter.navigateByUrl(path)` where `path` is validated at compile time.
11
+ - Use `<a routerLink="...">` with type checking via an augmented `TypedRouterLink` directive.
12
+ - Build command tuples with correct literal segments (`Commands` type).
13
+
14
+ No decorators, no custom builders, no code generation – just TypeScript type inference and interface augmentation.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install angular-typed-router
20
+ # or
21
+ pnpm add angular-typed-router
22
+ # or
23
+ yarn add angular-typed-router
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ 1. Define your application routes:
29
+
30
+ ```ts
31
+ // app.routes.ts
32
+ import { Routes } from '@angular/router';
33
+ import { DashboardComponent } from './dashboard.component';
34
+
35
+ export const appRoutes = [
36
+ { path: 'dashboard', component: DashboardComponent },
37
+ { path: 'projects/:id', loadComponent: () => import('./project.component').then(m => m.ProjectComponent) },
38
+ { path: '**', redirectTo: 'dashboard' }
39
+ ] as const satisfies Routes;
40
+ ```
41
+
42
+ 2. Create the augmentation file so the library can “see” your routes:
43
+
44
+ ```ts
45
+ // angular-typed-router.d.ts (sibling to main.ts or inside src/ root)
46
+ import type { appRoutes } from './app/app.routes';
47
+
48
+ declare module 'angular-typed-router' {
49
+ interface UserTypedRoutes {
50
+ routes: typeof appRoutes;
51
+ }
52
+ }
53
+ ```
54
+
55
+ 3. Link the augmentation file in your tsconfig so the compiler includes it:
56
+
57
+ ```jsonc
58
+ // tsconfig.app.json
59
+ {
60
+ "extends": "./tsconfig.json",
61
+ "compilerOptions": { },
62
+ "include": [
63
+ "src/**/*.ts",
64
+ "angular-typed-router.d.ts" // <— add this line
65
+ ]
66
+ }
67
+ ```
68
+ If you have multiple tsconfigs, ensure the specific app tsconfig that drives the build/test includes the file.
69
+
70
+ 4. Use the typed router & link:
71
+
72
+ ```ts
73
+ import { Component, inject } from '@angular/core';
74
+ import { TypedRouter, Path } from 'angular-typed-router';
75
+
76
+ @Component({
77
+ selector: 'app-nav',
78
+ template: `
79
+ <a routerLink="dashboard">Dashboard</a>
80
+ <a routerLink="projects/123">Project 123</a>
81
+ `
82
+ })
83
+ export class NavComponent {
84
+ private readonly router = inject(TypedRouter);
85
+
86
+ go(p: Path) { // p must be one of the inferred paths
87
+ this.router.navigateByUrl(p);
88
+ }
89
+
90
+ openProject(id: string) {
91
+ // commands tuple – first literal segment + dynamic param value
92
+ this.router.navigate(['projects', id]);
93
+ }
94
+ }
95
+ ```
96
+
97
+ If you try `this.router.navigateByUrl('projcts/123')` (typo) or `<a routerLink="projcts/123">`, TypeScript errors.
98
+
99
+ ## Exports
100
+
101
+ ```ts
102
+ import { TypedRouter, TypedRouterLink, Path, Commands, UserTypedRoutes } from 'angular-typed-router';
103
+ ```
104
+
105
+ - `TypedRouter` – Extends Angular `Router`, overrides `navigateByUrl` & `navigate` signatures to accept `Path` / `Commands`.
106
+ - `TypedRouterLink` – Directive shadowing `[routerLink]` to type its input as `Commands | Path`.
107
+ - `Path` – Union of every reachable concrete URL path produced from your route tree (includes parameterized expansions with `string` in place of `:param` segments).
108
+ - `Commands` – Union of tuple command arrays representing valid `Router.navigate()` inputs (each static segment as a literal, each parameter position as `string`).
109
+ - `UserTypedRoutes` – Empty interface you augment with your `routes` reference.
110
+ - `ExtractPathsFromRoutes<Routes>` – Utility type if you need to compute from an arbitrary `Routes` array manually.
111
+
112
+ ## How It Works
113
+
114
+ 1. You augment `UserTypedRoutes` with the literal `const` route array.
115
+ 2. Type utilities recursively walk the route tree (including lazily loaded routes via `loadChildren` returning `Route[]` or `{ routes }`).
116
+ 3. Each navigable route (component / loadComponent / redirectTo) contributes a path string. Param segments (`:id`) are replaced by `string` (currently unrestricted – see Limitations).
117
+ 4. Child paths are joined with parents to form final concrete path unions.
118
+ 5. A tuple transformation creates the `Commands` variants.
119
+
120
+ All compile-time only; nothing ships to runtime.
121
+
122
+ ## Lazy Routes
123
+
124
+ Works with any lazy route whose `loadChildren` resolves to:
125
+ - `Promise<Route[]>`
126
+ - `Promise<{ routes: Route[] }>` (Angular v17+ pattern)
127
+
128
+ Example:
129
+
130
+ ```ts
131
+ { path: 'admin', loadChildren: () => import('./admin.routes').then(m => m.adminRoutes) }
132
+ ```
133
+
134
+ Those child paths get prefixed (`admin/...`) in `Path` & `Commands`.
135
+
136
+ ## Parameter Segments
137
+
138
+ A pattern `projects/:id/details/:section` produces a `Path` variant like:
139
+ ```
140
+ 'projects/' + string + '/details/' + string
141
+ ```
142
+ and a `Commands` tuple like:
143
+ ```
144
+ ['projects', string, 'details', string]
145
+ ```
146
+ You pass real runtime values for the `string` positions. Empty string values and values like 'param/still-param' cannot currently be prevented at the type level without hurting DX (see Limitations).
147
+
148
+ ## Usage Patterns
149
+
150
+ Navigate by full path (typed):
151
+ ```ts
152
+ router.navigateByUrl('/dashboard');
153
+ ```
154
+ Navigate with commands array:
155
+ ```ts
156
+ router.navigate(['/', 'projects', someId]);
157
+ ```
158
+ Generate a `UrlTree`:
159
+ ```ts
160
+ router.createUrlTree(['/', 'projects', id]);
161
+ ```
162
+ Template links:
163
+ ```html
164
+ <a routerLink="/projects/42">Project 42</a>
165
+ <a [routerLink]="['/', 'projects', projectId]"></a>
166
+ ```
167
+
168
+
169
+ ## Augmentation Placement
170
+
171
+ Keep the augmentation in a `.d.ts` that is included by `tsconfig.app.json` (`include` array). If you see `Path` still as `never`, ensure:
172
+ - The augmentation file is included.
173
+ - The `routes` constant is `as const satisfies Routes`.
174
+ - No circular import (augmentation file should only import the routes, nothing else runtime-heavy).
175
+
176
+ ## Limitations & Tradeoffs
177
+
178
+ | Concern | Status / Rationale |
179
+ |---------|---------------------------------------------------------------------------------------|
180
+ | Restrict param values (non-empty) | Not enforced; doing so significantly worsens DX (would reject plain `string` vars). |
181
+ | Trailing slashes | Not generated unless authored; currently no auto-normalization. |
182
+ | `relativeTo` (relative navigation) | Not supported – all inferred `Path` / `Commands` are absolute. Use absolute commands. |
183
+
184
+
185
+ ## ESLint Recommendation (Optional)
186
+
187
+ You can use `angular-typed-router-eslint` plugin to forbid untyped navigation calls.
188
+
189
+ ```bash
190
+
191
+ ## Troubleshooting
192
+
193
+ | Symptom | Fix |
194
+ |---------|-----|
195
+ | `Path` is `never` | Check augmentation file is included in tsconfig. |
196
+ | Lazy children missing | Ensure promise resolves to `Route[]` or `{ routes: Route[] }`. |
197
+ | Template error: unknown routerLink type | Ensure `TypedRouterLink` directive is imported (standalone). |
198
+
199
+ ## Contributing
200
+
201
+ PRs welcome.
202
+
203
+ ## License
204
+
205
+ MIT
206
+
207
+ ---
208
+ Happy routing.
@@ -0,0 +1,46 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, Input, Directive } from '@angular/core';
3
+ import { Router, RouterLink } from '@angular/router';
4
+
5
+ class TypedRouter extends Router {
6
+ navigate(commands, extras) {
7
+ return super.navigate(commands, extras);
8
+ }
9
+ navigateByUrl(url, extras) {
10
+ return super.navigateByUrl(url, extras);
11
+ }
12
+ createUrlTree(commands, navigationExtras) {
13
+ return super.createUrlTree(commands, navigationExtras);
14
+ }
15
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
16
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouter, providedIn: 'root' });
17
+ }
18
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouter, decorators: [{
19
+ type: Injectable,
20
+ args: [{
21
+ providedIn: 'root',
22
+ }]
23
+ }] });
24
+
25
+ class TypedRouterLink extends RouterLink {
26
+ set routerLink(commandsOrUrlTree) {
27
+ super.routerLink = commandsOrUrlTree;
28
+ }
29
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouterLink, deps: null, target: i0.ɵɵFactoryTarget.Directive });
30
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.2.4", type: TypedRouterLink, isStandalone: true, selector: "[routerLink]", inputs: { routerLink: "routerLink" }, usesInheritance: true, ngImport: i0 });
31
+ }
32
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: TypedRouterLink, decorators: [{
33
+ type: Directive,
34
+ args: [{
35
+ selector: '[routerLink]'
36
+ }]
37
+ }], propDecorators: { routerLink: [{
38
+ type: Input
39
+ }] } });
40
+
41
+ /**
42
+ * Generated bundle index. Do not edit.
43
+ */
44
+
45
+ export { TypedRouter, TypedRouterLink };
46
+ //# sourceMappingURL=angular-typed-router.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular-typed-router.mjs","sources":["../tmp-esm2022/lib/router.js","../tmp-esm2022/lib/router-link.js","../tmp-esm2022/angular-typed-router.js"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { Router, } from '@angular/router';\nimport * as i0 from \"@angular/core\";\nexport class TypedRouter extends Router {\n navigate(commands, extras) {\n return super.navigate(commands, extras);\n }\n navigateByUrl(url, extras) {\n return super.navigateByUrl(url, extras);\n }\n createUrlTree(commands, navigationExtras) {\n return super.createUrlTree(commands, navigationExtras);\n }\n static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouter, deps: null, target: i0.ɵɵFactoryTarget.Injectable });\n static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouter, providedIn: 'root' });\n}\ni0.ɵɵngDeclareClassMetadata({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouter, decorators: [{\n type: Injectable,\n args: [{\n providedIn: 'root',\n }]\n }] });\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGlicy90eXBlZC1yb3V0ZXIvc3JjL2xpYi9yb3V0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBR0wsTUFBTSxHQUdQLE1BQU0saUJBQWlCLENBQUM7O0FBTXpCLE1BQU0sT0FBTyxXQUFZLFNBQVEsTUFBTTtJQUM1QixRQUFRLENBQ2YsUUFBa0IsRUFDbEIsTUFBeUI7UUFFekIsT0FBTyxLQUFLLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRVEsYUFBYSxDQUNwQixHQUFTLEVBQ1QsTUFBa0M7UUFFbEMsT0FBTyxLQUFLLENBQUMsYUFBYSxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRVEsYUFBYSxDQUNwQixRQUFrQixFQUNsQixnQkFBcUM7UUFFckMsT0FBTyxLQUFLLENBQUMsYUFBYSxDQUFDLFFBQVEsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3pELENBQUM7dUdBcEJVLFdBQVc7MkdBQVgsV0FBVyxjQUZWLE1BQU07OzJGQUVQLFdBQVc7a0JBSHZCLFVBQVU7bUJBQUM7b0JBQ1YsVUFBVSxFQUFFLE1BQU07aUJBQ25CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtcbiAgTmF2aWdhdGlvbkJlaGF2aW9yT3B0aW9ucyxcbiAgTmF2aWdhdGlvbkV4dHJhcyxcbiAgUm91dGVyLFxuICBVcmxDcmVhdGlvbk9wdGlvbnMsXG4gIFVybFRyZWUsXG59IGZyb20gJ0Bhbmd1bGFyL3JvdXRlcic7XG5pbXBvcnQgeyBDb21tYW5kcywgUGF0aCB9IGZyb20gJy4vdHlwZWQtcm91dGVzJztcblxuQEluamVjdGFibGUoe1xuICBwcm92aWRlZEluOiAncm9vdCcsXG59KVxuZXhwb3J0IGNsYXNzIFR5cGVkUm91dGVyIGV4dGVuZHMgUm91dGVyIHtcbiAgb3ZlcnJpZGUgbmF2aWdhdGUoXG4gICAgY29tbWFuZHM6IENvbW1hbmRzLFxuICAgIGV4dHJhcz86IE5hdmlnYXRpb25FeHRyYXNcbiAgKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgcmV0dXJuIHN1cGVyLm5hdmlnYXRlKGNvbW1hbmRzLCBleHRyYXMpO1xuICB9XG5cbiAgb3ZlcnJpZGUgbmF2aWdhdGVCeVVybChcbiAgICB1cmw6IFBhdGgsXG4gICAgZXh0cmFzPzogTmF2aWdhdGlvbkJlaGF2aW9yT3B0aW9uc1xuICApOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICByZXR1cm4gc3VwZXIubmF2aWdhdGVCeVVybCh1cmwsIGV4dHJhcyk7XG4gIH1cblxuICBvdmVycmlkZSBjcmVhdGVVcmxUcmVlKFxuICAgIGNvbW1hbmRzOiBDb21tYW5kcyxcbiAgICBuYXZpZ2F0aW9uRXh0cmFzPzogVXJsQ3JlYXRpb25PcHRpb25zXG4gICk6IFVybFRyZWUge1xuICAgIHJldHVybiBzdXBlci5jcmVhdGVVcmxUcmVlKGNvbW1hbmRzLCBuYXZpZ2F0aW9uRXh0cmFzKTtcbiAgfVxufVxuIl19","import { Directive, Input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport * as i0 from \"@angular/core\";\nexport class TypedRouterLink extends RouterLink {\n set routerLink(commandsOrUrlTree) {\n super.routerLink = commandsOrUrlTree;\n }\n static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouterLink, deps: null, target: i0.ɵɵFactoryTarget.Directive });\n static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: \"14.0.0\", version: \"20.2.4\", type: TypedRouterLink, isStandalone: true, selector: \"[routerLink]\", inputs: { routerLink: \"routerLink\" }, usesInheritance: true, ngImport: i0 });\n}\ni0.ɵɵngDeclareClassMetadata({ minVersion: \"12.0.0\", version: \"20.2.4\", ngImport: i0, type: TypedRouterLink, decorators: [{\n type: Directive,\n args: [{\n selector: '[routerLink]'\n }]\n }], propDecorators: { routerLink: [{\n type: Input\n }] } });\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGVyLWxpbmsuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9saWJzL3R5cGVkLXJvdXRlci9zcmMvbGliL3JvdXRlci1saW5rLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pELE9BQU8sRUFBRSxVQUFVLEVBQVcsTUFBTSxpQkFBaUIsQ0FBQzs7QUFNdEQsTUFBTSxPQUFPLGVBQWdCLFNBQVEsVUFBVTtJQUM3QyxJQUNhLFVBQVUsQ0FBQyxpQkFBK0Q7UUFDckYsS0FBSyxDQUFDLFVBQVUsR0FBRyxpQkFBaUIsQ0FBQztJQUN2QyxDQUFDO3VHQUpVLGVBQWU7MkZBQWYsZUFBZTs7MkZBQWYsZUFBZTtrQkFIM0IsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsY0FBYztpQkFDekI7OEJBR2MsVUFBVTtzQkFEdEIsS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IERpcmVjdGl2ZSwgSW5wdXQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFJvdXRlckxpbmssIFVybFRyZWUgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgQ29tbWFuZHMsIFBhdGggfSBmcm9tICcuL3R5cGVkLXJvdXRlcyc7XG5cbkBEaXJlY3RpdmUoe1xuICBzZWxlY3RvcjogJ1tyb3V0ZXJMaW5rXSdcbn0pXG5leHBvcnQgY2xhc3MgVHlwZWRSb3V0ZXJMaW5rIGV4dGVuZHMgUm91dGVyTGluayB7XG4gIEBJbnB1dCgpXG4gIG92ZXJyaWRlIHNldCByb3V0ZXJMaW5rKGNvbW1hbmRzT3JVcmxUcmVlOiBDb21tYW5kcyB8IFBhdGggfCBVcmxUcmVlIHwgbnVsbCB8IHVuZGVmaW5lZCkge1xuICAgIHN1cGVyLnJvdXRlckxpbmsgPSBjb21tYW5kc09yVXJsVHJlZTtcbiAgfVxufVxuIl19","/**\n * Generated bundle index. Do not edit.\n */\nexport * from './index';\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW5ndWxhci10eXBlZC1yb3V0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9saWJzL3R5cGVkLXJvdXRlci9zcmMvYW5ndWxhci10eXBlZC1yb3V0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0="],"names":[],"mappings":";;;;AAGO,MAAM,WAAW,SAAS,MAAM,CAAC;AACxC,IAAI,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE;AAC/B,QAAQ,OAAO,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;AAC/C,IAAI;AACJ,IAAI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE;AAC/B,QAAQ,OAAO,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC;AAC/C,IAAI;AACJ,IAAI,aAAa,CAAC,QAAQ,EAAE,gBAAgB,EAAE;AAC9C,QAAQ,OAAO,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,gBAAgB,CAAC;AAC9D,IAAI;AACJ,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;AACxK,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC,qBAAqB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAC7I;AACA,EAAE,CAAC,wBAAwB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACrH,YAAY,IAAI,EAAE,UAAU;AAC5B,YAAY,IAAI,EAAE,CAAC;AACnB,oBAAoB,UAAU,EAAE,MAAM;AACtC,iBAAiB;AACjB,SAAS,CAAC,EAAE,CAAC;;AClBN,MAAM,eAAe,SAAS,UAAU,CAAC;AAChD,IAAI,IAAI,UAAU,CAAC,iBAAiB,EAAE;AACtC,QAAQ,KAAK,CAAC,UAAU,GAAG,iBAAiB;AAC5C,IAAI;AACJ,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;AAC3K,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AACtO;AACA,EAAE,CAAC,wBAAwB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;AACzH,YAAY,IAAI,EAAE,SAAS;AAC3B,YAAY,IAAI,EAAE,CAAC;AACnB,oBAAoB,QAAQ,EAAE;AAC9B,iBAAiB;AACjB,SAAS,CAAC,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,CAAC;AAC3C,gBAAgB,IAAI,EAAE;AACtB,aAAa,CAAC,EAAE,EAAE,CAAC;;ACjBnB;AACA;AACA;;;;"}
package/index.d.ts ADDED
@@ -0,0 +1,66 @@
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
+ type AllowedRouteParamValue<S extends string> = Exclude<S, '' | `${string}/${string}`>;
20
+ type _ReplaceParams<S extends string> = S extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${AllowedRouteParamValue<string>}/${_ReplaceParams<Rest>}` : S extends `${infer Start}:${string}` ? `${Start}${AllowedRouteParamValue<string>}` : S extends `${infer Start}**/${infer Rest}` ? `${Start}${string}/${_ReplaceParams<Rest>}` : S extends `${infer Start}**` ? Start extends '' ? RootCatchAll : `${Start}${string}` : S;
21
+ type ReplaceParams<S extends string> = _ReplaceParams<S>;
22
+
23
+ type PathOrEmptyString<R extends Route> = R['path'] extends string ? R['path'] : '';
24
+
25
+ type ExtractLazyChildRoutes<R extends Route> = R['loadChildren'] extends (...args: any) => Promise<infer M> ? M extends readonly Route[] ? M : M extends {
26
+ routes: readonly Route[];
27
+ } ? M['routes'] : [] : [];
28
+
29
+ type JoinPathSegments<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
30
+
31
+ type ExtractChildren<Routes extends readonly Route[], Prefix extends string> = Routes[number] extends infer R ? R extends Route ? ExtractPathsFromRoute<R, Prefix> : never : never;
32
+ 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);
33
+
34
+ type ExtractPathsFromRoutes<Routes extends readonly Route[], Prefix extends string = ''> = Routes[number] extends infer R ? R extends Route ? ExtractPathsFromRoute<R, Prefix> : never : never;
35
+
36
+ type RemoveTrailingSlash<S extends string> = S extends '/' ? S : S extends `${infer T}/` ? RemoveTrailingSlash<T> : S;
37
+
38
+ /**
39
+ * Merge target. Consumer augments this interface:
40
+ * declare module 'angular-typed-router' { interface UserTypedRoutes { routes: typeof routes; } }`
41
+ */
42
+ interface UserTypedRoutes {
43
+ }
44
+ type ProvidedRoutes = UserTypedRoutes extends {
45
+ routes: readonly Route[];
46
+ } ? UserTypedRoutes['routes'] : [];
47
+ type Path = RemoveTrailingSlash<`/${ExtractPathsFromRoutes<ProvidedRoutes>}`>;
48
+ type CommandPath = ExtractPathsFromRoutes<ProvidedRoutes>;
49
+ type Commands = ['/', ...PathToTuple<CommandPath>];
50
+
51
+ declare class TypedRouter extends Router {
52
+ navigate(commands: Commands, extras?: NavigationExtras): Promise<boolean>;
53
+ navigateByUrl(url: Path, extras?: NavigationBehaviorOptions): Promise<boolean>;
54
+ createUrlTree(commands: Commands, navigationExtras?: UrlCreationOptions): UrlTree;
55
+ static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouter, never>;
56
+ static ɵprov: i0.ɵɵInjectableDeclaration<TypedRouter>;
57
+ }
58
+
59
+ declare class TypedRouterLink extends RouterLink {
60
+ set routerLink(commandsOrUrlTree: Commands | Path | UrlTree | null | undefined);
61
+ static ɵfac: i0.ɵɵFactoryDeclaration<TypedRouterLink, never>;
62
+ static ɵdir: i0.ɵɵDirectiveDeclaration<TypedRouterLink, "[routerLink]", never, { "routerLink": { "alias": "routerLink"; "required": false; }; }, {}, never, never, true, never>;
63
+ }
64
+
65
+ export { TypedRouter, TypedRouterLink };
66
+ export type { Commands, ExtractPathsFromRoutes, Path, UserTypedRoutes };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "angular-typed-router",
3
+ "version": "0.1.1-0",
4
+ "peerDependencies": {
5
+ "@angular/common": "^20.2.0",
6
+ "@angular/core": "^20.2.0"
7
+ },
8
+ "sideEffects": false,
9
+ "module": "fesm2022/angular-typed-router.mjs",
10
+ "typings": "index.d.ts",
11
+ "exports": {
12
+ "./package.json": {
13
+ "default": "./package.json"
14
+ },
15
+ ".": {
16
+ "types": "./index.d.ts",
17
+ "default": "./fesm2022/angular-typed-router.mjs"
18
+ }
19
+ },
20
+ "dependencies": {
21
+ "tslib": "^2.3.0"
22
+ }
23
+ }