@loozo/angular-router 0.0.3 → 0.0.5
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 +246 -124
- package/fesm2022/loozo-angular-router-_core.mjs +19 -41
- package/fesm2022/loozo-angular-router-_core.mjs.map +1 -1
- package/fesm2022/loozo-angular-router-_generated.mjs +148 -315
- package/fesm2022/loozo-angular-router-_generated.mjs.map +1 -1
- package/fesm2022/loozo-angular-router.mjs +2 -2
- package/node/src/builders/application.js +12 -3
- package/node/src/builders/options.js +9 -3
- package/node/src/builders/serve.js +21 -14
- package/node/src/generation/emit.js +26 -8
- package/node/src/generation/filesystem.js +261 -69
- package/node/src/generation/generate.d.ts +1 -1
- package/node/src/generation/generate.js +6 -1
- package/node/src/generation/sources.js +7 -2
- package/node/src/generation/validate.js +15 -24
- package/package.json +1 -1
- package/schemas/application.json +29 -20
- package/schemas/serve.json +142 -7
- package/snippets/angular.json +29 -0
- package/snippets/generated/destinations.generated.ts +126 -0
- package/snippets/generated/routes.generated.ts +35 -0
- package/snippets/src/catalog.ts +20 -0
- package/snippets/src/docs-url.ts +8 -0
- package/snippets/src/index.html +11 -0
- package/snippets/src/main.ts +6 -0
- package/snippets/src/product-url.ts +8 -0
- package/snippets/src/routes/products/$productId/page.ts +20 -0
- package/snippets/src/routes/products/$productId/route.generated.ts +24 -0
- package/snippets/src/routes/products/$productId/route.ts +7 -0
- package/snippets/src/routes/reports/$reportId/page.ts +9 -0
- package/snippets/src/routes/reports/$reportId/route.generated.ts +24 -0
- package/snippets/src/routes/reports/$reportId/route.ts +6 -0
- package/snippets/src/routes/teams/$teamId/items/$id/inputs.ts +15 -0
- package/snippets/src/routes/teams/$teamId/items/$id/links.ts +13 -0
- package/snippets/src/routes/teams/$teamId/items/$id/page.ts +9 -0
- package/snippets/src/routes/teams/$teamId/items/$id/route.generated.ts +24 -0
- package/snippets/src/routes/teams/$teamId/items/$id/route.ts +18 -0
- package/snippets/src/routes/teams/$teamId/members/$memberId/page.ts +9 -0
- package/snippets/src/routes/teams/$teamId/members/$memberId/route.generated.ts +24 -0
- package/snippets/src/routes/teams/$teamId/members/$memberId/route.ts +7 -0
- package/snippets/src/routes/teams/$teamId/old-items/$id/route.ts +13 -0
- package/snippets/src/routes/teams/$teamId/route.generated.ts +24 -0
- package/snippets/src/routes/teams/$teamId/route.ts +6 -0
- package/snippets/src/shell.ts +9 -0
- package/snippets/src/slug.ts +14 -0
- package/snippets/tsconfig.app.json +14 -0
- package/snippets/tsconfig.routes.json +5 -0
- package/types/loozo-angular-router-_core.d.ts +141 -99
- package/types/loozo-angular-router-_generated.d.ts +48 -31
- package/types/loozo-angular-router.d.ts +2 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Component } from "@angular/core";
|
|
2
|
+
import { injectRoute } from "./route.generated.js";
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
template: `<h1>Member {{ route.params().memberId }} of team {{ route.params().teamId }}</h1>`,
|
|
6
|
+
})
|
|
7
|
+
export default class Member {
|
|
8
|
+
readonly route = injectRoute();
|
|
9
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @loozo/angular-router routegen: owned route.generated.ts
|
|
2
|
+
import { createDestination, injectGeneratedRoute } from '@loozo/angular-router/_generated';
|
|
3
|
+
import { getRouteSet, type Destination, type Handle } from "../../../../../../generated/destinations.generated.js";
|
|
4
|
+
export { defineRoute } from '@loozo/angular-router';
|
|
5
|
+
/**
|
|
6
|
+
* Inert reference to this exact declaration for same-set handle link/navigate calls.
|
|
7
|
+
* No injection context is required; copies and foreign-set references are invalid.
|
|
8
|
+
* This is not an authored route and cannot be passed to the root createUrl function.
|
|
9
|
+
*/
|
|
10
|
+
export const destination: Destination<"src/routes/teams/$teamId/members/$memberId/route.ts"> = createDestination(() => getRouteSet(), "src/routes/teams/$teamId/members/$memberId/route.ts");
|
|
11
|
+
/**
|
|
12
|
+
* Injects a typed handle bound to this exact generated activated route record.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Call in an Angular injection context. The handle exposes readonly native
|
|
16
|
+
* params/search/data Signals and destination-typed link/navigate methods.
|
|
17
|
+
* Data includes local resolver results only, not static or inherited data.
|
|
18
|
+
* All navigation values are explicit; current inputs are never reused implicitly.
|
|
19
|
+
*
|
|
20
|
+
* @returns A noncallable handle for this declaration.
|
|
21
|
+
* @throws A synchronous wiring error outside an injection context or when the
|
|
22
|
+
* activated record does not carry this declaration's own generated identity.
|
|
23
|
+
*/
|
|
24
|
+
export function injectRoute(): Handle<"src/routes/teams/$teamId/members/$memberId/route.ts"> { return injectGeneratedRoute(getRouteSet(), "src/routes/teams/$teamId/members/$memberId/route.ts"); }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineRoute, numberParam } from "@loozo/angular-router";
|
|
2
|
+
import team from "../../route.js";
|
|
3
|
+
import item from "../../items/$id/route.js";
|
|
4
|
+
|
|
5
|
+
export default defineRoute("old-items/$id", {
|
|
6
|
+
parent: team,
|
|
7
|
+
params: { id: numberParam },
|
|
8
|
+
redirect: {
|
|
9
|
+
to: item,
|
|
10
|
+
search: { mode: "drop" }, // or "preserve", or { mode: "replace", values }
|
|
11
|
+
fragment: { mode: "drop" }, // or "preserve", or { mode: "replace", value }
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @loozo/angular-router routegen: owned route.generated.ts
|
|
2
|
+
import { createDestination, injectGeneratedRoute } from '@loozo/angular-router/_generated';
|
|
3
|
+
import { getRouteSet, type Destination, type Handle } from "../../../../generated/destinations.generated.js";
|
|
4
|
+
export { defineRoute } from '@loozo/angular-router';
|
|
5
|
+
/**
|
|
6
|
+
* Inert reference to this exact declaration for same-set handle link/navigate calls.
|
|
7
|
+
* No injection context is required; copies and foreign-set references are invalid.
|
|
8
|
+
* This is not an authored route and cannot be passed to the root createUrl function.
|
|
9
|
+
*/
|
|
10
|
+
export const destination: Destination<"src/routes/teams/$teamId/route.ts"> = createDestination(() => getRouteSet(), "src/routes/teams/$teamId/route.ts");
|
|
11
|
+
/**
|
|
12
|
+
* Injects a typed handle bound to this exact generated activated route record.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Call in an Angular injection context. The handle exposes readonly native
|
|
16
|
+
* params/search/data Signals and destination-typed link/navigate methods.
|
|
17
|
+
* Data includes local resolver results only, not static or inherited data.
|
|
18
|
+
* All navigation values are explicit; current inputs are never reused implicitly.
|
|
19
|
+
*
|
|
20
|
+
* @returns A noncallable handle for this declaration.
|
|
21
|
+
* @throws A synchronous wiring error outside an injection context or when the
|
|
22
|
+
* activated record does not carry this declaration's own generated identity.
|
|
23
|
+
*/
|
|
24
|
+
export function injectRoute(): Handle<"src/routes/teams/$teamId/route.ts"> { return injectGeneratedRoute(getRouteSet(), "src/routes/teams/$teamId/route.ts"); }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ParamCodec } from "@loozo/angular-router";
|
|
2
|
+
|
|
3
|
+
export const slug: ParamCodec<string> = {
|
|
4
|
+
parse(raw) {
|
|
5
|
+
if (!/^[a-z0-9-]+$/.test(raw)) {
|
|
6
|
+
throw new Error("invalid slug");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return raw;
|
|
10
|
+
},
|
|
11
|
+
stringify(value) {
|
|
12
|
+
return value;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"lib": ["ES2022", "DOM"],
|
|
9
|
+
"types": []
|
|
10
|
+
},
|
|
11
|
+
"angularCompilerOptions": { "strictTemplates": true },
|
|
12
|
+
"files": ["src/main.ts"],
|
|
13
|
+
"include": ["src/**/*.ts", "generated/**/*.ts"]
|
|
14
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { ActivatedRouteSnapshot, GuardResult, MaybeAsync, PartialMatchRouteSnapshot, RedirectCommand, Route as Route$1, RouterStateSnapshot, RunGuardsAndResolvers, UrlSegment } from "@angular/router";
|
|
1
|
+
import { ActivatedRouteSnapshot, GuardResult, MaybeAsync, PartialMatchRouteSnapshot, RedirectCommand, Route as Route$1, RouterStateSnapshot, RunGuardsAndResolvers, UrlSegment, UrlTree } from "@angular/router";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
3
|
/**
|
|
4
4
|
* Converts one decoded path or search value to and from an application value.
|
|
5
5
|
*
|
|
6
6
|
* @remarks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
10
11
|
* For a terminal `$name[]` path, this codec applies to each element separately.
|
|
11
12
|
*
|
|
12
|
-
* @typeParam Value - The logical value,
|
|
13
|
+
* @typeParam Value - The logical value, never a promise of that value.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* ```ts
|
|
@@ -24,11 +25,12 @@ interface ParamCodec<Value> {
|
|
|
24
25
|
* Parses one already percent-decoded value; do not decode it again.
|
|
25
26
|
*
|
|
26
27
|
* @param raw - A single logical string from Angular's URL inputs.
|
|
27
|
-
* @returns The logical value
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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.
|
|
30
32
|
*/
|
|
31
|
-
parse(raw: string): Value
|
|
33
|
+
parse(raw: string): Value;
|
|
32
34
|
/**
|
|
33
35
|
* Serializes one logical value without percent-encoding it.
|
|
34
36
|
*
|
|
@@ -38,23 +40,34 @@ interface ParamCodec<Value> {
|
|
|
38
40
|
*/
|
|
39
41
|
stringify(value: Value): string;
|
|
40
42
|
}
|
|
41
|
-
declare const
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
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;
|
|
49
50
|
};
|
|
50
51
|
type PathCodec = ParamCodec<any> & {
|
|
51
|
-
readonly [
|
|
52
|
+
readonly [searchFallback]?: never;
|
|
52
53
|
};
|
|
53
54
|
type CodecValue<Codec> = Codec extends ParamCodec<infer Value> ? Value : never;
|
|
54
|
-
type
|
|
55
|
-
readonly [
|
|
55
|
+
type HasFallback<Codec> = Codec extends {
|
|
56
|
+
readonly [searchFallback]: true;
|
|
56
57
|
} ? true : false;
|
|
57
|
-
|
|
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;
|
|
58
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.
|
|
@@ -94,44 +107,30 @@ export declare const booleanParam: ParamCodec<boolean>;
|
|
|
94
107
|
*/
|
|
95
108
|
export declare function enumParam<const Values extends readonly string[]>(values: Values): ParamCodec<Values[number]>;
|
|
96
109
|
/**
|
|
97
|
-
*
|
|
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
|
-
export declare function optionalSearchParam<Value>(codec: ParamCodec<Value>): OptionalCodec<Value>;
|
|
112
|
-
/**
|
|
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
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
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
|
-
*
|
|
129
|
-
* //
|
|
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
|
-
export declare function
|
|
133
|
-
readonly [searchPresence]?: "nullable";
|
|
134
|
-
}): NullableCodec<Value>;
|
|
133
|
+
export declare function withFallback<Value>(codec: ParamCodec<Value> & SyncValue<NoInfer<Value>>, fallback: NoInfer<Value>): FallbackCodec<Value>;
|
|
135
134
|
type ValidName<Name extends string> = Name extends "" | "__proto__" | `...${string}` | `${string}?${string}` | `${string}*${string}` | `${string}[]${string}` ? false : true;
|
|
136
135
|
type SegmentValid<Segment extends string> = Segment extends `$${infer Name}` ? ValidName<Name> : Segment extends "" | "." | ".." | "*" | "**" | `:${string}` ? false : true;
|
|
137
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>;
|
|
@@ -160,15 +159,19 @@ declare const routeBrand: unique symbol;
|
|
|
160
159
|
*
|
|
161
160
|
* @typeParam Path - The local authored path literal, not the effective URL path.
|
|
162
161
|
* @typeParam Params - Effective inherited and local logical path values.
|
|
163
|
-
* @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.
|
|
164
164
|
* @typeParam Resolvers - Local resolver callbacks; data unwraps Promise/Observable
|
|
165
165
|
* results and excludes Angular RedirectCommand values.
|
|
166
166
|
* @typeParam Fragments - This declaration's own allowed fragment-name union.
|
|
167
167
|
* @typeParam Catchall - Whether the declaration is a terminal catchall leaf.
|
|
168
168
|
*/
|
|
169
169
|
interface Route<Path extends string, Params, Search, Resolvers = {}, Fragments extends string = string, Catchall extends boolean = CatchallName<Path> extends never ? false : true> {
|
|
170
|
+
/** Type-only nominal brand carrying fragment and catchall metadata; not populated at runtime. */
|
|
170
171
|
readonly [routeBrand]: {
|
|
172
|
+
/** Type-only: this declaration's own allowed fragment-name union. */
|
|
171
173
|
readonly fragments: Fragments;
|
|
174
|
+
/** Type-only: whether this declaration is a terminal catchall leaf. */
|
|
172
175
|
readonly catchall: Catchall;
|
|
173
176
|
};
|
|
174
177
|
/** The exact local authored path supplied to `defineRoute`. */
|
|
@@ -189,13 +192,18 @@ type Resolved<Callback> = Callback extends ((...args: any[]) => infer Value) ? E
|
|
|
189
192
|
type AnyRoute = Route<string, any, any, any, any, boolean>;
|
|
190
193
|
/** Extracts a declaration's effective inherited and local path-value types. */
|
|
191
194
|
type ParamsOf<Target> = Target extends Route<string, infer Params, any, any, any, boolean> ? Params : {};
|
|
192
|
-
/** Extracts effective search-value types
|
|
195
|
+
/** Extracts effective parsed search-value types; keys without a fallback are optional. */
|
|
193
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; };
|
|
194
202
|
/** Extracts only the selected declaration's own allowed fragment-name union. */
|
|
195
203
|
type FragmentsOf<Target> = Target extends Route<string, any, any, any, infer Fragments, boolean> ? Fragments : never;
|
|
196
204
|
type CodecMap = Record<string, ParamCodec<any>>;
|
|
197
205
|
type LocalParams<Path extends string, Codecs> = { readonly [Key in keyof Codecs]: Key extends CatchallName<Path> ? readonly CodecValue<Codecs[Key]>[] : CodecValue<Codecs[Key]>; };
|
|
198
|
-
type SearchValues<Codecs> = { readonly [Key in keyof Codecs as
|
|
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]>; };
|
|
199
207
|
type EffectiveParams<Path extends string, Codecs, Parent> = ParamsOf<Parent> & LocalParams<Path, Codecs>;
|
|
200
208
|
type EffectiveSearch<Codecs, Parent> = SearchOf<Parent> & SearchValues<Codecs>;
|
|
201
209
|
/**
|
|
@@ -205,7 +213,7 @@ type EffectiveSearch<Codecs, Parent> = SearchOf<Parent> & SearchValues<Codecs>;
|
|
|
205
213
|
type Inputs<Params, Search> = {
|
|
206
214
|
/** Logical positional path values, not Angular's matrix-merged params. */
|
|
207
215
|
readonly params: Readonly<Params>;
|
|
208
|
-
/** Logical declared search values;
|
|
216
|
+
/** Logical declared search values; keys without a value or fallback are absent. */
|
|
209
217
|
readonly search: Readonly<Search>;
|
|
210
218
|
};
|
|
211
219
|
/**
|
|
@@ -219,6 +227,16 @@ type Inputs<Params, Search> = {
|
|
|
219
227
|
* @returns A scalar, Promise, Observable or RedirectCommand result.
|
|
220
228
|
*/
|
|
221
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>;
|
|
222
240
|
/**
|
|
223
241
|
* An activation callback entered in Angular's injection context after parsing.
|
|
224
242
|
* Parse failures prevent entry and propagate as navigation errors, not false.
|
|
@@ -232,7 +250,9 @@ type Resolver<Params, Search> = (inputs: Inputs<Params, Search>, snapshot: Activ
|
|
|
232
250
|
type ActivationGuard<Params, Search> = (inputs: Inputs<Params, Search>, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => MaybeAsync<GuardResult>;
|
|
233
251
|
/**
|
|
234
252
|
* A candidate matching callback entered in Angular's injection context after parsing.
|
|
235
|
-
* 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.
|
|
236
256
|
*
|
|
237
257
|
* @param inputs - Local positional params plus effective inherited/local search.
|
|
238
258
|
* @param routeConfig - Angular's native candidate configuration.
|
|
@@ -241,13 +261,37 @@ type ActivationGuard<Params, Search> = (inputs: Inputs<Params, Search>, snapshot
|
|
|
241
261
|
* @returns A native guard result; false permits Angular's next candidate.
|
|
242
262
|
*/
|
|
243
263
|
type MatchingGuard<Params, Search> = (inputs: Inputs<Params, Search>, routeConfig: Route$1, segments: UrlSegment[], partialSnapshot: PartialMatchRouteSnapshot) => MaybeAsync<GuardResult>;
|
|
244
|
-
|
|
245
|
-
|
|
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>;
|
|
246
290
|
/**
|
|
247
291
|
* Explicit values for URL creation and generated link/navigation calls.
|
|
248
|
-
* Effective path
|
|
249
|
-
*
|
|
250
|
-
*
|
|
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.
|
|
251
295
|
*
|
|
252
296
|
* @typeParam Target - The selected route declaration.
|
|
253
297
|
* @typeParam SearchKeys - Supplied search keys inferred by each call to preserve
|
|
@@ -259,15 +303,12 @@ type UrlValues<Target extends AnyRoute, SearchKeys extends keyof SearchOf<Target
|
|
|
259
303
|
} : {
|
|
260
304
|
/** All effective path values; catchalls are readonly element-value arrays. */
|
|
261
305
|
params: ParamsOf<Target>;
|
|
262
|
-
}) & (keyof
|
|
306
|
+
}) & (keyof SearchInputOf<Target> extends never ? {
|
|
263
307
|
/** Forbidden when the destination has no search declarations. */
|
|
264
308
|
search?: never;
|
|
265
|
-
} : RequiredKeys<SearchOf<Target>> extends never ? {
|
|
266
|
-
/** Optional-only search; absent or undefined optional values are omitted. */
|
|
267
|
-
search?: SearchInput<SearchOf<Target>, SearchKeys>;
|
|
268
309
|
} : {
|
|
269
|
-
/**
|
|
270
|
-
search
|
|
310
|
+
/** Any declared search values; absent or undefined values are omitted. */
|
|
311
|
+
search?: SearchInput<SearchInputOf<Target>, SearchKeys & keyof SearchInputOf<Target>>;
|
|
271
312
|
}) & (FragmentsOf<Target> extends never ? {
|
|
272
313
|
/** Forbidden when this declaration has no fragment names. */
|
|
273
314
|
fragment?: never;
|
|
@@ -278,35 +319,30 @@ type UrlValues<Target extends AnyRoute, SearchKeys extends keyof SearchOf<Target
|
|
|
278
319
|
type CompatibleKeys<Source, Value> = { [Key in keyof Source]: Source[Key] extends Value ? Key : never; }[keyof Source];
|
|
279
320
|
type Mapping<Source, Destination> = { [Key in keyof Destination]: CompatibleKeys<Source, Destination[Key]>; };
|
|
280
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;
|
|
281
|
-
type Preservable<Source, Destination> = { [Key in RequiredKeys<Destination>]: Key extends RequiredKeys<Source> ? Source[Key & keyof Source] extends Destination[Key] ? never : Key : Key; }[RequiredKeys<Destination>] extends never ? true : false;
|
|
282
322
|
/**
|
|
283
323
|
* A typed redirect to a genuine authored declaration, not a concrete URL.
|
|
284
324
|
*
|
|
285
325
|
* @remarks
|
|
286
326
|
* Params map destination keys to compatible effective source path keys. Omission
|
|
287
327
|
* forwards by name only when all target keys are compatible. Search and fragment
|
|
288
|
-
* policies are mandatory. Search preserve
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* 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.
|
|
293
332
|
*/
|
|
294
|
-
type Redirect<SourceParams,
|
|
333
|
+
type Redirect<SourceParams, Target extends AnyRoute, SearchKeys extends keyof SearchOf<Target>> = {
|
|
295
334
|
/** The already constructed target; generation requires a direct default import. */
|
|
296
335
|
to: Target;
|
|
297
336
|
/** Explicit query policy: preserve incoming text, drop it, or replace with values. */
|
|
298
337
|
search: {
|
|
299
338
|
/** Discard incoming queries and serialize explicit target-typed values. */
|
|
300
339
|
mode: "replace";
|
|
301
|
-
/**
|
|
302
|
-
values: SearchInput<
|
|
303
|
-
} |
|
|
304
|
-
/**
|
|
305
|
-
mode: "drop";
|
|
306
|
-
}
|
|
307
|
-
/** Keep original query text, validating destination-declared cardinality/values. */
|
|
308
|
-
mode: "preserve";
|
|
309
|
-
} : never);
|
|
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
|
+
};
|
|
310
346
|
/** Explicit hash policy; preserved incoming names need not be declared. */
|
|
311
347
|
fragment: {
|
|
312
348
|
/** Keep the raw incoming hash, or remove it entirely. */
|
|
@@ -331,8 +367,8 @@ type Redirect<SourceParams, SourceSearch, Target extends AnyRoute, SearchKeys ex
|
|
|
331
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>> = {
|
|
332
368
|
/** Genuine authored parent; pathless nodes retain inherited input ownership. */
|
|
333
369
|
parent?: Parent & (Parent extends Route<string, any, any, any, any, true> ? never : unknown);
|
|
334
|
-
/** Local search codecs;
|
|
335
|
-
search?: Search & Record<Extract<keyof Search, keyof SearchOf<Parent>>, never
|
|
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>>;
|
|
336
372
|
/** Local parsed-input resolvers; not snapshot-first native ResolveFn callbacks. */
|
|
337
373
|
resolve?: Resolvers & Record<PropertyKey, Resolver<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>> & {
|
|
338
374
|
__loozoAngularRouterRouteId?: never;
|
|
@@ -347,8 +383,8 @@ type RouteConfig<Path extends string, Parent extends AnyRoute | undefined, Param
|
|
|
347
383
|
canDeactivate?: Route$1["canDeactivate"];
|
|
348
384
|
/** Native route-scoped providers; provider factories are not run at declaration. */
|
|
349
385
|
providers?: Route$1["providers"];
|
|
350
|
-
/**
|
|
351
|
-
title?:
|
|
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>>;
|
|
352
388
|
/** Native static data; cannot set the reserved __loozoAngularRouterRouteId key. */
|
|
353
389
|
data?: Route$1["data"] & {
|
|
354
390
|
__loozoAngularRouterRouteId?: never;
|
|
@@ -358,14 +394,21 @@ type RouteConfig<Path extends string, Parent extends AnyRoute | undefined, Param
|
|
|
358
394
|
params?: never;
|
|
359
395
|
} : {
|
|
360
396
|
/** Exactly the local placeholder names; catchall codecs apply per element. */
|
|
361
|
-
params: Params & Record<PathNames<Path>, PathCodec> & Record<Exclude<keyof Params, PathNames<Path>> | Extract<keyof Params, keyof ParamsOf<Parent>>, never
|
|
397
|
+
params: Params & Record<PathNames<Path>, PathCodec> & Record<Exclude<keyof Params, PathNames<Path>> | Extract<keyof Params, keyof ParamsOf<Parent>>, never> & SyncCodecs<NoInfer<Params>>;
|
|
362
398
|
}) & (Target extends AnyRoute ? {
|
|
363
|
-
/** Typed native redirect, incompatible with own canActivate/canMatch fields. */
|
|
364
|
-
redirect: Redirect<EffectiveParams<Path, Params, Parent>,
|
|
399
|
+
/** Typed native redirect, incompatible with own canActivate/canMatch/onInvalidParams fields. */
|
|
400
|
+
redirect: Redirect<EffectiveParams<Path, Params, Parent>, Target, ReplacementKeys>;
|
|
365
401
|
canActivate?: never;
|
|
366
402
|
canMatch?: never;
|
|
403
|
+
onInvalidParams?: never;
|
|
367
404
|
} : {
|
|
368
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;
|
|
369
412
|
/** Activation guards with effective inherited and local parsed inputs. */
|
|
370
413
|
canActivate?: readonly ActivationGuard<EffectiveParams<Path, Params, Parent>, EffectiveSearch<Search, Parent>>[];
|
|
371
414
|
/** Matching guards with local path params and effective search only. */
|
|
@@ -383,7 +426,6 @@ interface RouteMetadata {
|
|
|
383
426
|
readonly config: Readonly<Record<string, unknown>>;
|
|
384
427
|
}
|
|
385
428
|
export declare function routeMetadata(route: unknown): RouteMetadata;
|
|
386
|
-
declare function validateSearchValues(codecs: Readonly<Record<string, ParamCodec<any>>>, values: Record<string, unknown>): void;
|
|
387
429
|
/**
|
|
388
430
|
* Declares a nominal typed route without executing codecs or native callbacks.
|
|
389
431
|
*
|
|
@@ -410,13 +452,14 @@ declare function validateSearchValues(codecs: Readonly<Record<string, ParamCodec
|
|
|
410
452
|
* generator-owned and cannot be overridden.
|
|
411
453
|
* @returns A frozen route preserving inferred effective input and local data types.
|
|
412
454
|
* @throws TypeError for invalid paths/configuration, missing or extra local
|
|
413
|
-
* codecs, inherited key conflicts, or
|
|
455
|
+
* codecs, fallback codecs in path params, inherited key conflicts, or
|
|
456
|
+
* nongenuine parent/redirect routes.
|
|
414
457
|
*
|
|
415
458
|
* @example
|
|
416
459
|
* ```ts
|
|
417
460
|
* const product = defineRoute("products/$productId", {
|
|
418
461
|
* params: { productId: numberParam },
|
|
419
|
-
* search: { tab:
|
|
462
|
+
* search: { tab: withFallback(enumParam(["details", "reviews"]), "details") },
|
|
420
463
|
* fragments: ["summary", "reviews"],
|
|
421
464
|
* });
|
|
422
465
|
* ```
|
|
@@ -427,15 +470,14 @@ export declare function defineRoute<const Path extends string, Parent extends An
|
|
|
427
470
|
*
|
|
428
471
|
* @remarks
|
|
429
472
|
* Includes inherited path/search declarations, but only this route's fragment
|
|
430
|
-
* names. Every effective path value
|
|
431
|
-
*
|
|
432
|
-
* 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.
|
|
433
475
|
* Values are never taken from a current navigation.
|
|
434
476
|
*
|
|
435
477
|
* Stringifies without calling parsers, guards, resolvers or redirects. Static
|
|
436
478
|
* segments and logical values are component-encoded (also escaping parentheses),
|
|
437
|
-
* so a dynamic slash becomes `%2F`. Search keys are globally sorted;
|
|
438
|
-
* `undefined` values are
|
|
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 `/`.
|
|
439
481
|
* Redirect declarations serialize their source URL, not their redirect target.
|
|
440
482
|
*
|
|
441
483
|
* @param route - A genuine declaration from this physical package instance,
|