@loozo/angular-router 0.0.2 → 0.0.4

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.
Files changed (68) hide show
  1. package/README.md +565 -80
  2. package/builders.json +2 -2
  3. package/fesm2022/loozo-angular-router-_core.mjs +332 -595
  4. package/fesm2022/loozo-angular-router-_core.mjs.map +1 -1
  5. package/fesm2022/loozo-angular-router-_generated.mjs +335 -627
  6. package/fesm2022/loozo-angular-router-_generated.mjs.map +1 -1
  7. package/fesm2022/loozo-angular-router.mjs +2 -19
  8. package/node/_core/src/path.d.ts +17 -0
  9. package/node/src/builders/application.js +19 -7
  10. package/node/src/builders/options.js +47 -61
  11. package/node/src/builders/serve.js +66 -16
  12. package/node/src/generation/anchor.d.ts +13 -0
  13. package/node/src/generation/anchor.js +92 -0
  14. package/node/src/generation/compiler.d.ts +6 -0
  15. package/node/src/generation/compiler.js +68 -0
  16. package/node/src/generation/emit.d.ts +3 -0
  17. package/node/src/generation/emit.js +26 -8
  18. package/node/src/generation/files.d.ts +26 -0
  19. package/node/src/generation/files.js +14 -3
  20. package/node/src/generation/filesystem.d.ts +7 -0
  21. package/node/src/generation/filesystem.js +261 -69
  22. package/node/src/generation/generate.d.ts +16 -0
  23. package/node/src/generation/generate.js +10 -4
  24. package/node/src/generation/index.d.ts +16 -0
  25. package/node/src/generation/index.js +15 -0
  26. package/node/src/generation/overlay.d.ts +3 -0
  27. package/node/src/generation/selectors.d.ts +2 -0
  28. package/node/src/generation/sources.d.ts +50 -0
  29. package/node/src/generation/sources.js +57 -12
  30. package/node/src/generation/validate.d.ts +12 -0
  31. package/node/src/generation/validate.js +53 -57
  32. package/package.json +7 -2
  33. package/schemas/application.json +764 -20
  34. package/schemas/serve.json +144 -30
  35. package/snippets/angular.json +29 -0
  36. package/snippets/generated/destinations.generated.ts +126 -0
  37. package/snippets/generated/routes.generated.ts +35 -0
  38. package/snippets/src/catalog.ts +20 -0
  39. package/snippets/src/docs-url.ts +8 -0
  40. package/snippets/src/index.html +11 -0
  41. package/snippets/src/main.ts +6 -0
  42. package/snippets/src/product-url.ts +8 -0
  43. package/snippets/src/routes/products/$productId/page.ts +20 -0
  44. package/snippets/src/routes/products/$productId/route.generated.ts +24 -0
  45. package/snippets/src/routes/products/$productId/route.ts +7 -0
  46. package/snippets/src/routes/reports/$reportId/page.ts +9 -0
  47. package/snippets/src/routes/reports/$reportId/route.generated.ts +24 -0
  48. package/snippets/src/routes/reports/$reportId/route.ts +6 -0
  49. package/snippets/src/routes/teams/$teamId/items/$id/inputs.ts +15 -0
  50. package/snippets/src/routes/teams/$teamId/items/$id/links.ts +13 -0
  51. package/snippets/src/routes/teams/$teamId/items/$id/page.ts +9 -0
  52. package/snippets/src/routes/teams/$teamId/items/$id/route.generated.ts +24 -0
  53. package/snippets/src/routes/teams/$teamId/items/$id/route.ts +18 -0
  54. package/snippets/src/routes/teams/$teamId/members/$memberId/page.ts +9 -0
  55. package/snippets/src/routes/teams/$teamId/members/$memberId/route.generated.ts +24 -0
  56. package/snippets/src/routes/teams/$teamId/members/$memberId/route.ts +7 -0
  57. package/snippets/src/routes/teams/$teamId/old-items/$id/route.ts +13 -0
  58. package/snippets/src/routes/teams/$teamId/route.generated.ts +24 -0
  59. package/snippets/src/routes/teams/$teamId/route.ts +6 -0
  60. package/snippets/src/shell.ts +9 -0
  61. package/snippets/src/slug.ts +14 -0
  62. package/snippets/tsconfig.app.json +14 -0
  63. package/snippets/tsconfig.routes.json +5 -0
  64. package/types/loozo-angular-router-_core.d.ts +262 -242
  65. package/types/loozo-angular-router-_generated.d.ts +155 -116
  66. package/types/loozo-angular-router.d.ts +2 -1
  67. package/.npmignore +0 -3
  68. package/fesm2022/loozo-angular-router.mjs.map +0 -1
@@ -1,16 +1,16 @@
1
- import { RedirectCommand, RunGuardsAndResolvers, ActivatedRouteSnapshot, RouterStateSnapshot, MaybeAsync, GuardResult, Route as Route$1, UrlSegment, PartialMatchRouteSnapshot } from '@angular/router';
2
- import { Observable } from 'rxjs';
3
-
1
+ import { ActivatedRouteSnapshot, GuardResult, MaybeAsync, PartialMatchRouteSnapshot, RedirectCommand, Route as Route$1, RouterStateSnapshot, RunGuardsAndResolvers, UrlSegment, UrlTree } from "@angular/router";
2
+ import { Observable } from "rxjs";
4
3
  /**
5
4
  * Converts one decoded path or search value to and from an application value.
6
5
  *
7
6
  * @remarks
8
- * Parsing may be asynchronous; stringification must be synchronous. Neither
9
- * method performs URL encoding. Codecs have no Angular injection-context
10
- * guarantee, and their returned objects are not recursively frozen.
7
+ * Both methods are synchronous, and parsing should be pure and deterministic:
8
+ * like Angular's matching callbacks, it runs once while matching and again for
9
+ * the activated route. Neither method performs URL encoding. Codecs have no
10
+ * Angular injection context, and their returned objects are not recursively frozen.
11
11
  * For a terminal `$name[]` path, this codec applies to each element separately.
12
12
  *
13
- * @typeParam Value - The logical value, not a promise of that value.
13
+ * @typeParam Value - The logical value, never a promise of that value.
14
14
  *
15
15
  * @example
16
16
  * ```ts
@@ -21,41 +21,54 @@ import { Observable } from 'rxjs';
21
21
  * ```
22
22
  */
23
23
  interface ParamCodec<Value> {
24
- /**
25
- * Parses one already percent-decoded value; do not decode it again.
26
- *
27
- * @param raw - A single logical string from Angular's URL inputs.
28
- * @returns The logical value or a promise for it.
29
- * @throws An application error for invalid input; generated input parsing
30
- * reports throws and rejections as invalid path/search values with a cause.
31
- */
32
- parse(raw: string): Value | Promise<Value>;
33
- /**
34
- * Serializes one logical value without percent-encoding it.
35
- *
36
- * @param value - The application value to serialize.
37
- * @returns An unencoded string. Path output must not be empty, `.` or `..`.
38
- * @throws An application error when the value cannot be serialized.
39
- */
40
- stringify(value: Value): string;
24
+ /**
25
+ * Parses one already percent-decoded value; do not decode it again.
26
+ *
27
+ * @param raw - A single logical string from Angular's URL inputs.
28
+ * @returns The logical value, synchronously. Returning a promise or other
29
+ * thenable is a programming error that fails the navigation with a TypeError.
30
+ * @throws An application error for invalid input: an invalid path value makes
31
+ * the route not match, and an invalid search value reads as absent or as its fallback.
32
+ */
33
+ parse(raw: string): Value;
34
+ /**
35
+ * Serializes one logical value without percent-encoding it.
36
+ *
37
+ * @param value - The application value to serialize.
38
+ * @returns An unencoded string. Path output must not be empty, `.` or `..`.
39
+ * @throws An application error when the value cannot be serialized.
40
+ */
41
+ stringify(value: Value): string;
41
42
  }
42
- declare const searchPresence: unique symbol;
43
- /** A search-only codec whose key may be omitted or supplied as `undefined`. */
44
- type OptionalCodec<Value> = ParamCodec<Value | undefined> & {
45
- readonly [searchPresence]: "optional";
46
- };
47
- /** A search-only codec that represents explicit `null` as the text `"null"`. */
48
- type NullableCodec<Value> = ParamCodec<Value | null> & {
49
- readonly [searchPresence]: "nullable";
43
+ declare const searchFallback: unique symbol;
44
+ /**
45
+ * A search-only codec whose key reads a fallback value when it is missing,
46
+ * repeated or invalid, while URL creation may still omit it.
47
+ */
48
+ type FallbackCodec<Value> = ParamCodec<Value> & {
49
+ readonly [searchFallback]: true;
50
50
  };
51
51
  type PathCodec = ParamCodec<any> & {
52
- readonly [searchPresence]?: never;
52
+ readonly [searchFallback]?: never;
53
53
  };
54
54
  type CodecValue<Codec> = Codec extends ParamCodec<infer Value> ? Value : never;
55
- type IsOptional<Codec> = Codec extends {
56
- readonly [searchPresence]: "optional";
55
+ type HasFallback<Codec> = Codec extends {
56
+ readonly [searchFallback]: true;
57
57
  } ? true : false;
58
- declare function codecPresence(codec: object): "optional" | "nullable" | undefined;
58
+ /** Type-only marker: a codec whose parse returns a promise fails where it is declared. */
59
+ interface SynchronousParse {
60
+ /** Never present: `parse` must return its value synchronously, not a Promise. */
61
+ readonly "ParamCodec.parse must return its value synchronously, not a Promise": never;
62
+ }
63
+ type ParseResult<Codec> = Codec extends {
64
+ parse(raw: string): infer Result;
65
+ } ? Result : never;
66
+ type SyncValue<Value> = Extract<Value, PromiseLike<unknown>> extends never ? unknown : SynchronousParse;
67
+ type SyncCodecs<Codecs> = { [Key in keyof Codecs]: SyncValue<ParseResult<Codecs[Key]>>; };
68
+ export declare function codecFallback(codec: object): {
69
+ readonly value: unknown;
70
+ } | undefined;
71
+ declare function validateCodec(codec: unknown): asserts codec is ParamCodec<any>;
59
72
  /**
60
73
  * Preserves strings verbatim, including empty strings and literal percent text.
61
74
  *
@@ -63,7 +76,7 @@ declare function codecPresence(codec: object): "optional" | "nullable" | undefin
63
76
  * Empty, dot and dotdot values are valid codec values but cannot be serialized
64
77
  * into path segments by URL creation. Search values may contain them.
65
78
  */
66
- declare const stringParam: ParamCodec<string>;
79
+ export declare const stringParam: ParamCodec<string>;
67
80
  /**
68
81
  * Parses nonblank text with JavaScript `Number` and requires a finite result.
69
82
  *
@@ -72,12 +85,12 @@ declare const stringParam: ParamCodec<string>;
72
85
  * Blank text, NaN and infinities throw. Stringification requires a finite number
73
86
  * and uses its JavaScript string representation (including `-0` becoming `"0"`).
74
87
  */
75
- declare const numberParam: ParamCodec<number>;
88
+ export declare const numberParam: ParamCodec<number>;
76
89
  /**
77
90
  * Parses only lowercase `"true"` and `"false"`, and emits those same strings.
78
91
  * Other representations and nonboolean values throw.
79
92
  */
80
- declare const booleanParam: ParamCodec<boolean>;
93
+ export declare const booleanParam: ParamCodec<boolean>;
81
94
  /**
82
95
  * Creates a case-sensitive string codec from a snapshotted list of members.
83
96
  *
@@ -92,47 +105,32 @@ declare const booleanParam: ParamCodec<boolean>;
92
105
  * tab.parse("reviews"); // "reviews"
93
106
  * ```
94
107
  */
95
- declare function enumParam<const Values extends readonly string[]>(values: Values): ParamCodec<Values[number]>;
96
- /**
97
- * Makes a search key optional without changing the handling of present values.
98
- *
99
- * @remarks
100
- * Generated parsing omits absent keys; URL creation omits absent or `undefined`
101
- * values. The wrapper's parser delegates present strings unchanged, preserving
102
- * promises and failures. Its stringifier cannot serialize `undefined` directly.
103
- * This wrapper is not a path codec. For optional nullable values, wrap
104
- * {@link nullableSearchParam} with this function, not the other way around.
105
- *
106
- * @typeParam Value - The wrapped codec's logical value.
107
- * @param codec - The codec used for present search values.
108
- * @returns A search-only codec allowing omission.
109
- * @throws TypeError if the codec lacks callable parse/stringify methods.
110
- */
111
- declare function optionalSearchParam<Value>(codec: ParamCodec<Value>): OptionalCodec<Value>;
108
+ export declare function enumParam<const Values extends readonly string[]>(values: Values): ParamCodec<Values[number]>;
112
109
  /**
113
- * Adds explicit null to a search codec, represented by the decoded text `"null"`.
110
+ * Gives a search key a value read in place of a missing, repeated or invalid one.
114
111
  *
115
112
  * @remarks
116
- * The key remains required unless wrapped with {@link optionalSearchParam}.
117
- * Non-null values delegate unchanged, including promise/failure identity. The
118
- * text `"null"` is reserved by this wrapper even if the inner codec accepts it.
119
- * This wrapper is not a path codec and cannot wrap an optional search codec.
120
- *
121
- * @typeParam Value - The wrapped codec's non-null logical value.
122
- * @param codec - The codec used for values other than null.
123
- * @returns A search-only codec accepting and serializing null.
124
- * @throws TypeError if the codec is malformed or is an optional search wrapper.
113
+ * Every search key is optional: a plain codec's key reads as absent when its
114
+ * value is missing, repeated or invalid. With a fallback, the key always reads a
115
+ * value of the codec's type, while URL creation, links, navigation and redirect
116
+ * replacements may still omit it. The fallback is returned as is: it is neither
117
+ * parsed nor copied. Present values delegate to the wrapped codec unchanged.
118
+ * Fallback codecs are search-only: a path param can't use one.
125
119
  *
126
120
  * @example
127
121
  * ```ts
128
- * const score = optionalSearchParam(nullableSearchParam(numberParam));
129
- * // Omission, null, and a present number remain distinct.
122
+ * search: { page: withFallback(numberParam, 1) }
123
+ * // ?page=abc, ?page=1&page=2 and no page all read as 1.
130
124
  * ```
125
+ *
126
+ * @typeParam Value - The wrapped codec's logical value.
127
+ * @param codec - The codec used for present search values.
128
+ * @param fallback - The value read when the key is missing, repeated or invalid.
129
+ * @returns A search-only codec that reads the fallback in place of an absent value.
130
+ * @throws TypeError if the codec lacks callable parse/stringify methods or already
131
+ * has a fallback.
131
132
  */
132
- declare function nullableSearchParam<Value>(codec: ParamCodec<Value> & {
133
- readonly [searchPresence]?: "nullable";
134
- }): NullableCodec<Value>;
135
-
133
+ export declare function withFallback<Value>(codec: ParamCodec<Value> & SyncValue<NoInfer<Value>>, fallback: NoInfer<Value>): FallbackCodec<Value>;
136
134
  type ValidName<Name extends string> = Name extends "" | "__proto__" | `...${string}` | `${string}?${string}` | `${string}*${string}` | `${string}[]${string}` ? false : true;
137
135
  type SegmentValid<Segment extends string> = Segment extends `$${infer Name}` ? ValidName<Name> : Segment extends "" | "." | ".." | "*" | "**" | `:${string}` ? false : true;
138
136
  type ValidateSegments<Path extends string, Scalars extends string = never> = Path extends `${infer Head}/${infer Tail}` ? Head extends `$${string}[]` ? false : SegmentValid<Head> extends true ? ValidateSegments<Tail, Scalars | ScalarName<Head>> : false : Path extends `$${infer Name}[]` ? ValidName<Name> extends true ? Name extends Scalars ? false : true : false : SegmentValid<Path>;
@@ -142,13 +140,13 @@ type SegmentName<Segment extends string> = Segment extends `$${infer Name}[]` ?
142
140
  type PathNames<Path extends string> = Path extends `${infer Head}/${infer Tail}` ? SegmentName<Head> | PathNames<Tail> : SegmentName<Path>;
143
141
  type CatchallName<Path extends string> = Path extends `${string}/${infer Tail}` ? CatchallName<Tail> : Path extends `$${infer Name}[]` ? Name : never;
144
142
  type Segment = {
145
- readonly kind: "static";
146
- readonly text: string;
143
+ readonly kind: "static";
144
+ readonly text: string;
147
145
  } | {
148
- readonly kind: "scalar" | "catchall";
149
- readonly name: string;
146
+ readonly kind: "scalar" | "catchall";
147
+ readonly name: string;
150
148
  };
151
-
149
+ declare function parsePath(path: unknown): readonly Segment[];
152
150
  declare const routeBrand: unique symbol;
153
151
  /**
154
152
  * A nominal route declaration created by `defineRoute`, not a native Angular Route.
@@ -161,50 +159,51 @@ declare const routeBrand: unique symbol;
161
159
  *
162
160
  * @typeParam Path - The local authored path literal, not the effective URL path.
163
161
  * @typeParam Params - Effective inherited and local logical path values.
164
- * @typeParam Search - Effective inherited and local logical search values.
162
+ * @typeParam Search - Effective inherited and local logical search values; keys
163
+ * without a fallback are optional. URL creation accepts every key as optional.
165
164
  * @typeParam Resolvers - Local resolver callbacks; data unwraps Promise/Observable
166
165
  * results and excludes Angular RedirectCommand values.
167
166
  * @typeParam Fragments - This declaration's own allowed fragment-name union.
168
167
  * @typeParam Catchall - Whether the declaration is a terminal catchall leaf.
169
168
  */
170
169
  interface Route<Path extends string, Params, Search, Resolvers = {}, Fragments extends string = string, Catchall extends boolean = CatchallName<Path> extends never ? false : true> {
171
- readonly [routeBrand]: {
172
- readonly fragments: Fragments;
173
- readonly catchall: Catchall;
174
- };
175
- /** The exact local authored path supplied to `defineRoute`. */
176
- readonly path: Path;
177
- /** Type-only witness for effective path values; not populated at runtime. */
178
- readonly __params?: Params;
179
- /** Type-only witness for effective search values; not populated at runtime. */
180
- readonly __search?: Search;
181
- /** Type-only witness for unwrapped local resolver data, excluding redirects. */
182
- readonly __data?: {
183
- readonly [Key in keyof Resolvers]: Resolved<Resolvers[Key]>;
184
- };
185
- /** The snapshotted local resolver map, retaining original callback identities. */
186
- readonly resolve?: Readonly<Resolvers>;
187
- /** The original native rerun policy; omission preserves Angular's default. */
188
- readonly runGuardsAndResolvers?: RunGuardsAndResolvers;
170
+ /** Type-only nominal brand carrying fragment and catchall metadata; not populated at runtime. */
171
+ readonly [routeBrand]: {
172
+ /** Type-only: this declaration's own allowed fragment-name union. */
173
+ readonly fragments: Fragments;
174
+ /** Type-only: whether this declaration is a terminal catchall leaf. */
175
+ readonly catchall: Catchall;
176
+ };
177
+ /** The exact local authored path supplied to `defineRoute`. */
178
+ readonly path: Path;
179
+ /** Type-only witness for effective path values; not populated at runtime. */
180
+ readonly __params?: Params;
181
+ /** Type-only witness for effective search values; not populated at runtime. */
182
+ readonly __search?: Search;
183
+ /** Type-only witness for unwrapped local resolver data, excluding redirects. */
184
+ readonly __data?: { readonly [Key in keyof Resolvers]: Resolved<Resolvers[Key]>; };
185
+ /** The snapshotted local resolver map, retaining original callback identities. */
186
+ readonly resolve?: Readonly<Resolvers>;
187
+ /** The original native rerun policy; omission preserves Angular's default. */
188
+ readonly runGuardsAndResolvers?: RunGuardsAndResolvers;
189
189
  }
190
190
  type Unwrap<Value> = Value extends Promise<infer Inner> | Observable<infer Inner> ? Unwrap<Inner> : Value;
191
- type Resolved<Callback> = Callback extends (...args: any[]) => infer Value ? Exclude<Unwrap<Value>, RedirectCommand> : never;
191
+ type Resolved<Callback> = Callback extends ((...args: any[]) => infer Value) ? Exclude<Unwrap<Value>, RedirectCommand> : never;
192
192
  type AnyRoute = Route<string, any, any, any, any, boolean>;
193
193
  /** Extracts a declaration's effective inherited and local path-value types. */
194
194
  type ParamsOf<Target> = Target extends Route<string, infer Params, any, any, any, boolean> ? Params : {};
195
- /** Extracts effective search-value types, retaining optional/nullable modifiers. */
195
+ /** Extracts effective parsed search-value types; keys without a fallback are optional. */
196
196
  type SearchOf<Target> = Target extends Route<string, any, infer Search, any, any, boolean> ? Search : {};
197
+ /**
198
+ * Effective search values accepted by URL creation: every key may be omitted or
199
+ * `undefined`, and a supplied value has the key's codec type.
200
+ */
201
+ type SearchInputOf<Target> = { readonly [Key in keyof SearchOf<Target>]?: SearchOf<Target>[Key] | undefined; };
197
202
  /** Extracts only the selected declaration's own allowed fragment-name union. */
198
203
  type FragmentsOf<Target> = Target extends Route<string, any, any, any, infer Fragments, boolean> ? Fragments : never;
199
204
  type CodecMap = Record<string, ParamCodec<any>>;
200
- type LocalParams<Path extends string, Codecs> = {
201
- readonly [Key in keyof Codecs]: Key extends CatchallName<Path> ? readonly CodecValue<Codecs[Key]>[] : CodecValue<Codecs[Key]>;
202
- };
203
- type SearchValues<Codecs> = {
204
- readonly [Key in keyof Codecs as IsOptional<Codecs[Key]> extends true ? never : Key]: CodecValue<Codecs[Key]>;
205
- } & {
206
- readonly [Key in keyof Codecs as IsOptional<Codecs[Key]> extends true ? Key : never]?: CodecValue<Codecs[Key]>;
207
- };
205
+ type LocalParams<Path extends string, Codecs> = { readonly [Key in keyof Codecs]: Key extends CatchallName<Path> ? readonly CodecValue<Codecs[Key]>[] : CodecValue<Codecs[Key]>; };
206
+ type SearchValues<Codecs> = { readonly [Key in keyof Codecs as HasFallback<Codecs[Key]> extends true ? Key : never]: CodecValue<Codecs[Key]>; } & { readonly [Key in keyof Codecs as HasFallback<Codecs[Key]> extends true ? never : Key]?: CodecValue<Codecs[Key]>; };
208
207
  type EffectiveParams<Path extends string, Codecs, Parent> = ParamsOf<Parent> & LocalParams<Path, Codecs>;
209
208
  type EffectiveSearch<Codecs, Parent> = SearchOf<Parent> & SearchValues<Codecs>;
210
209
  /**
@@ -212,10 +211,10 @@ type EffectiveSearch<Codecs, Parent> = SearchOf<Parent> & SearchValues<Codecs>;
212
211
  * The container and maps are shallow-frozen; nested codec values remain opaque.
213
212
  */
214
213
  type Inputs<Params, Search> = {
215
- /** Logical positional path values, not Angular's matrix-merged params. */
216
- readonly params: Readonly<Params>;
217
- /** Logical declared search values; absent optional keys are omitted. */
218
- readonly search: Readonly<Search>;
214
+ /** Logical positional path values, not Angular's matrix-merged params. */
215
+ readonly params: Readonly<Params>;
216
+ /** Logical declared search values; keys without a value or fallback are absent. */
217
+ readonly search: Readonly<Search>;
219
218
  };
220
219
  /**
221
220
  * A local resolver invoked after successful effective input parsing.
@@ -228,6 +227,16 @@ type Inputs<Params, Search> = {
228
227
  * @returns A scalar, Promise, Observable or RedirectCommand result.
229
228
  */
230
229
  type Resolver<Params, Search> = (inputs: Inputs<Params, Search>, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => unknown;
230
+ /**
231
+ * A title callback, run by Angular as a resolver: entered in Angular's injection context
232
+ * after successful effective input parsing, alongside the route's local resolvers.
233
+ *
234
+ * @param inputs - Effective inherited and local parsed values.
235
+ * @param snapshot - The declaring route's native snapshot.
236
+ * @param state - The native router state being resolved.
237
+ * @returns The page title, synchronously or via Promise/Observable.
238
+ */
239
+ type TitleResolver<Params, Search> = (inputs: Inputs<Params, Search>, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<string>;
231
240
  /**
232
241
  * An activation callback entered in Angular's injection context after parsing.
233
242
  * Parse failures prevent entry and propagate as navigation errors, not false.
@@ -241,7 +250,9 @@ type Resolver<Params, Search> = (inputs: Inputs<Params, Search>, snapshot: Activ
241
250
  type ActivationGuard<Params, Search> = (inputs: Inputs<Params, Search>, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<GuardResult>;
242
251
  /**
243
252
  * A candidate matching callback entered in Angular's injection context after parsing.
244
- * Unlike activation callbacks, it has no ancestor positional path values.
253
+ * Unlike activation callbacks, it has no ancestor positional path values. It isn't
254
+ * entered when the candidate's own path params fail to parse; the route then
255
+ * doesn't match, or its onInvalidParams handler decides.
245
256
  *
246
257
  * @param inputs - Local positional params plus effective inherited/local search.
247
258
  * @param routeConfig - Angular's native candidate configuration.
@@ -250,159 +261,171 @@ type ActivationGuard<Params, Search> = (inputs: Inputs<Params, Search>, snapshot
250
261
  * @returns A native guard result; false permits Angular's next candidate.
251
262
  */
252
263
  type MatchingGuard<Params, Search> = (inputs: Inputs<Params, Search>, routeConfig: Route$1, segments: UrlSegment[], partialSnapshot: PartialMatchRouteSnapshot) => MaybeAsync<GuardResult>;
253
- type RequiredKeys<Value> = {
254
- [Key in keyof Value]-?: Pick<Value, Key> extends Required<Pick<Value, Key>> ? Key : never;
255
- }[keyof Value];
256
- type SearchInput<Schema, Keys extends keyof Schema> = keyof Schema extends never ? Record<string, never> : Pick<Schema, RequiredKeys<Schema> | Keys>;
264
+ /**
265
+ * The error passed to onInvalidParams: the first of the candidate's own path
266
+ * params that failed to parse. The error class is not exported.
267
+ */
268
+ type PathInputError = Error & {
269
+ /** Always `"path"`: search never takes part in matching. */
270
+ readonly source: "path";
271
+ /** The failed param's name. */
272
+ readonly key: string;
273
+ /** Why the raw value was rejected; the codec failure is the error's `cause`. */
274
+ readonly reason: "missing" | "repeated" | "invalid";
275
+ };
276
+ /**
277
+ * Decides what a candidate whose own path params fail to parse does instead of
278
+ * not matching. Entered in Angular's injection context; throwing or rejecting
279
+ * fails the navigation.
280
+ *
281
+ * @param error - The path input error, with the codec failure as its cause.
282
+ * @param routeConfig - Angular's native candidate configuration.
283
+ * @param segments - Angular's candidate URL segments.
284
+ * @param partialSnapshot - Angular's native partial matching snapshot.
285
+ * @returns `false` to try Angular's next candidate, or a UrlTree/RedirectCommand
286
+ * to redirect; `true` is rejected because the params are invalid.
287
+ */
288
+ type InvalidParamsHandler = (error: PathInputError, routeConfig: Route$1, segments: UrlSegment[], partialSnapshot: PartialMatchRouteSnapshot) => MaybeAsync<false | UrlTree | RedirectCommand>;
289
+ type SearchInput<Schema, Keys extends keyof Schema> = keyof Schema extends never ? Record<string, never> : Pick<Schema, Keys>;
257
290
  /**
258
291
  * Explicit values for URL creation and generated link/navigation calls.
259
- * Effective path and required search keys must be present; optional search may
260
- * be omitted. Paramless/searchless routes forbid those fields. Fragment names
261
- * come only from the destination's own declaration.
292
+ * Effective path keys must be present; every search key may be omitted.
293
+ * Paramless/searchless routes forbid those fields. Fragment names come only
294
+ * from the destination's own declaration.
262
295
  *
263
296
  * @typeParam Target - The selected route declaration.
264
297
  * @typeParam SearchKeys - Supplied search keys inferred by each call to preserve
265
298
  * optional-key typing, including keys that coincide with Object members.
266
299
  */
267
300
  type UrlValues<Target extends AnyRoute, SearchKeys extends keyof SearchOf<Target> = keyof SearchOf<Target>> = (keyof ParamsOf<Target> extends never ? {
268
- /** Forbidden when the destination has no effective path params. */
269
- params?: never;
301
+ /** Forbidden when the destination has no effective path params. */
302
+ params?: never;
270
303
  } : {
271
- /** All effective path values; catchalls are readonly element-value arrays. */
272
- params: ParamsOf<Target>;
273
- }) & (keyof SearchOf<Target> extends never ? {
274
- /** Forbidden when the destination has no search declarations. */
275
- search?: never;
276
- } : RequiredKeys<SearchOf<Target>> extends never ? {
277
- /** Optional-only search; absent or undefined optional values are omitted. */
278
- search?: SearchInput<SearchOf<Target>, SearchKeys>;
304
+ /** All effective path values; catchalls are readonly element-value arrays. */
305
+ params: ParamsOf<Target>;
306
+ }) & (keyof SearchInputOf<Target> extends never ? {
307
+ /** Forbidden when the destination has no search declarations. */
308
+ search?: never;
279
309
  } : {
280
- /** All required search values plus any supplied optional values. */
281
- search: SearchInput<SearchOf<Target>, SearchKeys>;
310
+ /** Any declared search values; absent or undefined values are omitted. */
311
+ search?: SearchInput<SearchInputOf<Target>, SearchKeys & keyof SearchInputOf<Target>>;
282
312
  }) & (FragmentsOf<Target> extends never ? {
283
- /** Forbidden when this declaration has no fragment names. */
284
- fragment?: never;
313
+ /** Forbidden when this declaration has no fragment names. */
314
+ fragment?: never;
285
315
  } : {
286
- /** An optional logical name declared by this exact destination, without `#`. */
287
- fragment?: FragmentsOf<Target>;
316
+ /** An optional logical name declared by this exact destination, without `#`. */
317
+ fragment?: FragmentsOf<Target>;
288
318
  });
289
- type CompatibleKeys<Source, Value> = {
290
- [Key in keyof Source]: Source[Key] extends Value ? Key : never;
291
- }[keyof Source];
292
- type Mapping<Source, Destination> = {
293
- [Key in keyof Destination]: CompatibleKeys<Source, Destination[Key]>;
294
- };
295
- type Forwardable<Source, Destination> = {
296
- [Key in keyof Destination]: Key extends keyof Source ? Source[Key] extends Destination[Key] ? never : Key : Key;
297
- }[keyof Destination] extends never ? true : false;
298
- type Preservable<Source, Destination> = {
299
- [Key in RequiredKeys<Destination>]: Key extends RequiredKeys<Source> ? Source[Key & keyof Source] extends Destination[Key] ? never : Key : Key;
300
- }[RequiredKeys<Destination>] extends never ? true : false;
319
+ type CompatibleKeys<Source, Value> = { [Key in keyof Source]: Source[Key] extends Value ? Key : never; }[keyof Source];
320
+ type Mapping<Source, Destination> = { [Key in keyof Destination]: CompatibleKeys<Source, Destination[Key]>; };
321
+ type Forwardable<Source, Destination> = { [Key in keyof Destination]: Key extends keyof Source ? Source[Key] extends Destination[Key] ? never : Key : Key; }[keyof Destination] extends never ? true : false;
301
322
  /**
302
323
  * A typed redirect to a genuine authored declaration, not a concrete URL.
303
324
  *
304
325
  * @remarks
305
326
  * Params map destination keys to compatible effective source path keys. Omission
306
327
  * forwards by name only when all target keys are compatible. Search and fragment
307
- * policies are mandatory. Search preserve validates destination-declared values
308
- * but retains original query text and unknown keys; drop requires no required
309
- * target search keys; replace uses explicit target-typed values (`{}` is allowed
310
- * for searchless targets). Fragment preserve retains the incoming raw name,
311
- * whereas replace requires a target-declared name. Angular executes the redirect.
328
+ * policies are mandatory. Search preserve retains the original query text, drop
329
+ * removes it, and replace serializes explicit target-typed values (`{}` removes
330
+ * the query). Fragment preserve retains the incoming raw name, whereas replace
331
+ * requires a target-declared name. Angular executes the redirect.
312
332
  */
313
- type Redirect<SourceParams, SourceSearch, Target extends AnyRoute, SearchKeys extends keyof SearchOf<Target>> = {
314
- /** The already constructed target; generation requires a direct default import. */
315
- to: Target;
316
- /** Explicit query policy: preserve incoming text, drop it, or replace with values. */
317
- search: {
318
- /** Discard incoming queries and serialize explicit target-typed values. */
319
- mode: "replace";
320
- /** Required target search keys plus selected optional keys; unknown keys fail. */
321
- values: SearchInput<SearchOf<NoInfer<Target>>, SearchKeys>;
322
- } | (RequiredKeys<SearchOf<Target>> extends never ? {
323
- /** Remove all incoming query values; target required keys are forbidden. */
324
- mode: "drop";
325
- } : never) | (Preservable<SourceSearch, SearchOf<Target>> extends true ? {
326
- /** Keep original query text, validating destination-declared cardinality/values. */
327
- mode: "preserve";
328
- } : never);
329
- /** Explicit hash policy; preserved incoming names need not be declared. */
330
- fragment: {
331
- /** Keep the raw incoming hash, or remove it entirely. */
332
- mode: "preserve" | "drop";
333
- } | (FragmentsOf<Target> extends never ? never : {
334
- /** Replace the incoming hash with an explicitly declared target name. */
335
- mode: "replace";
336
- /** A logical target fragment name without the `#` delimiter. */
337
- value: FragmentsOf<Target>;
338
- });
333
+ type Redirect<SourceParams, Target extends AnyRoute, SearchKeys extends keyof SearchOf<Target>> = {
334
+ /** The already constructed target; generation requires a direct default import. */
335
+ to: Target;
336
+ /** Explicit query policy: preserve incoming text, drop it, or replace with values. */
337
+ search: {
338
+ /** Discard incoming queries and serialize explicit target-typed values. */
339
+ mode: "replace";
340
+ /** Any target search keys; unknown keys fail. */
341
+ values: SearchInput<SearchInputOf<NoInfer<Target>>, SearchKeys & keyof SearchInputOf<NoInfer<Target>>>;
342
+ } | {
343
+ /** Keep the original query text, or remove all incoming query values. */
344
+ mode: "preserve" | "drop";
345
+ };
346
+ /** Explicit hash policy; preserved incoming names need not be declared. */
347
+ fragment: {
348
+ /** Keep the raw incoming hash, or remove it entirely. */
349
+ mode: "preserve" | "drop";
350
+ } | (FragmentsOf<Target> extends never ? never : {
351
+ /** Replace the incoming hash with an explicitly declared target name. */
352
+ mode: "replace";
353
+ /** A logical target fragment name without the `#` delimiter. */
354
+ value: FragmentsOf<Target>;
355
+ });
339
356
  } & (Forwardable<SourceParams, ParamsOf<Target>> extends true ? {
340
- /** Optional destination-key to source-key mapping; otherwise forward by name. */
341
- params?: Mapping<SourceParams, ParamsOf<Target>>;
357
+ /** Optional destination-key to source-key mapping; otherwise forward by name. */
358
+ params?: Mapping<SourceParams, ParamsOf<Target>>;
342
359
  } : {
343
- /** Required mapping of every destination path key to a compatible source key. */
344
- params: Mapping<SourceParams, ParamsOf<Target>>;
360
+ /** Required mapping of every destination path key to a compatible source key. */
361
+ params: Mapping<SourceParams, ParamsOf<Target>>;
345
362
  });
346
363
  /**
347
364
  * Options inferred by `defineRoute`; Angular's structural route fields are excluded.
348
365
  * Local params/search cannot redeclare keys inherited from the parent.
349
366
  */
350
367
  type RouteConfig<Path extends string, Parent extends AnyRoute | undefined, Params extends Record<string, PathCodec>, Search extends CodecMap, Resolvers, Fragments extends readonly string[], Target extends AnyRoute | undefined, ReplacementKeys extends keyof SearchOf<Target>> = {
351
- /** Genuine authored parent; pathless nodes retain inherited input ownership. */
352
- parent?: Parent & (Parent extends Route<string, any, any, any, any, true> ? never : unknown);
353
- /** Local search codecs; keys are required unless wrapped with optionalSearchParam. */
354
- search?: Search & Record<Extract<keyof Search, keyof SearchOf<Parent>>, never>;
355
- /** Local parsed-input resolvers; not snapshot-first native ResolveFn callbacks. */
356
- resolve?: Resolvers & Record<PropertyKey, Resolver<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>> & {
357
- __loozoAngularRouterRouteId?: never;
358
- };
359
- /** Local logical anchor names, never inherited; this does not enable scrolling. */
360
- fragments?: Fragments;
361
- /** Native rerun policy. Use paramsOrQueryParamsChange for query-dependent data. */
362
- runGuardsAndResolvers?: RunGuardsAndResolvers;
363
- /** Guards with declaring-parent inputs and Angular's native child snapshot. */
364
- canActivateChild?: readonly ActivationGuard<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>[];
365
- /** Native component-first guard functions/tokens, without automatic input parsing. */
366
- canDeactivate?: Route$1["canDeactivate"];
367
- /** Native route-scoped providers; provider factories are not run at declaration. */
368
- providers?: Route$1["providers"];
369
- /** Native static title or title resolver, not a parsed-input callback. */
370
- title?: Route$1["title"];
371
- /** Native static data; cannot set the reserved __loozoAngularRouterRouteId key. */
372
- data?: Route$1["data"] & {
373
- __loozoAngularRouterRouteId?: never;
374
- };
368
+ /** Genuine authored parent; pathless nodes retain inherited input ownership. */
369
+ parent?: Parent & (Parent extends Route<string, any, any, any, any, true> ? never : unknown);
370
+ /** Local search codecs; a key is absent unless present and valid, or wrapped withFallback. */
371
+ search?: Search & Record<Extract<keyof Search, keyof SearchOf<Parent>>, never> & SyncCodecs<NoInfer<Search>>;
372
+ /** Local parsed-input resolvers; not snapshot-first native ResolveFn callbacks. */
373
+ resolve?: Resolvers & Record<PropertyKey, Resolver<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>> & {
374
+ __loozoAngularRouterRouteId?: never;
375
+ };
376
+ /** Local logical anchor names, never inherited; this does not enable scrolling. */
377
+ fragments?: Fragments;
378
+ /** Native rerun policy. Use paramsOrQueryParamsChange for query-dependent data. */
379
+ runGuardsAndResolvers?: RunGuardsAndResolvers;
380
+ /** Guards with declaring-parent inputs and Angular's native child snapshot. */
381
+ canActivateChild?: readonly ActivationGuard<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>[];
382
+ /** Native component-first guard functions/tokens, without automatic input parsing. */
383
+ canDeactivate?: Route$1["canDeactivate"];
384
+ /** Native route-scoped providers; provider factories are not run at declaration. */
385
+ providers?: Route$1["providers"];
386
+ /** A static title, or a parsed-input callback; not a snapshot-first native ResolveFn. */
387
+ title?: string | TitleResolver<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>;
388
+ /** Native static data; cannot set the reserved __loozoAngularRouterRouteId key. */
389
+ data?: Route$1["data"] & {
390
+ __loozoAngularRouterRouteId?: never;
391
+ };
375
392
  } & (PathNames<Path> extends never ? {
376
- /** Paths without local placeholders cannot declare a params map. */
377
- params?: never;
393
+ /** Paths without local placeholders cannot declare a params map. */
394
+ params?: never;
378
395
  } : {
379
- /** Exactly the local placeholder names; catchall codecs apply per element. */
380
- params: Params & Record<PathNames<Path>, PathCodec> & Record<Exclude<keyof Params, PathNames<Path>> | Extract<keyof Params, keyof ParamsOf<Parent>>, never>;
396
+ /** Exactly the local placeholder names; catchall codecs apply per element. */
397
+ params: Params & Record<PathNames<Path>, PathCodec> & Record<Exclude<keyof Params, PathNames<Path>> | Extract<keyof Params, keyof ParamsOf<Parent>>, never> & SyncCodecs<NoInfer<Params>>;
381
398
  }) & (Target extends AnyRoute ? {
382
- /** Typed native redirect, incompatible with own canActivate/canMatch fields. */
383
- redirect: Redirect<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>, Target, ReplacementKeys>;
384
- canActivate?: never;
385
- canMatch?: never;
399
+ /** Typed native redirect, incompatible with own canActivate/canMatch/onInvalidParams fields. */
400
+ redirect: Redirect<EffectiveParams<Path, Params, Parent>, Target, ReplacementKeys>;
401
+ canActivate?: never;
402
+ canMatch?: never;
403
+ onInvalidParams?: never;
386
404
  } : {
387
- redirect?: never;
388
- /** Activation guards with effective inherited and local parsed inputs. */
389
- canActivate?: readonly ActivationGuard<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>[];
390
- /** Matching guards with local path params and effective search only. */
391
- canMatch?: readonly MatchingGuard<LocalParams<Path, Params>, EffectiveSearch<Search, Parent>>[];
405
+ redirect?: never;
406
+ /**
407
+ * Decides what happens when this route's own path params fail to parse;
408
+ * without it the route doesn't match and Angular tries its next candidate.
409
+ * Only routes with local path params accept it.
410
+ */
411
+ onInvalidParams?: PathNames<Path> extends never ? never : InvalidParamsHandler;
412
+ /** Activation guards with effective inherited and local parsed inputs. */
413
+ canActivate?: readonly ActivationGuard<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>[];
414
+ /** Matching guards with local path params and effective search only. */
415
+ canMatch?: readonly MatchingGuard<LocalParams<Path, Params>, EffectiveSearch<Search, Parent>>[];
392
416
  });
393
-
394
417
  interface RouteMetadata {
395
- readonly parent?: AnyRoute;
396
- readonly localSegments: readonly Segment[];
397
- readonly segments: readonly Segment[];
398
- readonly localParams: Readonly<Record<string, ParamCodec<any>>>;
399
- readonly localSearch: Readonly<Record<string, ParamCodec<any>>>;
400
- readonly params: Readonly<Record<string, ParamCodec<any>>>;
401
- readonly search: Readonly<Record<string, ParamCodec<any>>>;
402
- readonly fragments: readonly string[];
403
- readonly config: Readonly<Record<string, unknown>>;
418
+ readonly parent?: AnyRoute;
419
+ readonly localSegments: readonly Segment[];
420
+ readonly segments: readonly Segment[];
421
+ readonly localParams: Readonly<Record<string, ParamCodec<any>>>;
422
+ readonly localSearch: Readonly<Record<string, ParamCodec<any>>>;
423
+ readonly params: Readonly<Record<string, ParamCodec<any>>>;
424
+ readonly search: Readonly<Record<string, ParamCodec<any>>>;
425
+ readonly fragments: readonly string[];
426
+ readonly config: Readonly<Record<string, unknown>>;
404
427
  }
405
- declare function routeMetadata(route: unknown): RouteMetadata;
428
+ export declare function routeMetadata(route: unknown): RouteMetadata;
406
429
  /**
407
430
  * Declares a nominal typed route without executing codecs or native callbacks.
408
431
  *
@@ -429,33 +452,32 @@ declare function routeMetadata(route: unknown): RouteMetadata;
429
452
  * generator-owned and cannot be overridden.
430
453
  * @returns A frozen route preserving inferred effective input and local data types.
431
454
  * @throws TypeError for invalid paths/configuration, missing or extra local
432
- * codecs, inherited key conflicts, or nongenuine parent/redirect routes.
455
+ * codecs, fallback codecs in path params, inherited key conflicts, or
456
+ * nongenuine parent/redirect routes.
433
457
  *
434
458
  * @example
435
459
  * ```ts
436
460
  * const product = defineRoute("products/$productId", {
437
461
  * params: { productId: numberParam },
438
- * search: { tab: optionalSearchParam(enumParam(["details", "reviews"])) },
462
+ * search: { tab: withFallback(enumParam(["details", "reviews"]), "details") },
439
463
  * fragments: ["summary", "reviews"],
440
464
  * });
441
465
  * ```
442
466
  */
443
- declare function defineRoute<const Path extends string, Parent extends AnyRoute | undefined = undefined, Params extends Record<string, PathCodec> = {}, Search extends CodecMap = {}, Resolvers extends Record<string, (...args: any[]) => unknown> = {}, const Fragments extends readonly string[] = [], Target extends AnyRoute | undefined = undefined, ReplacementKeys extends keyof SearchOf<NoInfer<Target>> = never>(path: Path & ValidPath<Path>, config: RouteConfig<Path, Parent, Params, Search, Resolvers, Fragments, Target, ReplacementKeys>): Route<Path, EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>, Resolvers, Fragments[number], CatchallName<Path> extends never ? false : true>;
444
-
467
+ export declare function defineRoute<const Path extends string, Parent extends AnyRoute | undefined = undefined, Params extends Record<string, PathCodec> = {}, Search extends CodecMap = {}, Resolvers extends Record<string, (...args: any[]) => unknown> = {}, const Fragments extends readonly string[] = [], Target extends AnyRoute | undefined = undefined, ReplacementKeys extends keyof SearchOf<NoInfer<Target>> = never>(path: Path & ValidPath<Path>, config: RouteConfig<Path, Parent, Params, Search, Resolvers, Fragments, Target, ReplacementKeys>): Route<Path, EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>, Resolvers, Fragments[number], CatchallName<Path> extends never ? false : true>;
445
468
  /**
446
469
  * Synchronously serializes a route's explicit values into a root-relative URL.
447
470
  *
448
471
  * @remarks
449
472
  * Includes inherited path/search declarations, but only this route's fragment
450
- * names. Every effective path value and required search key must be supplied.
451
- * Optional-only search may be omitted; searchless/paramless routes forbid those
452
- * records. Catchalls require readonly arrays, including a valid empty array.
473
+ * names. Every effective path value must be supplied; any search key may be
474
+ * omitted. Searchless/paramless routes forbid those records. Catchalls require readonly arrays, including a valid empty array.
453
475
  * Values are never taken from a current navigation.
454
476
  *
455
477
  * Stringifies without calling parsers, guards, resolvers or redirects. Static
456
478
  * segments and logical values are component-encoded (also escaping parentheses),
457
- * so a dynamic slash becomes `%2F`. Search keys are globally sorted; optional
458
- * `undefined` values are omitted. An empty effective path produces `/`.
479
+ * so a dynamic slash becomes `%2F`. Search keys are globally sorted; omitted
480
+ * and `undefined` values are left out. An empty effective path produces `/`.
459
481
  * Redirect declarations serialize their source URL, not their redirect target.
460
482
  *
461
483
  * @param route - A genuine declaration from this physical package instance,
@@ -478,7 +500,5 @@ declare function defineRoute<const Path extends string, Parent extends AnyRoute
478
500
  * // /products/42#summary
479
501
  * ```
480
502
  */
481
- declare function createUrl<Target extends AnyRoute, SearchKeys extends keyof SearchOf<NoInfer<Target>> = never>(route: Target, values: UrlValues<NoInfer<Target>, SearchKeys>): string;
482
-
483
- export { booleanParam, codecPresence, createUrl, defineRoute, enumParam, nullableSearchParam, numberParam, optionalSearchParam, routeMetadata, stringParam };
484
- export type { AnyRoute, FragmentsOf, Inputs, ParamCodec, ParamsOf, Route, RouteMetadata, SearchOf, UrlValues };
503
+ export declare function createUrl<Target extends AnyRoute, SearchKeys extends keyof SearchOf<NoInfer<Target>> = never>(route: Target, values: UrlValues<NoInfer<Target>, SearchKeys>): string;
504
+ export type { AnyRoute, FragmentsOf, Inputs, ParamCodec, ParamsOf, Route, RouteMetadata, SearchOf, UrlValues };