@securitydept/client-angular 0.3.0-beta.3 → 0.3.0-beta.6

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.
@@ -1,554 +1,610 @@
1
- import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, inject, Injectable } from '@angular/core';
3
- import { assertResolveEnvironment } from '@securitydept/client/web';
4
- import { createPlannerHost, resolveEffectiveClientSet, RequirementsClientSetComposition } from '@securitydept/client/auth-coordination';
5
- export { resolveEffectiveClientSet } from '@securitydept/client/auth-coordination';
6
- import { Observable } from 'rxjs';
1
+ import { InjectionToken, makeEnvironmentProviders, inject, DestroyRef, Injector, Optional } from '@angular/core';
2
+ import { Router } from '@angular/router';
3
+ import { createSecuritydeptDestroyRef, SecuritydeptDestroyRef, SecuritydeptInjector, readSecuritydeptRouteMetadata, RouteCompositionRequirementPlanner, PipelineOutcome, injectRequirementPlannerHost, RequirementPlannerHost, ENVIRONMENT_TOKEN, writeSecuritydeptRouteMetadata, provideRequirementPlannerHost, UriReferenceStringSchema, validateTraitInput, throwValidationClientError, RouterNavigationMode, ClientError, ClientErrorKind, UriReferenceString, TransportErrorCode, ClientErrorSource, getSecuritydeptProviderToken, createProviderIfTokenMissing, ROUTER_TRAIT_TOKEN, TRANSPORT_TRAIT_TOKEN, notMissingProvider, SYMBOL_OBSERVABLE } from '@securitydept/client';
4
+ import { HttpErrorResponse, HttpClient } from '@angular/common/http';
5
+ import { createRouterForNativeWeb } from '@securitydept/client/web';
6
+ import { type } from 'arktype';
7
+ import { takeUntil, from, firstValueFrom } from 'rxjs';
8
+ import { toSignal, rxResource } from '@angular/core/rxjs-interop';
7
9
 
8
- const PAGE_CLIENT_ENVIRONMENT = new InjectionToken("PAGE_CLIENT_ENVIRONMENT", {
9
- providedIn: "root",
10
- factory: () => undefined,
11
- });
12
- function providePageClientEnvironment(options) {
13
- return {
14
- provide: PAGE_CLIENT_ENVIRONMENT,
15
- useValue: options.environment,
16
- };
17
- }
18
- function resolvePageClientEnvironmentSource(source, onMissing) {
19
- const resolvedSource = assertResolveEnvironment(source, onMissing);
20
- if (isPageClientEnvironmentResolver(resolvedSource)) {
21
- return Promise.resolve(resolvedSource()).then((environment) => assertResolveEnvironment(environment, onMissing));
22
- }
23
- if (isPageClientEnvironmentService(resolvedSource)) {
24
- return resolvedSource
25
- .resolvePageEnvironment()
26
- .then((environment) => assertResolveEnvironment(environment, onMissing));
27
- }
28
- return Promise.resolve(resolvedSource);
29
- }
30
- function isPageClientEnvironmentResolver(source) {
31
- return typeof source === "function";
32
- }
33
- function isPageClientEnvironmentService(source) {
34
- return (typeof source === "object" &&
35
- source !== null &&
36
- "resolvePageEnvironment" in source &&
37
- typeof source.resolvePageEnvironment === "function");
10
+ const SECURITYDEPT_INJECTOR = new InjectionToken("SECURITYDEPT_INJECTOR");
11
+ function provideSecuritydept(options = {}) {
12
+ return makeEnvironmentProviders([
13
+ {
14
+ provide: SECURITYDEPT_INJECTOR,
15
+ useFactory: () => {
16
+ const parentInjector = inject(SECURITYDEPT_INJECTOR, {
17
+ optional: true,
18
+ skipSelf: true,
19
+ }) ?? undefined;
20
+ const angularDestroyRef = inject(DestroyRef);
21
+ const securitydeptDestroyRef = options.autoCreateDestroyRef === false
22
+ ? null
23
+ : createSecuritydeptDestroyRef();
24
+ if (securitydeptDestroyRef) {
25
+ angularDestroyRef.onDestroy(() => securitydeptDestroyRef.dispose());
26
+ }
27
+ const providers = [
28
+ ...(options.providers ?? []),
29
+ ...(securitydeptDestroyRef
30
+ ? [
31
+ {
32
+ provide: SecuritydeptDestroyRef,
33
+ useValue: securitydeptDestroyRef,
34
+ },
35
+ ]
36
+ : []),
37
+ ];
38
+ return parentInjector
39
+ ? SecuritydeptInjector.fromParentInjector(parentInjector, providers)
40
+ : SecuritydeptInjector.resolveAndCreate(providers);
41
+ },
42
+ },
43
+ ]);
38
44
  }
39
45
 
40
- // Angular planner-host integration — DI-based planner provider / lookup
46
+ // Angular route metadata projection
41
47
  //
42
48
  // Canonical import path:
43
- // import { AUTH_PLANNER_HOST, provideAuthPlannerHost, ... } from "@securitydept/client-angular"
49
+ // import { projectAngularRouteSegments } from "@securitydept/client-angular"
50
+ //
51
+ // Reads serializable auth requirement metadata declared in Angular route
52
+ // `data` and projects a matched route chain (`pathFromRoot`) into the SDK's
53
+ // framework-agnostic `RouteTreeSegment[]`. Composition folding is owned by the
54
+ // `RouteCompositionRequirementPlanner`, so this layer only extracts per-segment
55
+ // declarations.
44
56
  //
45
- // Provides Angular DI glue for the shared planner-host contract:
46
- // - InjectionToken for PlannerHost
47
- // - Provider helpers for app / module / route scoped planners
48
- // - InjectionToken + provider helpers for ScopedRequirementsClientSet
49
- // - injectPlannerHost() convenience function with fail-fast
57
+ // Route metadata storage is owned by `@securitydept/client`
58
+ // ({@link readSecuritydeptRouteMetadata} / {@link writeSecuritydeptRouteMetadata}).
50
59
  //
51
60
  // Architecture boundary:
52
- // - Does NOT own the planner-host contract (that lives in @securitydept/client)
53
- // - Does NOT carry token-set-specific mapping
54
- // - Provides the Angular DI wiring for the shared contract
61
+ // - Does NOT own auth enforcement, planner logic, or navigation.
62
+ // - Does NOT carry token-set-specific mapping or policy.
63
+ // - Framework-specific (Angular Router types) but requirement-system-agnostic.
55
64
  //
56
65
  // Stability: provisional
57
- // ---------------------------------------------------------------------------
58
- // PlannerHost injection token
59
- // ---------------------------------------------------------------------------
60
66
  /**
61
- * DI token for the shared {@link PlannerHost}.
62
- *
63
- * Provide at the app level via {@link provideAuthPlannerHost}.
64
- * Guards use {@link injectPlannerHost} to look up the nearest instance.
67
+ * Resolved route `data` for a segment: static `routeConfig.data` merged with
68
+ * snapshot `data` (snapshot wins on key collision).
65
69
  */
66
- const AUTH_PLANNER_HOST = new InjectionToken("AUTH_PLANNER_HOST");
70
+ function routeDataFromSegment(segment) {
71
+ return {
72
+ ...(segment.routeConfig?.data ?? {}),
73
+ ...segment.data,
74
+ };
75
+ }
67
76
  /**
68
- * Provide an {@link AUTH_PLANNER_HOST} at the current injector level.
77
+ * Project the `pathFromRoot` of an `ActivatedRouteSnapshot` into the SDK's
78
+ * `RouteTreeSegment[]` chain (root -> leaf).
69
79
  *
70
- * Use in app config, route providers, or NgModule providers to establish
71
- * a planner-host scope. Child routes inherit the nearest parent's planner.
80
+ * Each segment carries its declared requirements and composition strategy;
81
+ * inherit/merge/replace folding is applied later by the
82
+ * `RouteCompositionRequirementPlanner`.
72
83
  *
73
- * @example
74
- * ```ts
75
- * // app.config.ts — app-level planner (default sequential strategy)
76
- * export const appConfig: ApplicationConfig = {
77
- * providers: [provideAuthPlannerHost()],
78
- * };
79
- *
80
- * // Feature route — override with custom async chooser strategy
81
- * {
82
- * path: "admin",
83
- * providers: [provideAuthPlannerHost({
84
- * selectCandidate: async (candidates) => showAdminChooser(candidates),
85
- * })],
86
- * children: [...]
87
- * }
88
- * ```
84
+ * Segments without a `routeConfig` (e.g. the root) get routeId `"__root__"`;
85
+ * empty-path segments get `"__index__"`.
89
86
  */
90
- function provideAuthPlannerHost(options) {
91
- const plannerOptions = {};
92
- if (options?.selectCandidate) {
93
- plannerOptions.selectCandidate = options.selectCandidate;
87
+ function projectAngularRouteSegments(leafRoute) {
88
+ const chain = leafRoute.pathFromRoot ?? [leafRoute];
89
+ return chain.map((segment) => segmentToTreeNode(segment));
90
+ }
91
+ function segmentToTreeNode(segment) {
92
+ const metadata = readSecuritydeptRouteMetadata(routeDataFromSegment(segment));
93
+ const requirements = metadata?.requirements ? [...metadata.requirements] : [];
94
+ const composition = metadata?.composition;
95
+ return composition
96
+ ? { routeId: resolveRouteId(segment), requirements, composition }
97
+ : { routeId: resolveRouteId(segment), requirements };
98
+ }
99
+ function resolveRouteId(segment) {
100
+ if (!segment.routeConfig) {
101
+ return "__root__";
94
102
  }
95
- return makeEnvironmentProviders([
96
- { provide: AUTH_PLANNER_HOST, useValue: createPlannerHost(plannerOptions) },
97
- ]);
103
+ const path = segment.routeConfig.path;
104
+ if (path === undefined || path === null || path === "") {
105
+ return "__index__";
106
+ }
107
+ return path;
98
108
  }
99
- /**
100
- * Inject the nearest {@link PlannerHost} from the DI hierarchy.
101
- *
102
- * Throws an explicit error if no planner-host is provided, preventing
103
- * silent fallback behavior.
104
- *
105
- * @example
106
- * ```ts
107
- * const plannerHost = injectPlannerHost();
108
- * const result = await plannerHost.evaluate(candidates);
109
- * ```
110
- */
111
- function injectPlannerHost() {
112
- const host = inject(AUTH_PLANNER_HOST, { optional: true });
113
- if (!host) {
114
- throw new Error("[injectPlannerHost] No AUTH_PLANNER_HOST found in the injector hierarchy. " +
115
- "Use provideAuthPlannerHost() in your app config or route providers.");
109
+
110
+ // Angular route guards — bridge to RouteCompositionRequirementPlanner
111
+ //
112
+ // Canonical import path:
113
+ // import {
114
+ // createAngularCanActivate,
115
+ // createAngularCanActivateChild,
116
+ // } from "@securitydept/client-angular"
117
+ //
118
+ // Produces Angular `CanActivateFn` / `CanActivateChildFn` guards that project
119
+ // the matched route chain into `RouteTreeSegment[]`, run the SDK's
120
+ // `RouteCompositionRequirementPlanner` against the nearest
121
+ // `RequirementPlannerHost` resolved from the current Securitydept injector,
122
+ // and map the pipeline result onto Angular's guard return contract (`true` /
123
+ // `false` / `UrlTree`).
124
+ //
125
+ // Injection-context discipline: DI reads (host, Router) happen synchronously at
126
+ // guard entry, before any `await`. Behaviour that needs DI services should be
127
+ // provided through `provideSecuritydept({ providers: [...] })`, not inline
128
+ // callbacks that call `inject()` here.
129
+ //
130
+ // Architecture boundary:
131
+ // - Does NOT own behaviour (checkAuthenticated / onUnauthenticated) — that is
132
+ // resolved through the RequirementPlannerHost chain.
133
+ // - Does NOT carry token-set-specific mapping or policy.
134
+ //
135
+ // Stability: provisional
136
+ async function runRouteGuard(route, state, host, router) {
137
+ const segments = projectAngularRouteSegments(route);
138
+ const planner = RouteCompositionRequirementPlanner.fromRouteSegments(host, segments, { url: state.url });
139
+ const result = await planner.runUntilSettled();
140
+ switch (result.outcome) {
141
+ case PipelineOutcome.Settled:
142
+ return true;
143
+ case PipelineOutcome.Blocked:
144
+ return false;
145
+ case PipelineOutcome.Redirect:
146
+ return router.parseUrl(result.location);
116
147
  }
117
- return host;
118
148
  }
119
- // ---------------------------------------------------------------------------
120
- // Requirements client set injection token — stores the already-resolved
121
- // effective options for the current scope (parent + child composed).
122
- // ---------------------------------------------------------------------------
123
149
  /**
124
- * DI token for the effective requirements client set at the current scope.
125
- *
126
- * The value is always the **resolved** options after composing all ancestor
127
- * scopes — it is NOT the raw `ScopedRequirementsClientSet` declaration.
128
- *
129
- * Provide via {@link provideRouteScopedRequirements}.
130
- * Guards inject this token to get the effective scope requirements and then
131
- * merge their own declared candidates on top.
150
+ * Create a `CanActivateFn` bridging the route chain to the requirement
151
+ * planner pipeline.
132
152
  */
133
- const AUTH_REQUIREMENTS_CLIENT_SET = new InjectionToken("AUTH_REQUIREMENTS_CLIENT_SET");
153
+ function createAngularCanActivate(options) {
154
+ return (route, state) => {
155
+ const securitydeptInjector = inject(SECURITYDEPT_INJECTOR, {
156
+ optional: true,
157
+ });
158
+ const parentHost = securitydeptInjector
159
+ ? injectRequirementPlannerHost({ injector: securitydeptInjector })
160
+ : null;
161
+ const host = options?.plannerHost ??
162
+ (options?.behaviour
163
+ ? RequirementPlannerHost.fromBehaviour(typeof options.behaviour === "function"
164
+ ? options.behaviour()
165
+ : options.behaviour, {
166
+ parent: parentHost ?? undefined,
167
+ environment: securitydeptInjector?.get(ENVIRONMENT_TOKEN, null) ?? undefined,
168
+ })
169
+ : parentHost) ??
170
+ RequirementPlannerHost.fromBehaviour({});
171
+ const router = inject(Router);
172
+ return runRouteGuard(route, state, host, router);
173
+ };
174
+ }
134
175
  /**
135
- * Provide a {@link ScopedRequirementsClientSet} at the current route/module scope.
136
- *
137
- * Internally, the provider resolves the effective client set by composing the
138
- * declared `scopedSet` with the parent scope's already-resolved options
139
- * (injected via `skipSelf: true`). The token value stored in DI is the
140
- * resolved `readonly AuthGuardClientOption[]`, not the raw declaration.
141
- *
142
- * This mirrors the React `AuthRequirementsClientSetProvider` semantics:
143
- * each level stores its resolved effective options, so children inherit
144
- * the composed result without re-resolving the full ancestor chain.
145
- *
146
- * The `scopedSet` argument may also be a **factory function** — it will be
147
- * called inside Angular's `useFactory` context, so `inject()` is valid.
148
- * Use this when the requirement options need DI-resolved services (e.g. a
149
- * token-set registry or a redirect service).
150
- *
151
- * @example
152
- * ```ts
153
- * // routes.ts — static declaration (no DI needed)
154
- * {
155
- * path: "",
156
- * providers: [
157
- * provideRouteScopedRequirements({
158
- * composition: RequirementsClientSetComposition.Replace,
159
- * options: [sessionClientOption],
160
- * }),
161
- * ],
162
- * }
163
- *
164
- * // Feature route merges OIDC on top of the parent's session requirement
165
- * {
166
- * path: "confluence",
167
- * providers: [
168
- * provideRouteScopedRequirements({
169
- * composition: RequirementsClientSetComposition.Merge,
170
- * options: [confluenceOidcClientOption],
171
- * }),
172
- * ],
173
- * // Route-level DI scope only. Angular Router adopters should prefer
174
- * // secureRouteRoot()/secureRoute() over direct guard wiring.
175
- * }
176
- *
177
- * // Factory form — use when options depend on injected services
178
- * {
179
- * path: "protected",
180
- * providers: [
181
- * provideRouteScopedRequirements(() => {
182
- * const registry = inject(TokenSetAuthRegistry);
183
- * const authService = inject(AuthService);
184
- * return {
185
- * composition: RequirementsClientSetComposition.Replace,
186
- * options: [{
187
- * requirementId: "oidc",
188
- * requirementKind: "frontend_oidc",
189
- * checkAuthenticated: () => registry.require(clientKey).isAuthenticated(),
190
- * onUnauthenticated: () => { authService.redirectToLogin(clientKey).subscribe(); return false; },
191
- * }],
192
- * };
193
- * }),
194
- * ],
195
- * }
196
- * ```
176
+ * Create a `CanActivateChildFn` bridging the child route chain to the
177
+ * requirement planner pipeline.
197
178
  */
198
- function provideRouteScopedRequirements(scopedSet) {
199
- return makeEnvironmentProviders([
200
- {
201
- provide: AUTH_REQUIREMENTS_CLIENT_SET,
202
- useFactory: () => {
203
- // Walk up to the nearest parent scope's already-resolved effective set.
204
- // skipSelf: true ensures we don't read our own (not-yet-set) value.
205
- const parentOptions = inject(AUTH_REQUIREMENTS_CLIENT_SET, {
206
- optional: true,
207
- skipSelf: true,
208
- }) ?? [];
209
- // Support factory form so callers can use inject() to resolve
210
- // DI services (e.g. auth registry, redirect service) inside their
211
- // option callbacks without bypassing this helper.
212
- const resolved = typeof scopedSet === "function" ? scopedSet() : scopedSet;
213
- return resolveEffectiveClientSet(parentOptions, resolved);
214
- },
215
- },
216
- ]);
179
+ function createAngularCanActivateChild(options) {
180
+ return (childRoute, state) => {
181
+ const injector = inject(SECURITYDEPT_INJECTOR, {
182
+ optional: true,
183
+ });
184
+ const parentHost = injector
185
+ ? injectRequirementPlannerHost({ injector })
186
+ : null;
187
+ const host = options?.plannerHost ??
188
+ (options?.behaviour
189
+ ? RequirementPlannerHost.fromBehaviour(typeof options.behaviour === "function"
190
+ ? options.behaviour()
191
+ : options.behaviour, {
192
+ parent: parentHost ?? undefined,
193
+ environment: injector?.get(ENVIRONMENT_TOKEN, null) ?? undefined,
194
+ })
195
+ : parentHost) ??
196
+ RequirementPlannerHost.fromBehaviour({});
197
+ const router = inject(Router);
198
+ return runRouteGuard(childRoute, state, host, router);
199
+ };
217
200
  }
218
201
 
219
- // Angular Router route adapter for auth-coordination
202
+ // Angular secure route helpers — declarative route assembly
220
203
  //
221
204
  // Canonical import path:
222
- // import { ... } from "@securitydept/client-angular"
205
+ // import { secureRoute, secureRouteRoot } from "@securitydept/client-angular"
206
+ //
207
+ // Two composable helpers for wiring auth requirements onto Angular routes:
208
+ // - secureRoute: writes requirement metadata into route `data` ONLY.
209
+ // - secureRouteRoot: writes metadata AND attaches the canActivate /
210
+ // canActivateChild guards (and, optionally, a scoped
211
+ // RequirementPlannerHost provider).
223
212
  //
224
- // Provides the projection layer between Angular Router's ActivatedRouteSnapshot
225
- // tree and the SDK's framework-agnostic RouteMatchNode[] contract.
226
- // Uses real @angular/router types (ActivatedRouteSnapshot) so consuming apps
227
- // get full type-safety and IDE autocomplete without any casting.
213
+ // The split lets a single guarded root route cover a whole subtree: child
214
+ // segments declare their own requirements via `secureRoute`, and the root's
215
+ // canActivateChild folds the full chain through the planner.
228
216
  //
229
217
  // Architecture boundary:
230
- // - Does NOT own the Angular router, guards, or navigation lifecycle.
231
- // - Does NOT re-export core orchestration primitives.
232
- // - Does NOT carry token-set-specific mapping or policy.
233
- // - The adopter wires this into their guards / resolvers.
218
+ // - Does NOT own behaviour or token-set-specific policy.
219
+ // - Delegates enforcement to createAngularCanActivate/Child.
234
220
  //
235
221
  // Stability: provisional
236
- /** Default key for auth requirements in Angular route `data`. */
237
- const DEFAULT_ROUTE_REQUIREMENTS_KEY = "authRequirements";
238
- // ---------------------------------------------------------------------------
239
- // Service
240
- // ---------------------------------------------------------------------------
241
- /**
242
- * Angular service that bridges Angular Router's `ActivatedRouteSnapshot` to
243
- * the SDK's framework-agnostic `RouteMatchNode[]` contract.
244
- *
245
- * Inject this into `CanActivateFn` guards or class-based guards.
246
- *
247
- * @example
248
- * ```ts
249
- * export const authGuard: CanActivateFn = (route) => {
250
- * const adapter = inject(AuthRouteAdapter);
251
- * const result = adapter.check(route, orchestrator);
252
- * if (!result.canActivate && result.pendingRequirement) {
253
- * // Trigger auth flow for the pending requirement
254
- * }
255
- * return result.canActivate;
256
- * };
257
- * ```
258
- */
259
- class AuthRouteAdapter {
260
- /**
261
- * Project the `pathFromRoot` of an `ActivatedRouteSnapshot` into the SDK's
262
- * `RouteMatchNode[]` chain.
263
- *
264
- * Route segments without a `routeConfig` (e.g. the root segment) are
265
- * assigned routeId `"__root__"`. Segments with a config use the `path`
266
- * as their routeId (or `"__index__"` for empty-path segments).
267
- */
268
- projectRouteMatch(leafRoute, options) {
269
- const key = options?.requirementsKey ?? DEFAULT_ROUTE_REQUIREMENTS_KEY;
270
- const chain = leafRoute.pathFromRoot ?? [leafRoute];
271
- return chain.map((segment) => this.segmentToNode(segment, key));
222
+ function writeRequirementData(security, base) {
223
+ const patch = {};
224
+ if (security.requirements !== undefined) {
225
+ patch.requirements = security.requirements;
272
226
  }
273
- /**
274
- * Project an explicit array of `ActivatedRouteSnapshot` segments into the
275
- * SDK's `RouteMatchNode[]` chain.
276
- *
277
- * Use when you already have a flattened chain (e.g. from
278
- * `ActivatedRoute.pathFromRoot`).
279
- */
280
- projectRouteSegments(segments, options) {
281
- const key = options?.requirementsKey ?? DEFAULT_ROUTE_REQUIREMENTS_KEY;
282
- return segments.map((segment) => this.segmentToNode(segment, key));
227
+ if (security.composition !== undefined) {
228
+ patch.composition = security.composition;
283
229
  }
284
- /**
285
- * Project the route snapshot and check whether all requirements are settled.
286
- *
287
- * @param leafRoute - The leaf `ActivatedRouteSnapshot` from the guard call.
288
- * @param orchestrator - The SDK orchestrator managing the requirement plan.
289
- * @param options - Optional projection options.
290
- */
291
- check(leafRoute, orchestrator, options) {
292
- const chain = this.projectRouteMatch(leafRoute, options);
293
- orchestrator.activateMatchedRoutes(chain);
294
- const snap = orchestrator.snapshot();
295
- return {
296
- canActivate: snap.settled,
297
- pendingRequirement: snap.pendingRequirement,
298
- };
230
+ if (Object.keys(patch).length === 0) {
231
+ return { ...(base ?? {}) };
299
232
  }
300
- segmentToNode(segment, key) {
301
- const routeId = this.resolveRouteId(segment);
302
- const rawRequirements = segment.data[key] ?? segment.routeConfig?.data?.[key];
303
- const requirements = Array.isArray(rawRequirements)
304
- ? rawRequirements
305
- : [];
306
- return { routeId, requirements };
307
- }
308
- resolveRouteId(segment) {
309
- if (!segment.routeConfig)
310
- return "__root__";
311
- const path = segment.routeConfig.path;
312
- if (path === undefined || path === null || path === "")
313
- return "__index__";
314
- return path;
233
+ return writeSecuritydeptRouteMetadata(base, patch);
234
+ }
235
+ /**
236
+ * Declare a secure route segment: writes requirement metadata into route
237
+ * `data` only. No guard is attached — a guarded ancestor (see
238
+ * {@link secureRouteRoot}) enforces the folded chain.
239
+ */
240
+ function secureRoute(path, security, routeOptions) {
241
+ return {
242
+ ...routeOptions,
243
+ path,
244
+ data: writeRequirementData(security, routeOptions?.data),
245
+ };
246
+ }
247
+ /**
248
+ * Declare a guarded root route: writes requirement metadata AND attaches
249
+ * `canActivate` + `canActivateChild` guards bound to the requirement planner.
250
+ *
251
+ * When `security.behaviour` is set, a scoped {@link RequirementPlannerHost}
252
+ * provider is mounted so descendant guards inherit it through DI.
253
+ */
254
+ function secureRouteRoot(path, security, routeOptions) {
255
+ const guardOptions = security.plannerHost
256
+ ? { plannerHost: security.plannerHost }
257
+ : undefined;
258
+ const providers = security.behaviour
259
+ ? [
260
+ ...(routeOptions?.providers ?? []),
261
+ provideSecuritydept({
262
+ providers: [
263
+ provideRequirementPlannerHost(security.behaviour),
264
+ ],
265
+ }),
266
+ ]
267
+ : routeOptions?.providers;
268
+ const route = {
269
+ ...routeOptions,
270
+ path,
271
+ data: writeRequirementData(security, routeOptions?.data),
272
+ canActivate: [
273
+ ...(routeOptions?.canActivate ?? []),
274
+ createAngularCanActivate(guardOptions),
275
+ ],
276
+ canActivateChild: [
277
+ ...(routeOptions?.canActivateChild ?? []),
278
+ createAngularCanActivateChild(guardOptions),
279
+ ],
280
+ };
281
+ if (providers) {
282
+ route.providers = providers;
315
283
  }
316
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthRouteAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
317
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthRouteAdapter, providedIn: "root" });
284
+ return route;
318
285
  }
319
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthRouteAdapter, decorators: [{
320
- type: Injectable,
321
- args: [{ providedIn: "root" }]
322
- }] });
323
286
 
324
- // Angular route metadata requirement helper
287
+ // Angular Router trait adapter
325
288
  //
326
289
  // Canonical import path:
327
- // import { withRouteRequirements, extractFullRouteRequirements, ... } from "@securitydept/client-angular"
290
+ // import { createRouterForAngular } from "@securitydept/client-angular"
328
291
  //
329
- // Provides the canonical route-metadata layer for Angular Router auth requirement
330
- // declaration. Requirements are stored in route `data` rather than in DI scope,
331
- // enabling full-route aggregation guards to collect the complete requirement set
332
- // from the entire path-from-root chain in a single pass.
292
+ // Projects an Angular `Router` (or any navigation-capable object) onto the
293
+ // framework-agnostic `RouterTrait` (currentUrl + navigate) from
294
+ // `@securitydept/client`. Adopters use this to give SDK primitives a way to
295
+ // read the current URL and start navigations without depending on Angular.
333
296
  //
334
297
  // Architecture boundary:
335
- // - Does NOT own auth enforcement or planner logic.
298
+ // - Does NOT own the Angular router, guards, or navigation lifecycle.
299
+ // - Does NOT read auth requirements or run planners (see route-guard.ts).
336
300
  // - Does NOT carry token-set-specific mapping or policy.
337
- // - Provides typed helpers for declaring and reading requirements from route data.
338
- // - framework-specific (Angular Router types) but requirement-system-agnostic.
339
- //
340
- // Relationship to provideRouteScopedRequirements():
341
- // - provideRouteScopedRequirements() is a lower-level DI-scope helper.
342
- // - This module is the canonical path for Angular Router auth requirement
343
- // declaration when full-route aggregation is needed.
344
- //
345
- // Key sentinel note:
346
- // DEFAULT_ROUTE_REQUIREMENTS_KEY is defined in router.ts (AuthRouteAdapter's
347
- // companion constant). ROUTE_REQUIREMENTS_DATA_KEY exported here is its
348
- // semantic alias — both hold the same value ("authRequirements").
349
- // Adopters may use either name; they are interchangeable.
350
301
  //
351
302
  // Stability: provisional
352
- // ---------------------------------------------------------------------------
353
- // Route data key
354
- // ---------------------------------------------------------------------------
355
- /**
356
- * Default key for auth requirements in Angular route `data`.
357
- *
358
- * Semantic alias for {@link DEFAULT_ROUTE_REQUIREMENTS_KEY} (defined in
359
- * `router.ts`). Both hold the same value (`"authRequirements"`).
360
- * Use either name — they are interchangeable.
361
- *
362
- * Set this key in `Route.data` to declare auth requirements for a route segment:
363
- * ```ts
364
- * { path: "dashboard", data: withRouteRequirements([{ id: "session", kind: "session" }]) }
365
- * ```
366
- *
367
- * The key is also read by {@link AuthRouteAdapter} when projecting snapshots.
368
- *
369
- * @see {@link withRouteRequirements}
370
- * @see {@link extractFullRouteRequirements}
371
- */
372
- const ROUTE_REQUIREMENTS_DATA_KEY = DEFAULT_ROUTE_REQUIREMENTS_KEY;
373
- /**
374
- * Default key for the route-level composition strategy in Angular route `data`.
375
- *
376
- * Used together with {@link ROUTE_REQUIREMENTS_DATA_KEY}. The default
377
- * composition is `merge` when the key is omitted.
378
- */
379
- const ROUTE_REQUIREMENTS_COMPOSITION_DATA_KEY = "authRequirementsComposition";
380
- // ---------------------------------------------------------------------------
381
- // Declaration helper
382
- // ---------------------------------------------------------------------------
303
+ const AngularRouterNavigationLikeSchema = type({
304
+ url: "string?",
305
+ navigateByUrl: "Function",
306
+ });
307
+ const ResolvedRouterForAngularCreateOptionsSchema = type({
308
+ router: AngularRouterNavigationLikeSchema,
309
+ currentUrl: UriReferenceStringSchema.or("null"),
310
+ });
383
311
  /**
384
- * Build an Angular route `data` object declaring auth requirements for a
385
- * single route segment.
386
- *
387
- * Use this as the `data` field in an Angular `Route` config. Nested routes
388
- * can declare their own requirements; a full-route aggregation guard will
389
- * walk the entire `pathFromRoot` chain to collect all requirements in one pass.
312
+ * Create a {@link RouterTrait} backed by an Angular `Router`.
390
313
  *
391
314
  * @example
392
315
  * ```ts
393
- * // Parent route declares base requirement
394
- * {
395
- * path: "app",
396
- * data: withRouteRequirements([{ id: "session", kind: "session" }]),
397
- * children: [
398
- * // Child route adds an additional requirement
399
- * {
400
- * path: "admin",
401
- * data: withRouteRequirements([{ id: "admin-oidc", kind: "backend_oidc" }]),
402
- * canActivate: [createTokenSetRouteAggregationGuard(...)],
403
- * },
404
- * ],
405
- * }
316
+ * const routerTrait = createRouterForAngular({
317
+ * router: inject(Router),
318
+ * location: window.location,
319
+ * });
406
320
  * ```
407
- *
408
- * @param requirements - One or more auth requirements for this route segment.
409
- * @param extra - Optional additional data properties to merge.
410
321
  */
411
- function withRouteRequirements(requirements, optionsOrExtra, maybeExtra) {
412
- const { composition, extra } = normalizeRequirementOptions(optionsOrExtra, maybeExtra);
322
+ function createRouterForAngular(options) {
323
+ const routerInput = options.router;
324
+ const resolvedCreateOptions = {
325
+ router: options.router,
326
+ get currentUrl() {
327
+ return Object.hasOwn(options, "currentUrl")
328
+ ? (options.currentUrl ?? null)
329
+ : (routerInput?.url ?? null);
330
+ },
331
+ };
332
+ validateTraitInput({
333
+ value: resolvedCreateOptions,
334
+ bundledSchema: ResolvedRouterForAngularCreateOptionsSchema,
335
+ validator: options.validators?.router,
336
+ onInvalid: (failure) => throwValidationClientError({
337
+ code: "client_angular.router.invalid_router_options",
338
+ source: "client-angular",
339
+ messagePrefix: "createRouterForAngular could not validate routerForAngularCreateOptions",
340
+ failure,
341
+ }),
342
+ });
343
+ const { router } = resolvedCreateOptions;
344
+ const nativeWebRouter = createRouterForNativeWeb(projectNativeWebRouterCreateOptions(options));
413
345
  return {
414
- ...extra,
415
- [ROUTE_REQUIREMENTS_DATA_KEY]: requirements,
416
- [ROUTE_REQUIREMENTS_COMPOSITION_DATA_KEY]: composition,
346
+ currentUrl() {
347
+ const currentUrl = resolvedCreateOptions.currentUrl;
348
+ return currentUrl == null ? null : UriReferenceString.parse(currentUrl);
349
+ },
350
+ async navigate(request) {
351
+ if (request.mode === RouterNavigationMode.External) {
352
+ if (!nativeWebRouter) {
353
+ throw new ClientError({
354
+ kind: ClientErrorKind.Configuration,
355
+ code: "client_angular.router.native_web_router_unavailable",
356
+ message: "Angular router cannot perform external navigation without a native web router",
357
+ source: "client-angular.router",
358
+ });
359
+ }
360
+ await nativeWebRouter.navigate(request);
361
+ return;
362
+ }
363
+ await router.navigateByUrl(request.url.toString(), {
364
+ replaceUrl: request.mode === RouterNavigationMode.Replace,
365
+ state: request.state,
366
+ });
367
+ },
417
368
  };
418
369
  }
419
- // ---------------------------------------------------------------------------
420
- // Aggregation helper
421
- // ---------------------------------------------------------------------------
422
- /**
423
- * Extract and flatten auth requirements from a complete Angular route chain.
424
- *
425
- * Walks all segments from root to leaf (from `leafRoute.pathFromRoot`)
426
- * and concatenates the declared {@link AuthRequirement} arrays in order.
427
- * Requirements from ancestor routes appear before requirements from
428
- * descendant routes. Duplicate `id` values are NOT de-duplicated here —
429
- * the caller (guard / enforcement layer) is responsible for deduplication
430
- * or merge logic.
431
- *
432
- * This is the extraction counter-part to {@link withRouteRequirements}.
433
- * A full-route aggregation guard calls this once and hands the complete
434
- * flat array to the planner / enforcement layer.
435
- *
436
- * @param leafRoute - The leaf `ActivatedRouteSnapshot` from the guard call.
437
- * @param requirementsKey - Override the route data key (default: `"authRequirements"`).
438
- */
439
- function extractFullRouteRequirements(leafRoute, requirementsKey = ROUTE_REQUIREMENTS_DATA_KEY, compositionKey = ROUTE_REQUIREMENTS_COMPOSITION_DATA_KEY) {
440
- const chain = leafRoute.pathFromRoot ?? [leafRoute];
441
- let result = [];
442
- for (const segment of chain) {
443
- const raw = segment.data[requirementsKey] ??
444
- segment.routeConfig?.data?.[requirementsKey];
445
- const composition = readRouteComposition(segment, compositionKey) ??
446
- RequirementsClientSetComposition.Merge;
447
- const requirements = Array.isArray(raw) ? raw : [];
448
- result = resolveEffectiveRequirements(result, {
449
- composition,
450
- requirements,
451
- });
452
- }
453
- return result;
370
+ function projectNativeWebRouterCreateOptions(options) {
371
+ return {
372
+ ...(Object.hasOwn(options, "navigation")
373
+ ? { navigation: options.navigation }
374
+ : {}),
375
+ ...(Object.hasOwn(options, "location")
376
+ ? { location: options.location }
377
+ : {}),
378
+ ...(Object.hasOwn(options, "history") ? { history: options.history } : {}),
379
+ ...(Object.hasOwn(options, "window") ? { window: options.window } : {}),
380
+ };
454
381
  }
455
- /**
456
- * Resolve the effective auth requirements by composing the parent chain with a
457
- * child route declaration.
458
- *
459
- * Composition semantics mirror `resolveEffectiveClientSet()`:
460
- * - `inherit` keeps the parent requirements unchanged
461
- * - `merge` appends child requirements and replaces same-id parent entries
462
- * - `replace` discards the parent chain and uses only the child's requirements
463
- */
464
- function resolveEffectiveRequirements(parentRequirements, child) {
465
- switch (child.composition) {
466
- case RequirementsClientSetComposition.Inherit:
467
- return [...parentRequirements];
468
- case RequirementsClientSetComposition.Merge: {
469
- const merged = new Map();
470
- for (const requirement of parentRequirements) {
471
- merged.set(requirement.id, requirement);
382
+
383
+ const BaseTransportForAngularCreateOptionsSchema = type({
384
+ httpClient: {
385
+ request: "Function",
386
+ },
387
+ baseUrl: "unknown",
388
+ });
389
+ function createBaseTransportForAngular(options) {
390
+ const resolvedCreateOptions = {
391
+ httpClient: options.httpClient,
392
+ baseUrl: options.baseUrl,
393
+ };
394
+ validateTraitInput({
395
+ value: resolvedCreateOptions,
396
+ bundledSchema: BaseTransportForAngularCreateOptionsSchema,
397
+ validator: options.validators,
398
+ onInvalid: (failure) => throwValidationClientError({
399
+ code: TransportErrorCode.InvalidAngularOptions,
400
+ source: "client-angular",
401
+ messagePrefix: "createBaseTransportForAngular could not validate transportForAngularCreateOptions",
402
+ failure,
403
+ }),
404
+ });
405
+ const { httpClient, baseUrl } = resolvedCreateOptions;
406
+ return {
407
+ async execute(request) {
408
+ const requestUrl = baseUrl
409
+ ? new URL(request.url, baseUrl).toString()
410
+ : request.url;
411
+ const requestOptions = {
412
+ headers: request.headers,
413
+ observe: "response",
414
+ responseType: "text",
415
+ ...(request.body !== undefined &&
416
+ request.method !== "GET" &&
417
+ request.method !== "HEAD"
418
+ ? {
419
+ body: typeof request.body === "string"
420
+ ? request.body
421
+ : JSON.stringify(request.body),
422
+ }
423
+ : {}),
424
+ };
425
+ try {
426
+ const responseSource = request.cancellationToken
427
+ ? httpClient
428
+ .request(request.method, requestUrl, requestOptions)
429
+ .pipe(takeUntil(from(request.cancellationToken)))
430
+ : httpClient.request(request.method, requestUrl, requestOptions);
431
+ const response = await firstValueFrom(responseSource);
432
+ try {
433
+ return angularResponseToHttpResponse(response);
434
+ }
435
+ catch (error) {
436
+ throw new ClientError({
437
+ kind: ClientErrorKind.Protocol,
438
+ message: "The HTTP response body could not be decoded",
439
+ code: TransportErrorCode.ResponseDecodeFailed,
440
+ source: ClientErrorSource.Transport,
441
+ cause: error,
442
+ });
443
+ }
472
444
  }
473
- for (const requirement of child.requirements) {
474
- merged.set(requirement.id, requirement);
445
+ catch (error) {
446
+ if (error instanceof HttpErrorResponse && error.status !== 0) {
447
+ try {
448
+ return angularErrorResponseToHttpResponse(error);
449
+ }
450
+ catch (decodeError) {
451
+ throw new ClientError({
452
+ kind: ClientErrorKind.Protocol,
453
+ message: "The HTTP response body could not be decoded",
454
+ code: TransportErrorCode.ResponseDecodeFailed,
455
+ source: ClientErrorSource.Transport,
456
+ cause: decodeError,
457
+ });
458
+ }
459
+ }
460
+ const normalized = normalizeAngularTransportError(request.cancellationToken, error);
461
+ if (normalized instanceof ClientError) {
462
+ throw normalized;
463
+ }
464
+ throw new ClientError({
465
+ kind: ClientErrorKind.Transport,
466
+ message: "The HTTP request could not reach the remote service",
467
+ code: TransportErrorCode.RequestFailed,
468
+ source: ClientErrorSource.Transport,
469
+ retryable: true,
470
+ cause: error,
471
+ });
475
472
  }
476
- return [...merged.values()];
477
- }
478
- case RequirementsClientSetComposition.Replace:
479
- return [...child.requirements];
480
- default: {
481
- const _exhaustive = child.composition;
482
- throw new Error(`[resolveEffectiveRequirements] Unknown composition: ${_exhaustive}`);
483
- }
484
- }
473
+ },
474
+ };
485
475
  }
486
- function normalizeRequirementOptions(optionsOrExtra, maybeExtra) {
487
- if (optionsOrExtra &&
488
- ("composition" in optionsOrExtra || "extra" in optionsOrExtra)) {
489
- const options = optionsOrExtra;
490
- return {
491
- composition: options.composition ?? RequirementsClientSetComposition.Merge,
492
- extra: maybeExtra ?? options.extra,
493
- };
494
- }
476
+ function angularResponseToHttpResponse(response) {
477
+ return {
478
+ status: response.status,
479
+ headers: angularHeadersToRecord(response.headers),
480
+ body: parseAngularResponseBody(response.status, response.headers, response.body),
481
+ };
482
+ }
483
+ function angularErrorResponseToHttpResponse(error) {
495
484
  return {
496
- composition: RequirementsClientSetComposition.Merge,
497
- extra: optionsOrExtra,
485
+ status: error.status,
486
+ headers: angularHeadersToRecord(error.headers),
487
+ body: parseAngularResponseBody(error.status, error.headers, error.error),
498
488
  };
499
489
  }
500
- function readRouteComposition(segment, compositionKey) {
501
- const raw = segment.data[compositionKey] ?? segment.routeConfig?.data?.[compositionKey];
502
- if (raw === RequirementsClientSetComposition.Inherit ||
503
- raw === RequirementsClientSetComposition.Merge ||
504
- raw === RequirementsClientSetComposition.Replace) {
505
- return raw;
490
+ function angularHeadersToRecord(headers) {
491
+ const responseHeaders = {};
492
+ for (const key of headers.keys()) {
493
+ const value = headers.get(key);
494
+ if (value !== null) {
495
+ responseHeaders[key] = value;
496
+ }
497
+ }
498
+ return responseHeaders;
499
+ }
500
+ function parseAngularResponseBody(status, headers, body) {
501
+ const contentType = headers.get("content-type");
502
+ if (contentType?.includes("application/json") && typeof body === "string") {
503
+ return body.length > 0 ? JSON.parse(body) : undefined;
504
+ }
505
+ if (status !== 302 && status !== 303 && status !== 301) {
506
+ return body;
506
507
  }
507
508
  return undefined;
508
509
  }
510
+ function normalizeAngularTransportError(token, error) {
511
+ if (!token?.isCancellationRequested) {
512
+ return error;
513
+ }
514
+ return token.cancellationError ?? error;
515
+ }
509
516
 
510
- // Angular signal / Observable bridge utilities
511
- //
512
- // Canonical import path:
513
- // import { bridgeToAngularSignal, signalToObservable } from "@securitydept/client-angular"
514
- //
515
- // These utilities convert SDK-native ReadableSignalTrait values to Angular-native
516
- // primitives (WritableSignal, Observable) so that framework adapter packages
517
- // can surface reactive state using idiomatic Angular APIs.
518
- //
519
- // Owner: @securitydept/client-angular — these are generic framework bridges,
520
- // not token-set-specific utilities. All Angular adapters (basic-auth, session,
521
- // token-set, …) should import from here instead of implementing their own.
522
- //
523
- // Stability: provisional (framework adapter)
524
- /**
525
- * Bridge an SDK `ReadableSignalTrait` to an Angular writable signal.
526
- *
527
- * Immediately syncs the current value, then subscribes to future changes.
528
- *
529
- * @returns Cleanup function that unsubscribes from the SDK signal.
530
- */
531
- function bridgeToAngularSignal(source, target) {
532
- target.set(source.get());
533
- return source.subscribe(() => {
534
- target.set(source.get());
517
+ const ENVIRONMENT = new InjectionToken("ENVIRONMENT");
518
+ const ENVIRONMENT_PROVIDER = new InjectionToken("ENVIRONMENT_PROVIDER");
519
+ function createEnvironmentForAngular(options) {
520
+ const { createBaseEnvironment, routerForAngularCreateOptions, transportForAngularCreateOptions, injectorForAngularProvider, providers = [], ...baseOptions } = options;
521
+ const externalProviderTokens = new Set(providers.map((provider) => getSecuritydeptProviderToken(provider)));
522
+ const angularProviders = [
523
+ createProviderIfTokenMissing(externalProviderTokens, ROUTER_TRAIT_TOKEN, () => ({
524
+ provide: ROUTER_TRAIT_TOKEN,
525
+ useValue: createRouterForAngular(routerForAngularCreateOptions),
526
+ })),
527
+ createProviderIfTokenMissing(externalProviderTokens, TRANSPORT_TRAIT_TOKEN, () => ({
528
+ provide: TRANSPORT_TRAIT_TOKEN,
529
+ useValue: createBaseTransportForAngular(transportForAngularCreateOptions),
530
+ })),
531
+ injectorForAngularProvider
532
+ ? createProviderIfTokenMissing(externalProviderTokens, Injector, () => ({
533
+ provide: Injector,
534
+ useValue: injectorForAngularProvider,
535
+ }))
536
+ : null,
537
+ ].filter(notMissingProvider);
538
+ return createBaseEnvironment({
539
+ ...baseOptions,
540
+ providers: [...angularProviders, ...providers],
535
541
  });
536
542
  }
537
- /**
538
- * Bridge an SDK `ReadableSignalTrait` to an RxJS Observable.
539
- *
540
- * The observable emits the current value immediately on subscribe, then
541
- * emits whenever the SDK signal value changes. Completes on unsubscribe.
542
- */
543
- function signalToObservable(source) {
544
- return new Observable((subscriber) => {
545
- subscriber.next(source.get());
546
- const unsubscribe = source.subscribe(() => {
547
- subscriber.next(source.get());
548
- });
549
- return () => unsubscribe();
550
- });
543
+ function provideEnvironment(options) {
544
+ return [
545
+ {
546
+ provide: ENVIRONMENT,
547
+ useFactory: (router, httpClient, injector, destroyRef, providers) => {
548
+ const { autoCreateDestroyRef, providers: optionProviders, ...environmentOptions } = options;
549
+ const securitydeptDestroyRef = autoCreateDestroyRef === false
550
+ ? null
551
+ : createSecuritydeptDestroyRef();
552
+ if (securitydeptDestroyRef) {
553
+ destroyRef.onDestroy(() => securitydeptDestroyRef.dispose());
554
+ }
555
+ const environmentProviders = [
556
+ ...(optionProviders ?? []),
557
+ ...(providers ?? []),
558
+ ...(securitydeptDestroyRef
559
+ ? [
560
+ {
561
+ provide: SecuritydeptDestroyRef,
562
+ useValue: securitydeptDestroyRef,
563
+ },
564
+ ]
565
+ : []),
566
+ ];
567
+ return createEnvironmentForAngular({
568
+ ...environmentOptions,
569
+ routerForAngularCreateOptions: {
570
+ router,
571
+ ...environmentOptions.routerForAngularCreateOptions,
572
+ },
573
+ transportForAngularCreateOptions: {
574
+ httpClient,
575
+ ...environmentOptions.transportForAngularCreateOptions,
576
+ },
577
+ injectorForAngularProvider: injector,
578
+ providers: environmentProviders,
579
+ });
580
+ },
581
+ deps: [
582
+ Router,
583
+ HttpClient,
584
+ Injector,
585
+ DestroyRef,
586
+ [new Optional(), ENVIRONMENT_PROVIDER],
587
+ ],
588
+ },
589
+ {
590
+ provide: SECURITYDEPT_INJECTOR,
591
+ useFactory: (environment) => environment.injector,
592
+ deps: [ENVIRONMENT],
593
+ },
594
+ ];
595
+ }
596
+ function provideEnvironmentProvider(provider) {
597
+ return {
598
+ provide: ENVIRONMENT_PROVIDER,
599
+ useValue: provider,
600
+ multi: true,
601
+ };
602
+ }
603
+
604
+ function toNgSignal(source, options) {
605
+ return toSignal(source[SYMBOL_OBSERVABLE](), options);
551
606
  }
607
+ const toNgResource = rxResource;
552
608
 
553
609
  /*
554
610
  * Public API Surface of @securitydept/client-angular
@@ -558,5 +614,5 @@ function signalToObservable(source) {
558
614
  * Generated bundle index. Do not edit.
559
615
  */
560
616
 
561
- export { AUTH_PLANNER_HOST, AUTH_REQUIREMENTS_CLIENT_SET, AuthRouteAdapter, DEFAULT_ROUTE_REQUIREMENTS_KEY, PAGE_CLIENT_ENVIRONMENT, ROUTE_REQUIREMENTS_COMPOSITION_DATA_KEY, ROUTE_REQUIREMENTS_DATA_KEY, bridgeToAngularSignal, extractFullRouteRequirements, injectPlannerHost, provideAuthPlannerHost, providePageClientEnvironment, provideRouteScopedRequirements, resolveEffectiveRequirements, resolvePageClientEnvironmentSource, signalToObservable, withRouteRequirements };
617
+ export { AngularRouterNavigationLikeSchema, BaseTransportForAngularCreateOptionsSchema, ENVIRONMENT, ENVIRONMENT_PROVIDER, SECURITYDEPT_INJECTOR, createAngularCanActivate, createAngularCanActivateChild, createBaseTransportForAngular, createEnvironmentForAngular, createRouterForAngular, projectAngularRouteSegments, provideEnvironment, provideEnvironmentProvider, provideSecuritydept, routeDataFromSegment, secureRoute, secureRouteRoot, toNgResource, toNgSignal };
562
618
  //# sourceMappingURL=securitydept-client-angular.mjs.map