@loozo/angular-router 0.0.7 → 0.0.9

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 (27) hide show
  1. package/README.md +178 -39
  2. package/fesm2022/loozo-angular-router-_core.mjs +33 -2
  3. package/fesm2022/loozo-angular-router-_core.mjs.map +1 -1
  4. package/fesm2022/loozo-angular-router-_generated.mjs +196 -43
  5. package/fesm2022/loozo-angular-router-_generated.mjs.map +1 -1
  6. package/fesm2022/loozo-angular-router.mjs +2 -2
  7. package/node/src/builders/serve.js +82 -87
  8. package/node/src/generation/emit.js +29 -3
  9. package/node/src/generation/files.d.ts +1 -0
  10. package/node/src/generation/files.js +13 -2
  11. package/node/src/generation/overlay.js +9 -2
  12. package/node/src/generation/sources.js +12 -5
  13. package/package.json +1 -1
  14. package/snippets/generated/destinations.generated.ts +25 -1
  15. package/snippets/src/routes/products/$productId/route.generated.ts +4 -2
  16. package/snippets/src/routes/reports/$reportId/route.generated.ts +4 -2
  17. package/snippets/src/routes/teams/$teamId/items/$id/inputs.ts +2 -1
  18. package/snippets/src/routes/teams/$teamId/items/$id/links.ts +6 -4
  19. package/snippets/src/routes/teams/$teamId/items/$id/page.spec.ts +29 -0
  20. package/snippets/src/routes/teams/$teamId/items/$id/route.generated.ts +4 -2
  21. package/snippets/src/routes/teams/$teamId/members/$memberId/route.generated.ts +4 -2
  22. package/snippets/src/routes/teams/$teamId/route.generated.ts +4 -2
  23. package/snippets/src/routes/teams/$teamId/route.ts +3 -0
  24. package/snippets/src/shell.ts +39 -3
  25. package/types/loozo-angular-router-_core.d.ts +135 -53
  26. package/types/loozo-angular-router-_generated.d.ts +111 -61
  27. package/types/loozo-angular-router.d.ts +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"loozo-angular-router-_generated.mjs","names":[],"sources":["../../_generated/src/inputs.ts","../../_generated/src/callbacks.ts","../../_generated/src/handle.ts","../../_generated/src/index.ts"],"sourcesContent":["import type { ActivatedRouteSnapshot, PartialMatchRouteSnapshot } from \"@angular/router\";\nimport { codecFallback, routeMetadata, type AnyRoute } from \"@loozo/angular-router/_core\";\n\nexport interface RecordBinding {\n readonly definition: () => AnyRoute;\n readonly owners: readonly string[];\n}\nexport interface RouteSet {\n readonly records: Readonly<Record<string, RecordBinding>>;\n readonly destinations: Readonly<Record<string, () => AnyRoute>>;\n readonly absolute: Readonly<Record<string, string>>;\n readonly parents: Readonly<Record<string, string>>;\n}\nexport interface Parsed {\n readonly params: Readonly<Record<string, unknown>>;\n readonly search: Readonly<Record<string, unknown>>;\n}\nexport interface Job {\n readonly key: string;\n readonly raws: readonly string[];\n readonly array: boolean;\n readonly codec: { parse(raw: string): unknown };\n}\n\nexport function hasIdentity(\n snapshot: Pick<ActivatedRouteSnapshot, \"routeConfig\">,\n id: string,\n): boolean {\n const data = snapshot.routeConfig?.data;\n\n return (\n !!data &&\n Object.hasOwn(data, \"__loozoAngularRouterRouteId\") &&\n data.__loozoAngularRouterRouteId === id\n );\n}\n\nexport function assertIdentity(\n snapshot: Pick<ActivatedRouteSnapshot, \"routeConfig\">,\n id: string,\n): void {\n if (!hasIdentity(snapshot, id)) {\n throw new Error(`Generated route wiring identity mismatch: ${id}`);\n }\n}\n\n// Private: input errors are Errors with source/key/reason, not an exported class.\nclass InputError extends Error {\n readonly source = \"path\";\n\n constructor(\n readonly key: string,\n readonly reason: \"missing\" | \"repeated\" | \"invalid\",\n cause?: unknown,\n ) {\n super(`Invalid path input ${key}: ${reason}`, { cause });\n }\n}\n\nexport function validateRaw(\n key: string,\n raws: readonly unknown[],\n array: boolean,\n): asserts raws is readonly string[] {\n if (!array && raws.length === 0) {\n throw new InputError(key, \"missing\");\n }\n\n if (!array && raws.length > 1) {\n throw new InputError(key, \"repeated\");\n }\n\n if (raws.some((raw) => typeof raw !== \"string\")) {\n throw new InputError(key, \"invalid\", new TypeError(\"Raw input must be a decoded string\"));\n }\n}\n\n/**\n * Runs one parser. A throw is invalid input; a thenable is a programming error,\n * thrown as a TypeError so it fails the navigation instead of reading as invalid.\n */\nfunction parse(codec: Job[\"codec\"], raw: string): { value: unknown } | { cause: unknown } {\n let value: unknown;\n\n try {\n value = codec.parse(raw);\n } catch (cause) {\n return { cause };\n }\n\n if (\n value !== null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n ) {\n // Observe the stray result so its rejection can't surface as unhandled.\n Promise.resolve(value).catch(() => undefined);\n\n throw new TypeError(\"ParamCodec.parse must return its value synchronously, not a Promise\");\n }\n\n return { value };\n}\n\nexport function pathJobs(definition: AnyRoute, url: readonly { path: string }[]): Job[] {\n const metadata = routeMetadata(definition);\n const local = new Map<string, Job>();\n\n for (let index = 0; index < metadata.localSegments.length; index += 1) {\n const segment = metadata.localSegments[index];\n\n if (segment.kind === \"static\") {\n continue;\n }\n\n let raws: unknown[] = [];\n const array = segment.kind === \"catchall\";\n\n if (array) {\n raws = url.slice(index).map((value) => value.path);\n } else if (index < url.length) {\n raws = [url[index].path];\n }\n\n validateRaw(segment.name, raws, array);\n // Repeated local scalars intentionally use Angular's last positional occurrence.\n local.set(segment.name, {\n key: segment.name,\n raws,\n array,\n codec: metadata.localParams[segment.name],\n });\n }\n\n return [...local.values()];\n}\n\n/** Parses prechecked path jobs; the first codec failure is an input error. */\nexport function runPathJobs(jobs: readonly Job[]): Readonly<Record<string, unknown>> {\n const params: Record<string, unknown> = Object.create(null);\n\n for (const job of jobs) {\n const values: unknown[] = [];\n\n for (const raw of job.raws) {\n const result = parse(job.codec, raw);\n\n if (\"cause\" in result) {\n throw new InputError(job.key, \"invalid\", result.cause);\n }\n\n values.push(result.value);\n }\n\n if (job.array) {\n params[job.key] = Object.freeze(values);\n } else {\n params[job.key] = values[0];\n }\n }\n\n return Object.freeze(params);\n}\n\n/**\n * Parses a declaration's effective search. It never fails on input: a missing,\n * repeated or invalid value reads as the key's fallback, or leaves it absent.\n */\nexport function parseSearch(\n definition: AnyRoute,\n snapshot: Pick<ActivatedRouteSnapshot, \"queryParamMap\">,\n): Readonly<Record<string, unknown>> {\n const metadata = routeMetadata(definition);\n const search: Record<string, unknown> = Object.create(null);\n\n for (const key of Object.keys(metadata.search)) {\n const codec = metadata.search[key];\n const raws: unknown[] = snapshot.queryParamMap.getAll(key);\n\n if (raws.length === 1 && typeof raws[0] === \"string\") {\n const result = parse(codec, raws[0]);\n\n if (\"value\" in result) {\n search[key] = result.value;\n\n continue;\n }\n }\n\n const fallback = codecFallback(codec);\n\n if (fallback) {\n search[key] = fallback.value;\n }\n }\n\n return Object.freeze(search);\n}\n\ntype Outcome<Value> = { readonly value: Value } | { readonly error: unknown };\ntype Cache<Value> = WeakMap<object, WeakMap<object, Outcome<Value>>>;\n\nconst full: Cache<Parsed> = new WeakMap();\nconst paths: Cache<Readonly<Record<string, unknown>>> = new WeakMap();\nconst matching: Cache<Parsed | undefined> = new WeakMap();\n\n// One result or failure per exact declaration and snapshot, shared by every reader.\nfunction memo<Value>(\n cache: Cache<Value>,\n definition: AnyRoute,\n snapshot: object,\n run: () => Value,\n): Value {\n let snapshots = cache.get(definition);\n\n if (!snapshots) {\n snapshots = new WeakMap();\n cache.set(definition, snapshots);\n }\n\n let outcome = snapshots.get(snapshot);\n\n if (!outcome) {\n try {\n outcome = { value: run() };\n } catch (error) {\n outcome = { error };\n }\n\n snapshots.set(snapshot, outcome);\n }\n\n if (\"error\" in outcome) {\n throw outcome.error;\n }\n\n return outcome.value;\n}\n\n/** The effective inputs of a complete snapshot, shared by guards, resolvers and handles. */\nexport function parseInputs(set: RouteSet, id: string, snapshot: ActivatedRouteSnapshot): Parsed {\n assertIdentity(snapshot, id);\n\n const binding = set.records[id];\n\n return memo(full, binding.definition(), snapshot, () => {\n const jobs: Job[] = [];\n\n for (const ownerId of binding.owners) {\n const definition = set.records[ownerId].definition();\n\n // Pathless/static owners contribute no positional input. Effective search belongs\n // to this complete future record; inherited data is never positional authority.\n if (!routeMetadata(definition).localSegments.some((segment) => segment.kind !== \"static\")) {\n continue;\n }\n\n const owner = [...snapshot.pathFromRoot]\n .reverse()\n .find((record) => hasIdentity(record, ownerId));\n\n if (!owner) {\n throw new Error(`Generated route ancestor missing: ${ownerId}`);\n }\n\n assertIdentity(owner, ownerId);\n jobs.push(...pathJobs(definition, owner.url));\n }\n\n const params = runPathJobs(jobs);\n\n return Object.freeze({ params, search: parseSearch(binding.definition(), snapshot) });\n });\n}\n\n/**\n * Parses a matching candidate's own path params only. Angular passes one partial\n * snapshot to all of a candidate's canMatch guards, so they share this result.\n *\n * @returns An input error when a param is invalid; throws only programming errors.\n */\nexport function pathInputs(\n set: RouteSet,\n id: string,\n snapshot: PartialMatchRouteSnapshot,\n): { params: Readonly<Record<string, unknown>> } | { error: Error } {\n assertIdentity(snapshot, id);\n\n const definition = set.records[id].definition();\n\n try {\n return {\n params: memo(paths, definition, snapshot, () =>\n runPathJobs(pathJobs(definition, snapshot.url)),\n ),\n };\n } catch (error) {\n if (error instanceof InputError) {\n return { error };\n }\n\n throw error;\n }\n}\n\n/**\n * Local path params plus effective search for a candidate's canMatch guards, the\n * generated one included, or undefined when the path params fail: the generated\n * guard owns that outcome.\n */\nexport function matchingInputs(\n set: RouteSet,\n id: string,\n snapshot: PartialMatchRouteSnapshot,\n): Parsed | undefined {\n const definition = set.records[id].definition();\n\n return memo(matching, definition, snapshot, () => {\n const path = pathInputs(set, id, snapshot);\n\n // Search parses only once the path matches: a candidate the path rules out runs no\n // search codecs.\n if (\"error\" in path) {\n return undefined;\n }\n\n return Object.freeze({ params: path.params, search: parseSearch(definition, snapshot) });\n });\n}\n","import { inject } from \"@angular/core\";\nimport {\n Router,\n RedirectCommand,\n UrlTree,\n type Route,\n type ActivatedRouteSnapshot,\n type RouterStateSnapshot,\n type GuardResult,\n type CanActivateFn,\n type CanActivateChildFn,\n type CanMatchFn,\n type ResolveFn,\n type RedirectFunction,\n} from \"@angular/router\";\nimport { from, isObservable, map, type ObservableInput } from \"rxjs\";\nimport { routeMetadata, type AnyRoute } from \"@loozo/angular-router/_core\";\nimport {\n hasIdentity,\n parseInputs,\n matchingInputs,\n pathInputs,\n runPathJobs,\n validateRaw,\n type Job,\n type RouteSet,\n} from \"./inputs.js\";\n\n// Parsing is synchronous, so every adapter calls its callback in the same call Angular\n// made: Angular's injection context is still active and inject() works at entry.\nexport function callbacks(\n set: RouteSet,\n id: string,\n): Pick<Route, \"canActivate\" | \"canActivateChild\" | \"canMatch\" | \"resolve\" | \"title\"> {\n const metadata = routeMetadata(set.records[id].definition());\n const config = metadata.config;\n const result: Pick<Route, \"canActivate\" | \"canActivateChild\" | \"canMatch\" | \"resolve\" | \"title\"> =\n {};\n const activate = config.canActivate as readonly ((...args: any[]) => any)[] | undefined;\n\n if (activate) {\n result.canActivate = activate.map(\n (callback): CanActivateFn =>\n (snapshot, state) =>\n callback(parseInputs(set, id, snapshot), snapshot, state),\n );\n }\n\n const children = config.canActivateChild as readonly ((...args: any[]) => any)[] | undefined;\n\n if (children) {\n result.canActivateChild = children.map(\n (callback): CanActivateChildFn =>\n (child, state) => {\n let parent: ActivatedRouteSnapshot | undefined;\n\n // The native ancestry includes child itself; only proper ancestors can declare this guard.\n for (let index = child.pathFromRoot.length - 2; index >= 0; index -= 1) {\n const ancestor = child.pathFromRoot[index];\n\n if (hasIdentity(ancestor, id)) {\n parent = ancestor;\n\n break;\n }\n }\n\n if (!parent) {\n throw new Error(`Generated guard ancestor missing: ${id}`);\n }\n\n return callback(parseInputs(set, id, parent), child, state);\n },\n );\n }\n\n const matching = config.canMatch as readonly ((...args: any[]) => any)[] | undefined;\n const canMatch: CanMatchFn[] = [];\n\n // Local path params decide whether this record matches. Angular runs every canMatch\n // guard concurrently and prefers the first non-true result in array order.\n if (\n metadata.localSegments.some((segment) => segment.kind !== \"static\") ||\n Object.keys(metadata.search).length > 0\n ) {\n const handler = config.onInvalidParams as ((...args: any[]) => unknown) | undefined;\n\n canMatch.push((route, segments, snapshot) => {\n const path = pathInputs(set, id, snapshot);\n\n if (!(\"error\" in path)) {\n // Search never decides the match, but Angular runs this guard on every navigation,\n // query-only ones included, so a thenable search parse fails it here.\n matchingInputs(set, id, snapshot);\n\n return true;\n }\n\n if (!handler) {\n return false;\n }\n\n const decision = handler(path.error, route, segments, snapshot);\n\n if (isObservable(decision) || isPromiseResult(decision)) {\n return from(decision as ObservableInput<unknown>).pipe(map(invalidParamsResult));\n }\n\n return invalidParamsResult(decision);\n });\n }\n\n if (matching) {\n for (const callback of matching) {\n canMatch.push((route, segments, snapshot) => {\n const inputs = matchingInputs(set, id, snapshot);\n\n // Invalid path params: the generated guard ahead of this one decides.\n if (inputs === undefined) {\n return false;\n }\n\n return callback(inputs, route, segments, snapshot);\n });\n }\n }\n\n if (canMatch.length > 0) {\n result.canMatch = canMatch;\n }\n\n const resolvers = config.resolve as\n | Record<\n PropertyKey,\n (inputs: unknown, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => any\n >\n | undefined;\n\n if (resolvers) {\n const resolve: Record<PropertyKey, ResolveFn<unknown>> = Object.create(null);\n\n for (const key of Reflect.ownKeys(resolvers)) {\n resolve[key] = (snapshot, state) =>\n resolvers[key](parseInputs(set, id, snapshot), snapshot, state);\n }\n\n result.resolve = resolve;\n }\n\n const title = config.title as\n | string\n | ((inputs: unknown, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => any)\n | undefined;\n\n // Angular runs a title callback as a resolver, so it's adapted like one.\n if (typeof title === \"function\") {\n result.title = (snapshot, state) => title(parseInputs(set, id, snapshot), snapshot, state);\n } else if (title !== undefined) {\n result.title = title;\n }\n\n return result;\n}\n\nfunction isPromiseResult(value: unknown): value is PromiseLike<unknown> {\n return !!value && typeof (value as PromiseLike<unknown>).then === \"function\";\n}\n\n// Invalid params can't match, so a handler may only refuse or redirect.\nfunction invalidParamsResult(decision: unknown): GuardResult {\n if (decision === false || decision instanceof UrlTree || decision instanceof RedirectCommand) {\n return decision;\n }\n\n throw new TypeError(\"onInvalidParams must return false, a UrlTree or a RedirectCommand\");\n}\n\nexport function redirect(definition: () => AnyRoute): RedirectFunction {\n return (snapshot) => {\n const router = inject(Router);\n const source = routeMetadata(definition());\n const config = source.config.redirect as {\n to: AnyRoute;\n params: Record<string, string>;\n search: { mode: string; values?: Record<string, unknown> };\n fragment: { mode: string; value?: string };\n };\n const target = routeMetadata(config.to);\n const raw = new Map<string, { values: string[]; array: boolean }>();\n\n for (let index = 0; index < source.segments.length; index += 1) {\n const segment = source.segments[index];\n\n if (segment.kind === \"scalar\") {\n raw.set(segment.name, {\n values: snapshot.url.slice(index, index + 1).map((value) => value.path),\n array: false,\n });\n } else if (segment.kind === \"catchall\") {\n raw.set(segment.name, {\n values: snapshot.url.slice(index).map((value) => value.path),\n array: true,\n });\n }\n }\n\n const jobs: Job[] = [];\n\n // Only mapped source params are parsed, each cardinality checked before any parser runs.\n for (const key of new Set(Object.values(config.params))) {\n const input = raw.get(key);\n const raws = input?.values ?? [];\n const array = input?.array ?? false;\n\n validateRaw(key, raws, array);\n jobs.push({ key, raws, array, codec: source.params[key] });\n }\n\n // A failure fails the navigation: a redirect record has no matching guard to fall through.\n const logical = runPathJobs(jobs);\n const params: Record<string, unknown> = Object.create(null);\n\n for (const [key, sourceKey] of Object.entries(config.params)) {\n params[key] = logical[sourceKey];\n }\n\n const path = createUrlForPath(config.to, params);\n const replacement: Record<string, string> = Object.create(null);\n\n if (config.search.mode === \"replace\") {\n const values = config.search.values!;\n\n for (const key of Object.keys(target.search).sort()) {\n if (!Object.hasOwn(values, key) || values[key] === undefined) {\n continue;\n }\n\n const raw = target.search[key].stringify(values[key]);\n\n if (typeof raw !== \"string\") {\n throw new TypeError(`Redirect search codec must stringify to a string: ${key}`);\n }\n\n replacement[key] = raw;\n }\n }\n\n // No native conversion before source codecs and destination stringification succeed.\n const tree = router.parseUrl(path);\n\n if (config.search.mode === \"preserve\") {\n tree.queryParams = { ...snapshot.queryParams };\n } else if (config.search.mode === \"replace\") {\n tree.queryParams = replacement;\n }\n\n if (config.fragment.mode === \"preserve\") {\n tree.fragment = snapshot.fragment;\n } else if (config.fragment.mode === \"replace\") {\n tree.fragment = config.fragment.value!;\n }\n\n return tree;\n };\n}\n\nfunction createUrlForPath(definition: AnyRoute, params: Record<string, unknown>): string {\n const metadata = routeMetadata(definition);\n const parts: string[] = [];\n\n function encode(raw: string): string {\n if (raw === \"\" || raw === \".\" || raw === \"..\") {\n throw new TypeError(\"Invalid redirect path value\");\n }\n\n return encodeURIComponent(raw).replace(/\\(/g, \"%28\").replace(/\\)/g, \"%29\");\n }\n\n for (const segment of metadata.segments) {\n if (segment.kind === \"static\") {\n parts.push(encode(segment.text));\n } else if (segment.kind === \"scalar\") {\n const raw = metadata.params[segment.name].stringify(params[segment.name]);\n\n if (typeof raw !== \"string\") {\n throw new TypeError(\"Redirect codec must stringify to a string\");\n }\n\n parts.push(encode(raw));\n } else {\n const values = params[segment.name];\n\n if (!Array.isArray(values)) {\n throw new TypeError(\"Redirect catchall requires an array\");\n }\n\n for (const value of values) {\n const raw = metadata.params[segment.name].stringify(value);\n\n if (typeof raw !== \"string\") {\n throw new TypeError(\"Redirect codec must stringify to a string\");\n }\n\n parts.push(encode(raw));\n }\n }\n }\n\n return `/${parts.join(\"/\")}`;\n}\n","import { DestroyRef, computed, inject, signal, type Signal } from \"@angular/core\";\nimport { ActivatedRoute, Router, type ActivatedRouteSnapshot } from \"@angular/router\";\nimport { routeMetadata } from \"@loozo/angular-router/_core\";\nimport { merge } from \"rxjs\";\nimport { assertIdentity, parseInputs, type RouteSet } from \"./inputs.js\";\n\n/**\n * Signals of the route's committed snapshot. Angular replaces ActivatedRoute.snapshot\n * only when a navigation activates, so a pending, cancelled or failed navigation\n * leaves the values alone; params, search and data change together, from one snapshot.\n */\nexport function observeRoute(\n set: RouteSet,\n id: string,\n): {\n params: Signal<Readonly<Record<string, unknown>>>;\n search: Signal<Readonly<Record<string, unknown>>>;\n data: Signal<Readonly<Record<PropertyKey, unknown>>>;\n} {\n const activated = inject(ActivatedRoute);\n const router = inject(Router);\n\n assertIdentity(activated.snapshot, id);\n\n const snapshot = signal<ActivatedRouteSnapshot>(activated.snapshot);\n // Activation replaces the snapshot, then emits the route's changed observables, then\n // ActivationEnd. Following both keeps reads current inside subscribers registered after\n // the handle; router events cover a new snapshot whose values are all equal.\n const subscription = merge(\n activated.queryParams,\n activated.fragment,\n activated.params,\n activated.url,\n activated.data,\n router.events,\n ).subscribe(() => {\n snapshot.set(activated.snapshot);\n });\n\n inject(DestroyRef).onDestroy(() => subscription.unsubscribe());\n\n // Shares the parse of guards and resolvers for the same snapshot. An error here\n // means a codec rejected a value it accepted while matching, and reading rethrows it.\n const inputs = computed(() => parseInputs(set, id, snapshot()));\n const resolvers = routeMetadata(set.records[id].definition()).config.resolve ?? {};\n\n return {\n params: computed(() => inputs().params),\n search: computed(() => inputs().search),\n data: computed(() => {\n const data = snapshot().data;\n const own: Record<PropertyKey, unknown> = Object.create(null);\n\n for (const key of Reflect.ownKeys(resolvers)) {\n if (Object.hasOwn(data, key)) {\n own[key] = data[key];\n }\n }\n\n return Object.freeze(own);\n }),\n };\n}\n","export { callbacks, redirect } from \"./callbacks.js\";\nexport { assertIdentity, parseInputs } from \"./inputs.js\";\nexport type { RecordBinding, RouteSet } from \"./inputs.js\";\n\nimport { inject, type Signal } from \"@angular/core\";\nimport { Router, type UrlTree } from \"@angular/router\";\nimport {\n createUrl,\n routeMetadata,\n type AnyRoute,\n type ParamsOf,\n type SearchOf,\n type UrlValues,\n} from \"@loozo/angular-router/_core\";\nimport { observeRoute } from \"./handle.js\";\nimport type { RouteSet } from \"./inputs.js\";\n\ndeclare const destinationBrand: unique symbol;\n\n/**\n * An inert nominal reference exported as `destination` by a route-local facade.\n *\n * @remarks\n * Use the exported object with a handle's link/navigate methods; no injection\n * context or target activation is needed. It selects its exact declaration,\n * not a canonical pathless winner. Only references from the caller's generated\n * set are accepted. Copies, spreads, fakes and authored routes are not references.\n * Do not construct this type or pass it to the root createUrl function.\n *\n * @typeParam Target - The referenced route's input and fragment schema.\n * @typeParam Set - The generator's nominal route-set identity.\n * @typeParam Id - The exact generated declaration identity.\n */\nexport declare class Destination<\n Target extends AnyRoute,\n Set = unknown,\n Id extends string = string,\n> {\n // Type-only identity, intentionally not read; object spreads must lose this member.\n // oxlint-disable-next-line no-unused-private-class-members\n readonly #nominal: void;\n /** Type-only nominal brand carrying the reference's identity; not populated at runtime. */\n readonly [destinationBrand]: {\n /** Type-only: the referenced route's input and fragment schema. */\n readonly target: Target;\n /** Type-only: the generator's nominal route-set identity. */\n readonly set: Set;\n /** Type-only: the exact generated declaration identity. */\n readonly id: Id;\n };\n}\n\nconst destinations = new WeakMap<object, { getSet: () => RouteSet; id: string }>();\n\nexport function createDestination<Target extends AnyRoute, Set, Id extends string>(\n getSet: () => RouteSet,\n id: Id,\n): Destination<Target, Set, Id> {\n const destination = Object.freeze({});\n\n destinations.set(destination, { getSet, id });\n\n return destination as Destination<Target, Set, Id>;\n}\n\ntype DataOf<Target> = Target extends { readonly __data?: infer Data } ? Readonly<Data> : never;\ntype IsUnion<Value, Whole = Value> = Value extends unknown\n ? [Whole] extends [Value]\n ? false\n : true\n : never;\n\n/**\n * The noncallable typed handle returned by a colocated `injectRoute()` facade.\n *\n * @remarks\n * Obtain it in an Angular injection context for the exact generated activated\n * record. Params, search and data are readonly Angular Signals of the route's\n * committed snapshot, with values from the first read. Data contains only local\n * resolver results, not static/ancestor data or title.\n *\n * While a navigation is pending, the signals keep the current snapshot's values;\n * when a navigation commits a new snapshot for this route, all three change\n * together. Cancellation, failure or a same-URL skip leaves them unchanged. For\n * pending-navigation UI, read Angular's `Router.currentNavigation()`. There is no\n * resolved-data cache. Reading params or search throws only when a path codec\n * rejects a value it accepted while matching, a programming error.\n *\n * Selectors are single known authored literals, not concrete URLs or string\n * unions. `.` is exact-current; `..` selects the parent route: the nearest\n * ancestor with a shorter path, or the root for a top-level route. It and\n * absolute selectors use generated canonical targets. Every call supplies all\n * target values; there is no current-value reuse, extras argument or implicit\n * query merge.\n *\n * @typeParam Target - The bound declaration's schema.\n * @typeParam Absolute - Absolute literal selectors and their target schemas.\n * @typeParam Parent - The `..` target's schema, or never when there is none.\n * @typeParam Set - The generator's nominal route-set identity.\n * @typeParam References - Exact declarations addressable by same-set references.\n *\n * @example\n * ```ts\n * import { injectRoute } from \"./route.generated.js\";\n * import { destination as report } from \"../report/route.generated.js\";\n *\n * const route = injectRoute(); // Inside an Angular injection context.\n * const link = route.link(report, { params: { reportId: 42 } });\n * // Bind link to Angular RouterLink, or call route.navigate with the same values.\n * ```\n */\nexport interface Handle<\n Target extends AnyRoute,\n Absolute extends Record<string, AnyRoute> = {},\n Parent extends AnyRoute = never,\n Set = unknown,\n References extends Record<string, AnyRoute> = {},\n> {\n /** Effective positional params of the committed snapshot; kept while a navigation is pending. */\n readonly params: Signal<Readonly<ParamsOf<Target>>>;\n /** Effective declared search, from the same snapshot as params; absent keys have no value or fallback. */\n readonly search: Signal<Readonly<SearchOf<Target>>>;\n /** Own native resolver results only, from the same snapshot as params. */\n readonly data: Signal<DataOf<Target>>;\n /**\n * Creates a native UrlTree for this exact declaration without navigating.\n *\n * @param values - Explicit effective params/search and optional own fragment.\n * @returns Router.parseUrl's result, suitable for Angular RouterLink.\n * @throws Synchronous URL validation, encoding or stringifier failures before Angular.\n */\n link<Keys extends keyof SearchOf<Target> = never>(values: UrlValues<Target, Keys>): UrlTree;\n /**\n * Creates a UrlTree for the exact bound declaration, not its pathless winner.\n *\n * @param selector - The exact-current literal `.`.\n * @param values - Explicit values for this declaration, with no current-input reuse.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronous URL validation, encoding or stringifier failures before Angular.\n */\n link<Keys extends keyof SearchOf<Target> = never>(\n selector: \".\",\n values: UrlValues<Target, Keys>,\n ): UrlTree;\n /**\n * Creates a UrlTree for a statically admitted absolute authored selector.\n *\n * @param selector - A single known template literal, never a concrete URL.\n * @param values - The selected target's complete explicit values.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronously for unknown selectors or invalid URL values.\n */\n link<\n const Selector extends keyof Absolute & string,\n Keys extends keyof SearchOf<NoInfer<Absolute[Selector]>> = never,\n >(\n selector: Selector & (IsUnion<Selector> extends true ? never : unknown),\n values: UrlValues<NoInfer<Absolute[Selector]>, Keys>,\n ): UrlTree;\n /**\n * Creates a UrlTree for the parent route, when the bound declaration has one.\n *\n * @param selector - The literal `..`.\n * @param values - The parent route's complete explicit values.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronously for invalid URL values.\n */\n link<Keys extends keyof SearchOf<Parent> = never>(\n selector: [Parent] extends [never] ? never : \"..\",\n values: UrlValues<Parent, Keys>,\n ): UrlTree;\n /**\n * Creates a UrlTree for a generated reference's own exact declaration.\n *\n * @param selector - A genuine destination exported by a facade in this route set.\n * @param values - The referenced declaration's complete explicit values.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronously for foreign/fake references or invalid URL values.\n */\n link<\n const Id extends keyof References & string,\n Keys extends keyof SearchOf<NoInfer<References[Id]>> = never,\n >(\n selector: Destination<NoInfer<References[Id]>, Set, Id> &\n (IsUnion<Id> extends true ? never : unknown),\n values: UrlValues<NoInfer<References[Id]>, Keys>,\n ): UrlTree;\n /**\n * Navigates to this exact declaration via Router.navigateByUrl.\n *\n * @param values - Explicit effective params/search and optional own fragment.\n * @returns Angular's identical Promise; Angular owns skips, redirects and failures.\n * @throws Synchronous URL validation, encoding or stringifier failures before Angular.\n */\n navigate<Keys extends keyof SearchOf<Target> = never>(\n values: UrlValues<Target, Keys>,\n ): Promise<boolean>;\n /**\n * Navigates to the exact bound declaration, not its pathless winner.\n *\n * @param selector - The exact-current literal `.`.\n * @param values - Explicit values for this declaration, with no current-input reuse.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronous URL validation, encoding or stringifier failures before Angular.\n */\n navigate<Keys extends keyof SearchOf<Target> = never>(\n selector: \".\",\n values: UrlValues<Target, Keys>,\n ): Promise<boolean>;\n /**\n * Navigates to a statically admitted absolute authored selector.\n *\n * @param selector - A single known template literal, never a concrete URL.\n * @param values - The selected target's complete explicit values.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronously for unknown selectors or invalid URL values.\n */\n navigate<\n const Selector extends keyof Absolute & string,\n Keys extends keyof SearchOf<NoInfer<Absolute[Selector]>> = never,\n >(\n selector: Selector & (IsUnion<Selector> extends true ? never : unknown),\n values: UrlValues<NoInfer<Absolute[Selector]>, Keys>,\n ): Promise<boolean>;\n /**\n * Navigates to the parent route, when the bound declaration has one.\n *\n * @param selector - The literal `..`.\n * @param values - The parent route's complete explicit values.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronously for invalid URL values.\n */\n navigate<Keys extends keyof SearchOf<Parent> = never>(\n selector: [Parent] extends [never] ? never : \"..\",\n values: UrlValues<Parent, Keys>,\n ): Promise<boolean>;\n /**\n * Navigates to a generated reference's own exact declaration.\n *\n * @param selector - A genuine destination exported by a facade in this route set.\n * @param values - The referenced declaration's complete explicit values.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronously for foreign/fake references or invalid URL values.\n */\n navigate<\n const Id extends keyof References & string,\n Keys extends keyof SearchOf<NoInfer<References[Id]>> = never,\n >(\n selector: Destination<NoInfer<References[Id]>, Set, Id> &\n (IsUnion<Id> extends true ? never : unknown),\n values: UrlValues<NoInfer<References[Id]>, Keys>,\n ): Promise<boolean>;\n}\n\nexport function nativeOptions(definition: AnyRoute): Record<string, unknown> {\n const config = routeMetadata(definition).config;\n const options: Record<string, unknown> = {};\n\n for (const key of [\"providers\", \"data\", \"canDeactivate\", \"runGuardsAndResolvers\"]) {\n if (Object.hasOwn(config, key)) {\n options[key] = config[key];\n }\n }\n\n return options;\n}\n\nexport function injectGeneratedRoute<\n Target extends AnyRoute,\n Absolute extends Record<string, AnyRoute>,\n Parent extends AnyRoute,\n Set,\n References extends Record<string, AnyRoute>,\n>(set: RouteSet, id: string): Handle<Target, Absolute, Parent, Set, References> {\n const router = inject(Router);\n const view = observeRoute(set, id);\n\n function url(args: readonly unknown[]): string {\n if (args.length !== 1 && args.length !== 2) {\n throw new TypeError(\"Expected values or a selected destination and values\");\n }\n\n let target = id;\n let values = args[0];\n\n if (args.length === 2) {\n const selector = args[0];\n\n values = args[1];\n\n if (typeof selector === \"string\") {\n if (selector !== \".\") {\n if (Object.hasOwn(set.absolute, selector)) {\n target = set.absolute[selector];\n } else if (selector === \"..\" && Object.hasOwn(set.parents, id)) {\n target = set.parents[id];\n } else {\n throw new TypeError(`Unknown generated selector: ${selector}`);\n }\n }\n } else {\n if (typeof selector !== \"object\" || selector === null) {\n throw new TypeError(\"Expected a literal selector or genuine generated destination\");\n }\n\n const destination = destinations.get(selector);\n\n if (\n !destination ||\n destination.getSet() !== set ||\n !Object.hasOwn(set.records, destination.id)\n ) {\n throw new TypeError(\"Expected a destination from the same generated set\");\n }\n\n target = destination.id;\n }\n }\n\n return createUrl(set.destinations[target](), values as UrlValues<AnyRoute>);\n }\n\n return {\n params: view.params as Signal<Readonly<ParamsOf<Target>>>,\n search: view.search as Signal<Readonly<SearchOf<Target>>>,\n data: view.data as Signal<DataOf<Target>>,\n link: ((...args: unknown[]) => router.parseUrl(url(args))) as Handle<\n Target,\n Absolute,\n Parent,\n Set,\n References\n >[\"link\"],\n navigate: ((...args: unknown[]) => router.navigateByUrl(url(args))) as Handle<\n Target,\n Absolute,\n Parent,\n Set,\n References\n >[\"navigate\"],\n };\n}\n"],"mappings":";;;;AAwBA,SAAgB,YACd,UACA,IAAU;CAEV,MAAM,OAAO,SAAS,aAAa;CAEnC,OACE,CAAC,CAAC,QACF,OAAO,OAAO,MAAM,6BAA6B,KACjD,KAAK,gCAAgC;AAEzC;AAEA,SAAgB,eACd,UACA,IAAU;CAEV,IAAI,CAAC,YAAY,UAAU,EAAE,GAC3B,MAAM,IAAI,MAAM,6CAA6C,IAAI;AAErE;AAGA,IAAM,aAAN,cAAyB,MAAK;CAIjB;CACA;CAJF,SAAS;CAElB,YACW,KACA,QACT,OAAe;EAEf,MAAM,sBAAsB,IAAG,IAAK,UAAU,EAAE,MAAK,CAAE;EAJ9C,KAAA,MAAA;EACA,KAAA,SAAA;CAIX;AACD;AAED,SAAgB,YACd,KACA,MACA,OAAc;CAEd,IAAI,CAAC,SAAS,KAAK,WAAW,GAC5B,MAAM,IAAI,WAAW,KAAK,SAAS;CAGrC,IAAI,CAAC,SAAS,KAAK,SAAS,GAC1B,MAAM,IAAI,WAAW,KAAK,UAAU;CAGtC,IAAI,KAAK,MAAM,QAAQ,OAAO,QAAQ,QAAQ,GAC5C,MAAM,IAAI,WAAW,KAAK,2BAAW,IAAI,UAAU,oCAAoC,CAAC;AAE5F;AAMA,SAAS,MAAM,OAAqB,KAAW;CAC7C,IAAI;CAEJ,IAAI;EACF,QAAQ,MAAM,MAAM,GAAG;CACzB,SAAS,OAAO;EACd,OAAO,EAAE,MAAK;CAChB;CAEA,IACE,UAAU,SACT,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA+B,SAAS,YAChD;EAEA,QAAQ,QAAQ,KAAK,CAAC,CAAC,YAAY,MAAS;EAE5C,MAAM,IAAI,UAAU,qEAAqE;CAC3F;CAEA,OAAO,EAAE,MAAK;AAChB;AAEA,SAAgB,SAAS,YAAsB,KAAgC;CAC7E,MAAM,WAAW,cAAc,UAAU;CACzC,MAAM,wBAAQ,IAAI,IAAG;CAErB,KAAK,IAAI,QAAQ,GAAG,QAAQ,SAAS,cAAc,QAAQ,SAAS,GAAG;EACrE,MAAM,UAAU,SAAS,cAAc;EAEvC,IAAI,QAAQ,SAAS,UACnB;EAGF,IAAI,OAAkB,CAAA;EACtB,MAAM,QAAQ,QAAQ,SAAS;EAE/B,IAAI,OACF,OAAO,IAAI,MAAM,KAAK,CAAC,CAAC,KAAK,UAAU,MAAM,IAAI;OAC5C,IAAI,QAAQ,IAAI,QACrB,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI;EAGzB,YAAY,QAAQ,MAAM,MAAM,KAAK;EAErC,MAAM,IAAI,QAAQ,MAAM;GACtB,KAAK,QAAQ;GACb;GACA;GACA,OAAO,SAAS,YAAY,QAAQ;EACrC,CAAA;CACH;CAEA,OAAO,CAAC,GAAG,MAAM,OAAM,CAAE;AAC3B;AAGA,SAAgB,YAAY,MAAoB;CAC9C,MAAM,SAAkC,OAAO,OAAO,IAAI;CAE1D,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,SAAoB,CAAA;EAE1B,KAAK,MAAM,OAAO,IAAI,MAAM;GAC1B,MAAM,SAAS,MAAM,IAAI,OAAO,GAAG;GAEnC,IAAI,WAAW,QACb,MAAM,IAAI,WAAW,IAAI,KAAK,WAAW,OAAO,KAAK;GAGvD,OAAO,KAAK,OAAO,KAAK;EAC1B;EAEA,IAAI,IAAI,OACN,OAAO,IAAI,OAAO,OAAO,OAAO,MAAM;OAEtC,OAAO,IAAI,OAAO,OAAO;CAE7B;CAEA,OAAO,OAAO,OAAO,MAAM;AAC7B;AAMA,SAAgB,YACd,YACA,UAAuD;CAEvD,MAAM,WAAW,cAAc,UAAU;CACzC,MAAM,SAAkC,OAAO,OAAO,IAAI;CAE1D,KAAK,MAAM,OAAO,OAAO,KAAK,SAAS,MAAM,GAAG;EAC9C,MAAM,QAAQ,SAAS,OAAO;EAC9B,MAAM,OAAkB,SAAS,cAAc,OAAO,GAAG;EAEzD,IAAI,KAAK,WAAW,KAAK,OAAO,KAAK,OAAO,UAAU;GACpD,MAAM,SAAS,MAAM,OAAO,KAAK,EAAE;GAEnC,IAAI,WAAW,QAAQ;IACrB,OAAO,OAAO,OAAO;IAErB;GACF;EACF;EAEA,MAAM,WAAW,cAAc,KAAK;EAEpC,IAAI,UACF,OAAO,OAAO,SAAS;CAE3B;CAEA,OAAO,OAAO,OAAO,MAAM;AAC7B;AAKA,MAAM,uBAAsB,IAAI,QAAO;AACvC,MAAM,wBAAkD,IAAI,QAAO;AACnE,MAAM,2BAAsC,IAAI,QAAO;AAGvD,SAAS,KACP,OACA,YACA,UACA,KAAgB;CAEhB,IAAI,YAAY,MAAM,IAAI,UAAU;CAEpC,IAAI,CAAC,WAAW;EACd,4BAAY,IAAI,QAAO;EACvB,MAAM,IAAI,YAAY,SAAS;CACjC;CAEA,IAAI,UAAU,UAAU,IAAI,QAAQ;CAEpC,IAAI,CAAC,SAAS;EACZ,IAAI;GACF,UAAU,EAAE,OAAO,IAAG,EAAE;EAC1B,SAAS,OAAO;GACd,UAAU,EAAE,MAAK;EACnB;EAEA,UAAU,IAAI,UAAU,OAAO;CACjC;CAEA,IAAI,WAAW,SACb,MAAM,QAAQ;CAGhB,OAAO,QAAQ;AACjB;AAGA,SAAgB,YAAY,KAAe,IAAY,UAAgC;CACrF,eAAe,UAAU,EAAE;CAE3B,MAAM,UAAU,IAAI,QAAQ;CAE5B,OAAO,KAAK,MAAM,QAAQ,WAAU,GAAI,gBAAe;EACrD,MAAM,OAAc,CAAA;EAEpB,KAAK,MAAM,WAAW,QAAQ,QAAQ;GACpC,MAAM,aAAa,IAAI,QAAQ,QAAQ,CAAC,WAAU;GAIlD,IAAI,CAAC,cAAc,UAAU,CAAC,CAAC,cAAc,MAAM,YAAY,QAAQ,SAAS,QAAQ,GACtF;GAGF,MAAM,QAAQ,CAAC,GAAG,SAAS,YAAY,CAAC,CACrC,QAAO,CAAE,CACT,MAAM,WAAW,YAAY,QAAQ,OAAO,CAAC;GAEhD,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,qCAAqC,SAAS;GAGhE,eAAe,OAAO,OAAO;GAC7B,KAAK,KAAK,GAAG,SAAS,YAAY,MAAM,GAAG,CAAC;EAC9C;EAEA,MAAM,SAAS,YAAY,IAAI;EAE/B,OAAO,OAAO,OAAO;GAAE;GAAQ,QAAQ,YAAY,QAAQ,WAAU,GAAI,QAAQ;EAAC,CAAE;CACtF,CAAC;AACH;AAQA,SAAgB,WACd,KACA,IACA,UAAmC;CAEnC,eAAe,UAAU,EAAE;CAE3B,MAAM,aAAa,IAAI,QAAQ,GAAG,CAAC,WAAU;CAE7C,IAAI;EACF,OAAO,EACL,QAAQ,KAAK,OAAO,YAAY,gBAC9B,YAAY,SAAS,YAAY,SAAS,GAAG,CAAC,CAAC,EAElD;CACH,SAAS,OAAO;EACd,IAAI,iBAAiB,YACnB,OAAO,EAAE,MAAK;EAGhB,MAAM;CACR;AACF;AAOA,SAAgB,eACd,KACA,IACA,UAAmC;CAEnC,MAAM,aAAa,IAAI,QAAQ,GAAG,CAAC,WAAU;CAE7C,OAAO,KAAK,UAAU,YAAY,gBAAe;EAC/C,MAAM,OAAO,WAAW,KAAK,IAAI,QAAQ;EAIzC,IAAI,WAAW,MACb;EAGF,OAAO,OAAO,OAAO;GAAE,QAAQ,KAAK;GAAQ,QAAQ,YAAY,YAAY,QAAQ;EAAC,CAAE;CACzF,CAAC;AACH;AC1SA,SAAgB,UACd,KACA,IAAU;CAEV,MAAM,WAAW,cAAc,IAAI,QAAQ,GAAG,CAAC,WAAU,CAAE;CAC3D,MAAM,SAAS,SAAS;CACxB,MAAM,SACJ,CAAA;CACF,MAAM,WAAW,OAAO;CAExB,IAAI,UACF,OAAO,cAAc,SAAS,KAC3B,cACE,UAAU,UACT,SAAS,YAAY,KAAK,IAAI,QAAQ,GAAG,UAAU,KAAK,CAAC;CAIjE,MAAM,WAAW,OAAO;CAExB,IAAI,UACF,OAAO,mBAAmB,SAAS,KAChC,cACE,OAAO,UAAS;EACf,IAAI;EAGJ,KAAK,IAAI,QAAQ,MAAM,aAAa,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;GACtE,MAAM,WAAW,MAAM,aAAa;GAEpC,IAAI,YAAY,UAAU,EAAE,GAAG;IAC7B,SAAS;IAET;GACF;EACF;EAEA,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,qCAAqC,IAAI;EAG3D,OAAO,SAAS,YAAY,KAAK,IAAI,MAAM,GAAG,OAAO,KAAK;CAC5D,CAAC;CAIP,MAAM,WAAW,OAAO;CACxB,MAAM,WAAyB,CAAA;CAI/B,IACE,SAAS,cAAc,MAAM,YAAY,QAAQ,SAAS,QAAQ,KAClE,OAAO,KAAK,SAAS,MAAM,CAAC,CAAC,SAAS,GACtC;EACA,MAAM,UAAU,OAAO;EAEvB,SAAS,MAAM,OAAO,UAAU,aAAY;GAC1C,MAAM,OAAO,WAAW,KAAK,IAAI,QAAQ;GAEzC,IAAI,EAAE,WAAW,OAAO;IAGtB,eAAe,KAAK,IAAI,QAAQ;IAEhC,OAAO;GACT;GAEA,IAAI,CAAC,SACH,OAAO;GAGT,MAAM,WAAW,QAAQ,KAAK,OAAO,OAAO,UAAU,QAAQ;GAE9D,IAAI,aAAa,QAAQ,KAAK,gBAAgB,QAAQ,GACpD,OAAO,KAAK,QAAoC,CAAC,CAAC,KAAK,IAAI,mBAAmB,CAAC;GAGjF,OAAO,oBAAoB,QAAQ;EACrC,CAAC;CACH;CAEA,IAAI,UACF,KAAK,MAAM,YAAY,UACrB,SAAS,MAAM,OAAO,UAAU,aAAY;EAC1C,MAAM,SAAS,eAAe,KAAK,IAAI,QAAQ;EAG/C,IAAI,WAAW,QACb,OAAO;EAGT,OAAO,SAAS,QAAQ,OAAO,UAAU,QAAQ;CACnD,CAAC;CAIL,IAAI,SAAS,SAAS,GACpB,OAAO,WAAW;CAGpB,MAAM,YAAY,OAAO;CAOzB,IAAI,WAAW;EACb,MAAM,UAAmD,OAAO,OAAO,IAAI;EAE3E,KAAK,MAAM,OAAO,QAAQ,QAAQ,SAAS,GACzC,QAAQ,QAAQ,UAAU,UACxB,UAAU,IAAI,CAAC,YAAY,KAAK,IAAI,QAAQ,GAAG,UAAU,KAAK;EAGlE,OAAO,UAAU;CACnB;CAEA,MAAM,QAAQ,OAAO;CAMrB,IAAI,OAAO,UAAU,YACnB,OAAO,SAAS,UAAU,UAAU,MAAM,YAAY,KAAK,IAAI,QAAQ,GAAG,UAAU,KAAK;MACpF,IAAI,UAAU,QACnB,OAAO,QAAQ;CAGjB,OAAO;AACT;AAEA,SAAS,gBAAgB,OAAc;CACrC,OAAO,CAAC,CAAC,SAAS,OAAQ,MAA+B,SAAS;AACpE;AAGA,SAAS,oBAAoB,UAAiB;CAC5C,IAAI,aAAa,SAAS,oBAAoB,WAAW,oBAAoB,iBAC3E,OAAO;CAGT,MAAM,IAAI,UAAU,mEAAmE;AACzF;AAEA,SAAgB,SAAS,YAA0B;CACjD,QAAQ,aAAY;EAClB,MAAM,SAAS,OAAO,MAAM;EAC5B,MAAM,SAAS,cAAc,WAAU,CAAE;EACzC,MAAM,SAAS,OAAO,OAAO;EAM7B,MAAM,SAAS,cAAc,OAAO,EAAE;EACtC,MAAM,sBAAM,IAAI,IAAG;EAEnB,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,QAAQ,SAAS,GAAG;GAC9D,MAAM,UAAU,OAAO,SAAS;GAEhC,IAAI,QAAQ,SAAS,UACnB,IAAI,IAAI,QAAQ,MAAM;IACpB,QAAQ,SAAS,IAAI,MAAM,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,UAAU,MAAM,IAAI;IACtE,OAAO;GACR,CAAA;QACI,IAAI,QAAQ,SAAS,YAC1B,IAAI,IAAI,QAAQ,MAAM;IACpB,QAAQ,SAAS,IAAI,MAAM,KAAK,CAAC,CAAC,KAAK,UAAU,MAAM,IAAI;IAC3D,OAAO;GACR,CAAA;EAEL;EAEA,MAAM,OAAc,CAAA;EAGpB,KAAK,MAAM,OAAO,IAAI,IAAI,OAAO,OAAO,OAAO,MAAM,CAAC,GAAG;GACvD,MAAM,QAAQ,IAAI,IAAI,GAAG;GACzB,MAAM,OAAO,OAAO,UAAU,CAAA;GAC9B,MAAM,QAAQ,OAAO,SAAS;GAE9B,YAAY,KAAK,MAAM,KAAK;GAC5B,KAAK,KAAK;IAAE;IAAK;IAAM;IAAO,OAAO,OAAO,OAAO;GAAI,CAAE;EAC3D;EAGA,MAAM,UAAU,YAAY,IAAI;EAChC,MAAM,SAAkC,OAAO,OAAO,IAAI;EAE1D,KAAK,MAAM,CAAC,KAAK,cAAc,OAAO,QAAQ,OAAO,MAAM,GACzD,OAAO,OAAO,QAAQ;EAGxB,MAAM,OAAO,iBAAiB,OAAO,IAAI,MAAM;EAC/C,MAAM,cAAsC,OAAO,OAAO,IAAI;EAE9D,IAAI,OAAO,OAAO,SAAS,WAAW;GACpC,MAAM,SAAS,OAAO,OAAO;GAE7B,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,MAAM,CAAC,CAAC,KAAI,GAAI;IACnD,IAAI,CAAC,OAAO,OAAO,QAAQ,GAAG,KAAK,OAAO,SAAS,QACjD;IAGF,MAAM,MAAM,OAAO,OAAO,IAAI,CAAC,UAAU,OAAO,IAAI;IAEpD,IAAI,OAAO,QAAQ,UACjB,MAAM,IAAI,UAAU,qDAAqD,KAAK;IAGhF,YAAY,OAAO;GACrB;EACF;EAGA,MAAM,OAAO,OAAO,SAAS,IAAI;EAEjC,IAAI,OAAO,OAAO,SAAS,YACzB,KAAK,cAAc,EAAE,GAAG,SAAS,YAAW;OACvC,IAAI,OAAO,OAAO,SAAS,WAChC,KAAK,cAAc;EAGrB,IAAI,OAAO,SAAS,SAAS,YAC3B,KAAK,WAAW,SAAS;OACpB,IAAI,OAAO,SAAS,SAAS,WAClC,KAAK,WAAW,OAAO,SAAS;EAGlC,OAAO;CACT;AACF;AAEA,SAAS,iBAAiB,YAAsB,QAA+B;CAC7E,MAAM,WAAW,cAAc,UAAU;CACzC,MAAM,QAAkB,CAAA;CAExB,SAAS,OAAO,KAAW;EACzB,IAAI,QAAQ,MAAM,QAAQ,OAAO,QAAQ,MACvC,MAAM,IAAI,UAAU,6BAA6B;EAGnD,OAAO,mBAAmB,GAAG,CAAC,CAAC,QAAQ,OAAO,KAAK,CAAC,CAAC,QAAQ,OAAO,KAAK;CAC3E;CAEA,KAAK,MAAM,WAAW,SAAS,UAC7B,IAAI,QAAQ,SAAS,UACnB,MAAM,KAAK,OAAO,QAAQ,IAAI,CAAC;MAC1B,IAAI,QAAQ,SAAS,UAAU;EACpC,MAAM,MAAM,SAAS,OAAO,QAAQ,KAAK,CAAC,UAAU,OAAO,QAAQ,KAAK;EAExE,IAAI,OAAO,QAAQ,UACjB,MAAM,IAAI,UAAU,2CAA2C;EAGjE,MAAM,KAAK,OAAO,GAAG,CAAC;CACxB,OAAO;EACL,MAAM,SAAS,OAAO,QAAQ;EAE9B,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,UAAU,qCAAqC;EAG3D,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,MAAM,SAAS,OAAO,QAAQ,KAAK,CAAC,UAAU,KAAK;GAEzD,IAAI,OAAO,QAAQ,UACjB,MAAM,IAAI,UAAU,2CAA2C;GAGjE,MAAM,KAAK,OAAO,GAAG,CAAC;EACxB;CACF;CAGF,OAAO,IAAI,MAAM,KAAK,GAAG;AAC3B;AC1SA,SAAgB,aACd,KACA,IAAU;CAMV,MAAM,YAAY,OAAO,cAAc;CACvC,MAAM,SAAS,OAAO,MAAM;CAE5B,eAAe,UAAU,UAAU,EAAE;CAErC,MAAM,WAAW,OAA+B,UAAU,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAInE,MAAM,eAAe,MACnB,UAAU,aACV,UAAU,UACV,UAAU,QACV,UAAU,KACV,UAAU,MACV,OAAO,MAAM,CACd,CAAC,gBAAe;EACf,SAAS,IAAI,UAAU,QAAQ;CACjC,CAAC;CAED,OAAO,UAAU,CAAC,CAAC,gBAAgB,aAAa,YAAW,CAAE;CAI7D,MAAM,SAAS,eAAe,YAAY,KAAK,IAAI,SAAQ,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAC/D,MAAM,YAAY,cAAc,IAAI,QAAQ,GAAG,CAAC,WAAU,CAAE,CAAC,CAAC,OAAO,WAAW,CAAA;CAEhF,OAAO;EACL,QAAQ,eAAe,OAAM,CAAE,CAAC,MAAM;EACtC,QAAQ,eAAe,OAAM,CAAE,CAAC,MAAM;EACtC,MAAM,eAAc;GAClB,MAAM,OAAO,SAAQ,CAAE,CAAC;GACxB,MAAM,MAAoC,OAAO,OAAO,IAAI;GAE5D,KAAK,MAAM,OAAO,QAAQ,QAAQ,SAAS,GACzC,IAAI,OAAO,OAAO,MAAM,GAAG,GACzB,IAAI,OAAO,KAAK;GAIpB,OAAO,OAAO,OAAO,GAAG;EAC1B,CAAC;CACF;AACH;ACVA,MAAM,+BAAe,IAAI,QAAO;AAEhC,SAAgB,kBACd,QACA,IAAM;CAEN,MAAM,cAAc,OAAO,OAAO,CAAA,CAAE;CAEpC,aAAa,IAAI,aAAa;EAAE;EAAQ;CAAE,CAAE;CAE5C,OAAO;AACT;AA+LA,SAAgB,cAAc,YAAoB;CAChD,MAAM,SAAS,cAAc,UAAU,CAAC,CAAC;CACzC,MAAM,UAAmC,CAAA;CAEzC,KAAK,MAAM,OAAO;EAAC;EAAa;EAAQ;EAAiB;CAAuB,GAC9E,IAAI,OAAO,OAAO,QAAQ,GAAG,GAC3B,QAAQ,OAAO,OAAO;CAI1B,OAAO;AACT;AAEA,SAAgB,qBAMd,KAAe,IAAU;CACzB,MAAM,SAAS,OAAO,MAAM;CAC5B,MAAM,OAAO,aAAa,KAAK,EAAE;CAEjC,SAAS,IAAI,MAAwB;EACnC,IAAI,KAAK,WAAW,KAAK,KAAK,WAAW,GACvC,MAAM,IAAI,UAAU,sDAAsD;EAG5E,IAAI,SAAS;EACb,IAAI,SAAS,KAAK;EAElB,IAAI,KAAK,WAAW,GAAG;GACrB,MAAM,WAAW,KAAK;GAEtB,SAAS,KAAK;GAEd,IAAI,OAAO,aAAa,UACtB;QAAI,aAAa,KAAK;KACpB,IAAI,OAAO,OAAO,IAAI,UAAU,QAAQ,GACtC,SAAS,IAAI,SAAS;UACjB,IAAI,aAAa,QAAQ,OAAO,OAAO,IAAI,SAAS,EAAE,GAC3D,SAAS,IAAI,QAAQ;UAErB,MAAM,IAAI,UAAU,+BAA+B,UAAU;IAEjE;UACK;IACL,IAAI,OAAO,aAAa,YAAY,aAAa,MAC/C,MAAM,IAAI,UAAU,8DAA8D;IAGpF,MAAM,cAAc,aAAa,IAAI,QAAQ;IAE7C,IACE,CAAC,eACD,YAAY,OAAM,MAAO,OACzB,CAAC,OAAO,OAAO,IAAI,SAAS,YAAY,EAAE,GAE1C,MAAM,IAAI,UAAU,oDAAoD;IAG1E,SAAS,YAAY;GACvB;EACF;EAEA,OAAO,UAAU,IAAI,aAAa,OAAO,CAAA,GAAI,MAA6B;CAC5E;CAEA,OAAO;EACL,QAAQ,KAAK;EACb,QAAQ,KAAK;EACb,MAAM,KAAK;EACX,QAAQ,GAAG,SAAoB,OAAO,SAAS,IAAI,IAAI,CAAC;EAOxD,YAAY,GAAG,SAAoB,OAAO,cAAc,IAAI,IAAI,CAAC;CAOlE;AACH"}
1
+ {"version":3,"file":"loozo-angular-router-_generated.mjs","names":[],"sources":["../../_generated/src/inputs.ts","../../_generated/src/callbacks.ts","../../_generated/src/handle.ts","../../_generated/src/active.ts","../../_generated/src/index.ts"],"sourcesContent":["import type { ActivatedRouteSnapshot, PartialMatchRouteSnapshot } from \"@angular/router\";\nimport {\n codecFallback,\n isArrayCodec,\n routeMetadata,\n type AnyRoute,\n} from \"@loozo/angular-router/_core\";\n\nexport interface RecordBinding {\n readonly definition: () => AnyRoute;\n readonly owners: readonly string[];\n}\nexport interface RouteSet {\n readonly records: Readonly<Record<string, RecordBinding>>;\n readonly destinations: Readonly<Record<string, () => AnyRoute>>;\n readonly absolute: Readonly<Record<string, string>>;\n readonly parents: Readonly<Record<string, string>>;\n}\nexport interface Parsed {\n readonly params: Readonly<Record<string, unknown>>;\n readonly search: Readonly<Record<string, unknown>>;\n}\nexport interface Job {\n readonly key: string;\n readonly raws: readonly string[];\n readonly array: boolean;\n readonly codec: { parse(raw: string): unknown };\n}\n\n/** The generated declaration identity carried as the record's own route data, if any. */\nexport function routeIdentity(snapshot: Pick<ActivatedRouteSnapshot, \"routeConfig\">): unknown {\n const data = snapshot.routeConfig?.data;\n\n if (!data || !Object.hasOwn(data, \"__loozoAngularRouterRouteId\")) {\n return undefined;\n }\n\n return data.__loozoAngularRouterRouteId;\n}\n\nexport function hasIdentity(\n snapshot: Pick<ActivatedRouteSnapshot, \"routeConfig\">,\n id: string,\n): boolean {\n return routeIdentity(snapshot) === id;\n}\n\nexport function assertIdentity(\n snapshot: Pick<ActivatedRouteSnapshot, \"routeConfig\">,\n id: string,\n): void {\n if (!hasIdentity(snapshot, id)) {\n throw new Error(`Generated route wiring identity mismatch: ${id}`);\n }\n}\n\n// Private: input errors are Errors with source/key/reason, not an exported class.\nclass InputError extends Error {\n readonly source = \"path\";\n\n constructor(\n readonly key: string,\n readonly reason: \"missing\" | \"repeated\" | \"invalid\",\n cause?: unknown,\n ) {\n super(`Invalid path input ${key}: ${reason}`, { cause });\n }\n}\n\nexport function validateRaw(\n key: string,\n raws: readonly unknown[],\n array: boolean,\n): asserts raws is readonly string[] {\n if (!array && raws.length === 0) {\n throw new InputError(key, \"missing\");\n }\n\n if (!array && raws.length > 1) {\n throw new InputError(key, \"repeated\");\n }\n\n if (raws.some((raw) => typeof raw !== \"string\")) {\n throw new InputError(key, \"invalid\", new TypeError(\"Raw input must be a decoded string\"));\n }\n}\n\n/**\n * Runs one parser. A throw is invalid input; a thenable is a programming error,\n * thrown as a TypeError so it fails the navigation instead of reading as invalid.\n */\nfunction parse(codec: Job[\"codec\"], raw: string): { value: unknown } | { cause: unknown } {\n let value: unknown;\n\n try {\n value = codec.parse(raw);\n } catch (cause) {\n return { cause };\n }\n\n if (\n value !== null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n ) {\n // Observe the stray result so its rejection can't surface as unhandled.\n Promise.resolve(value).catch(() => undefined);\n\n throw new TypeError(\"ParamCodec.parse must return its value synchronously, not a Promise\");\n }\n\n return { value };\n}\n\nexport function pathJobs(definition: AnyRoute, url: readonly { path: string }[]): Job[] {\n const metadata = routeMetadata(definition);\n const local = new Map<string, Job>();\n\n for (let index = 0; index < metadata.localSegments.length; index += 1) {\n const segment = metadata.localSegments[index];\n\n if (segment.kind === \"static\") {\n continue;\n }\n\n let raws: unknown[] = [];\n const array = segment.kind === \"catchall\";\n\n if (array) {\n raws = url.slice(index).map((value) => value.path);\n } else if (index < url.length) {\n raws = [url[index].path];\n }\n\n validateRaw(segment.name, raws, array);\n // Repeated local scalars intentionally use Angular's last positional occurrence.\n local.set(segment.name, {\n key: segment.name,\n raws,\n array,\n codec: metadata.localParams[segment.name],\n });\n }\n\n return [...local.values()];\n}\n\n/** Parses prechecked path jobs; the first codec failure is an input error. */\nexport function runPathJobs(jobs: readonly Job[]): Readonly<Record<string, unknown>> {\n const params: Record<string, unknown> = Object.create(null);\n\n for (const job of jobs) {\n const values: unknown[] = [];\n\n for (const raw of job.raws) {\n const result = parse(job.codec, raw);\n\n if (\"cause\" in result) {\n throw new InputError(job.key, \"invalid\", result.cause);\n }\n\n values.push(result.value);\n }\n\n if (job.array) {\n params[job.key] = Object.freeze(values);\n } else {\n params[job.key] = values[0];\n }\n }\n\n return Object.freeze(params);\n}\n\n/**\n * Parses a declaration's effective search. It never fails on input: a missing,\n * repeated or invalid value reads as the key's fallback, or leaves it absent.\n * An array key reads every value, or `[]` when it is missing or any value is invalid.\n */\nexport function parseSearch(\n definition: AnyRoute,\n snapshot: Pick<ActivatedRouteSnapshot, \"queryParamMap\">,\n): Readonly<Record<string, unknown>> {\n const metadata = routeMetadata(definition);\n const search: Record<string, unknown> = Object.create(null);\n\n for (const key of Object.keys(metadata.search)) {\n const codec = metadata.search[key];\n const raws: unknown[] = snapshot.queryParamMap.getAll(key);\n\n if (isArrayCodec(codec)) {\n search[key] = parseArray(codec, raws);\n\n continue;\n }\n\n if (raws.length === 1 && typeof raws[0] === \"string\") {\n const result = parse(codec, raws[0]);\n\n if (\"value\" in result) {\n search[key] = result.value;\n\n continue;\n }\n }\n\n const fallback = codecFallback(codec);\n\n if (fallback) {\n search[key] = fallback.value;\n }\n }\n\n return Object.freeze(search);\n}\n\n// One invalid value empties the whole list, as an invalid scalar reads as absent.\nfunction parseArray(codec: Job[\"codec\"], raws: readonly unknown[]): readonly unknown[] {\n const values: unknown[] = [];\n\n for (const raw of raws) {\n if (typeof raw !== \"string\") {\n return Object.freeze([]);\n }\n\n const result = parse(codec, raw);\n\n if (\"cause\" in result) {\n return Object.freeze([]);\n }\n\n values.push(result.value);\n }\n\n return Object.freeze(values);\n}\n\ntype Outcome<Value> = { readonly value: Value } | { readonly error: unknown };\ntype Cache<Value> = WeakMap<object, WeakMap<object, Outcome<Value>>>;\n\nconst full: Cache<Parsed> = new WeakMap();\nconst paths: Cache<Readonly<Record<string, unknown>>> = new WeakMap();\nconst matching: Cache<Parsed | undefined> = new WeakMap();\n\n// One result or failure per exact declaration and snapshot, shared by every reader.\nfunction memo<Value>(\n cache: Cache<Value>,\n definition: AnyRoute,\n snapshot: object,\n run: () => Value,\n): Value {\n let snapshots = cache.get(definition);\n\n if (!snapshots) {\n snapshots = new WeakMap();\n cache.set(definition, snapshots);\n }\n\n let outcome = snapshots.get(snapshot);\n\n if (!outcome) {\n try {\n outcome = { value: run() };\n } catch (error) {\n outcome = { error };\n }\n\n snapshots.set(snapshot, outcome);\n }\n\n if (\"error\" in outcome) {\n throw outcome.error;\n }\n\n return outcome.value;\n}\n\n/** The effective inputs of a complete snapshot, shared by guards, resolvers and handles. */\nexport function parseInputs(set: RouteSet, id: string, snapshot: ActivatedRouteSnapshot): Parsed {\n assertIdentity(snapshot, id);\n\n const binding = set.records[id];\n\n return memo(full, binding.definition(), snapshot, () => {\n const jobs: Job[] = [];\n\n for (const ownerId of binding.owners) {\n const definition = set.records[ownerId].definition();\n\n // Pathless/static owners contribute no positional input. Effective search belongs\n // to this complete future record; inherited data is never positional authority.\n if (!routeMetadata(definition).localSegments.some((segment) => segment.kind !== \"static\")) {\n continue;\n }\n\n const owner = [...snapshot.pathFromRoot]\n .reverse()\n .find((record) => hasIdentity(record, ownerId));\n\n if (!owner) {\n throw new Error(`Generated route ancestor missing: ${ownerId}`);\n }\n\n assertIdentity(owner, ownerId);\n jobs.push(...pathJobs(definition, owner.url));\n }\n\n const params = runPathJobs(jobs);\n\n return Object.freeze({ params, search: parseSearch(binding.definition(), snapshot) });\n });\n}\n\n/**\n * Parses a matching candidate's own path params only. Angular passes one partial\n * snapshot to all of a candidate's canMatch guards, so they share this result.\n *\n * @returns An input error when a param is invalid; throws only programming errors.\n */\nexport function pathInputs(\n set: RouteSet,\n id: string,\n snapshot: PartialMatchRouteSnapshot,\n): { params: Readonly<Record<string, unknown>> } | { error: Error } {\n assertIdentity(snapshot, id);\n\n const definition = set.records[id].definition();\n\n try {\n return {\n params: memo(paths, definition, snapshot, () =>\n runPathJobs(pathJobs(definition, snapshot.url)),\n ),\n };\n } catch (error) {\n if (error instanceof InputError) {\n return { error };\n }\n\n throw error;\n }\n}\n\n/**\n * Local path params plus effective search for a candidate's canMatch guards, the\n * generated one included, or undefined when the path params fail: the generated\n * guard owns that outcome.\n */\nexport function matchingInputs(\n set: RouteSet,\n id: string,\n snapshot: PartialMatchRouteSnapshot,\n): Parsed | undefined {\n const definition = set.records[id].definition();\n\n return memo(matching, definition, snapshot, () => {\n const path = pathInputs(set, id, snapshot);\n\n // Search parses only once the path matches: a candidate the path rules out runs no\n // search codecs.\n if (\"error\" in path) {\n return undefined;\n }\n\n return Object.freeze({ params: path.params, search: parseSearch(definition, snapshot) });\n });\n}\n","import { inject } from \"@angular/core\";\nimport {\n Router,\n RedirectCommand,\n UrlTree,\n type Route,\n type ActivatedRouteSnapshot,\n type RouterStateSnapshot,\n type GuardResult,\n type CanActivateFn,\n type CanActivateChildFn,\n type CanMatchFn,\n type ResolveFn,\n type RedirectFunction,\n} from \"@angular/router\";\nimport { from, isObservable, map, type ObservableInput } from \"rxjs\";\nimport { isArrayCodec, routeMetadata, type AnyRoute } from \"@loozo/angular-router/_core\";\nimport {\n hasIdentity,\n parseInputs,\n matchingInputs,\n pathInputs,\n runPathJobs,\n validateRaw,\n type Job,\n type RouteSet,\n} from \"./inputs.js\";\n\n// Parsing is synchronous, so every adapter calls its callback in the same call Angular\n// made: Angular's injection context is still active and inject() works at entry.\nexport function callbacks(\n set: RouteSet,\n id: string,\n): Pick<Route, \"canActivate\" | \"canActivateChild\" | \"canMatch\" | \"resolve\" | \"title\"> {\n const metadata = routeMetadata(set.records[id].definition());\n const config = metadata.config;\n const result: Pick<Route, \"canActivate\" | \"canActivateChild\" | \"canMatch\" | \"resolve\" | \"title\"> =\n {};\n const activate = config.canActivate as readonly ((...args: any[]) => any)[] | undefined;\n\n if (activate) {\n result.canActivate = activate.map(\n (callback): CanActivateFn =>\n (snapshot, state) =>\n callback(parseInputs(set, id, snapshot), snapshot, state),\n );\n }\n\n const children = config.canActivateChild as readonly ((...args: any[]) => any)[] | undefined;\n\n if (children) {\n result.canActivateChild = children.map(\n (callback): CanActivateChildFn =>\n (child, state) => {\n let parent: ActivatedRouteSnapshot | undefined;\n\n // The native ancestry includes child itself; only proper ancestors can declare this guard.\n for (let index = child.pathFromRoot.length - 2; index >= 0; index -= 1) {\n const ancestor = child.pathFromRoot[index];\n\n if (hasIdentity(ancestor, id)) {\n parent = ancestor;\n\n break;\n }\n }\n\n if (!parent) {\n throw new Error(`Generated guard ancestor missing: ${id}`);\n }\n\n return callback(parseInputs(set, id, parent), child, state);\n },\n );\n }\n\n const matching = config.canMatch as readonly ((...args: any[]) => any)[] | undefined;\n const canMatch: CanMatchFn[] = [];\n\n // Local path params decide whether this record matches. Angular runs every canMatch\n // guard concurrently and prefers the first non-true result in array order.\n if (\n metadata.localSegments.some((segment) => segment.kind !== \"static\") ||\n Object.keys(metadata.search).length > 0\n ) {\n const handler = config.onInvalidParams as ((...args: any[]) => unknown) | undefined;\n\n canMatch.push((route, segments, snapshot) => {\n const path = pathInputs(set, id, snapshot);\n\n if (!(\"error\" in path)) {\n // Search never decides the match, but Angular runs this guard on every navigation,\n // query-only ones included, so a thenable search parse fails it here.\n matchingInputs(set, id, snapshot);\n\n return true;\n }\n\n if (!handler) {\n return false;\n }\n\n const decision = handler(path.error, route, segments, snapshot);\n\n if (isObservable(decision) || isPromiseResult(decision)) {\n return from(decision as ObservableInput<unknown>).pipe(map(invalidParamsResult));\n }\n\n return invalidParamsResult(decision);\n });\n }\n\n if (matching) {\n for (const callback of matching) {\n canMatch.push((route, segments, snapshot) => {\n const inputs = matchingInputs(set, id, snapshot);\n\n // Invalid path params: the generated guard ahead of this one decides.\n if (inputs === undefined) {\n return false;\n }\n\n return callback(inputs, route, segments, snapshot);\n });\n }\n }\n\n if (canMatch.length > 0) {\n result.canMatch = canMatch;\n }\n\n const resolvers = config.resolve as\n | Record<\n PropertyKey,\n (inputs: unknown, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => any\n >\n | undefined;\n\n if (resolvers) {\n const resolve: Record<PropertyKey, ResolveFn<unknown>> = Object.create(null);\n\n for (const key of Reflect.ownKeys(resolvers)) {\n resolve[key] = (snapshot, state) =>\n resolvers[key](parseInputs(set, id, snapshot), snapshot, state);\n }\n\n result.resolve = resolve;\n }\n\n const title = config.title as\n | string\n | ((inputs: unknown, snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) => any)\n | undefined;\n\n // Angular runs a title callback as a resolver, so it's adapted like one.\n if (typeof title === \"function\") {\n result.title = (snapshot, state) => title(parseInputs(set, id, snapshot), snapshot, state);\n } else if (title !== undefined) {\n result.title = title;\n }\n\n return result;\n}\n\nfunction isPromiseResult(value: unknown): value is PromiseLike<unknown> {\n return !!value && typeof (value as PromiseLike<unknown>).then === \"function\";\n}\n\n// Invalid params can't match, so a handler may only refuse or redirect.\nfunction invalidParamsResult(decision: unknown): GuardResult {\n if (decision === false || decision instanceof UrlTree || decision instanceof RedirectCommand) {\n return decision;\n }\n\n throw new TypeError(\"onInvalidParams must return false, a UrlTree or a RedirectCommand\");\n}\n\nexport function redirect(definition: () => AnyRoute): RedirectFunction {\n return (snapshot) => {\n const router = inject(Router);\n const source = routeMetadata(definition());\n const config = source.config.redirect as {\n to: AnyRoute;\n params: Record<string, string>;\n search: { mode: string; values?: Record<string, unknown> };\n fragment: { mode: string; value?: string };\n };\n const target = routeMetadata(config.to);\n const raw = new Map<string, { values: string[]; array: boolean }>();\n\n for (let index = 0; index < source.segments.length; index += 1) {\n const segment = source.segments[index];\n\n if (segment.kind === \"scalar\") {\n raw.set(segment.name, {\n values: snapshot.url.slice(index, index + 1).map((value) => value.path),\n array: false,\n });\n } else if (segment.kind === \"catchall\") {\n raw.set(segment.name, {\n values: snapshot.url.slice(index).map((value) => value.path),\n array: true,\n });\n }\n }\n\n const jobs: Job[] = [];\n\n // Only mapped source params are parsed, each cardinality checked before any parser runs.\n for (const key of new Set(Object.values(config.params))) {\n const input = raw.get(key);\n const raws = input?.values ?? [];\n const array = input?.array ?? false;\n\n validateRaw(key, raws, array);\n jobs.push({ key, raws, array, codec: source.params[key] });\n }\n\n // A failure fails the navigation: a redirect record has no matching guard to fall through.\n const logical = runPathJobs(jobs);\n const params: Record<string, unknown> = Object.create(null);\n\n for (const [key, sourceKey] of Object.entries(config.params)) {\n params[key] = logical[sourceKey];\n }\n\n const path = createUrlForPath(config.to, params);\n const replacement: Record<string, string | string[]> = Object.create(null);\n\n if (config.search.mode === \"replace\") {\n const values = config.search.values!;\n\n for (const key of Object.keys(target.search).sort()) {\n if (!Object.hasOwn(values, key) || values[key] === undefined) {\n continue;\n }\n\n const codec = target.search[key];\n\n if (!isArrayCodec(codec)) {\n replacement[key] = stringifySearch(codec, values[key], key);\n\n continue;\n }\n\n const raws: string[] = [];\n\n for (const value of values[key] as readonly unknown[]) {\n raws.push(stringifySearch(codec, value, key));\n }\n\n // Angular writes nothing for an empty array; leave the key out to match createUrl.\n if (raws.length > 0) {\n replacement[key] = raws;\n }\n }\n }\n\n // No native conversion before source codecs and destination stringification succeed.\n const tree = router.parseUrl(path);\n\n if (config.search.mode === \"preserve\") {\n tree.queryParams = { ...snapshot.queryParams };\n } else if (config.search.mode === \"replace\") {\n tree.queryParams = replacement;\n }\n\n if (config.fragment.mode === \"preserve\") {\n tree.fragment = snapshot.fragment;\n } else if (config.fragment.mode === \"replace\") {\n tree.fragment = config.fragment.value!;\n }\n\n return tree;\n };\n}\n\nfunction stringifySearch(\n codec: { stringify(value: unknown): unknown },\n value: unknown,\n key: string,\n): string {\n const raw = codec.stringify(value);\n\n if (typeof raw !== \"string\") {\n throw new TypeError(`Redirect search codec must stringify to a string: ${key}`);\n }\n\n return raw;\n}\n\nfunction createUrlForPath(definition: AnyRoute, params: Record<string, unknown>): string {\n const metadata = routeMetadata(definition);\n const parts: string[] = [];\n\n function encode(raw: string): string {\n if (raw === \"\" || raw === \".\" || raw === \"..\") {\n throw new TypeError(\"Invalid redirect path value\");\n }\n\n return encodeURIComponent(raw).replace(/\\(/g, \"%28\").replace(/\\)/g, \"%29\");\n }\n\n for (const segment of metadata.segments) {\n if (segment.kind === \"static\") {\n parts.push(encode(segment.text));\n } else if (segment.kind === \"scalar\") {\n const raw = metadata.params[segment.name].stringify(params[segment.name]);\n\n if (typeof raw !== \"string\") {\n throw new TypeError(\"Redirect codec must stringify to a string\");\n }\n\n parts.push(encode(raw));\n } else {\n const values = params[segment.name];\n\n if (!Array.isArray(values)) {\n throw new TypeError(\"Redirect catchall requires an array\");\n }\n\n for (const value of values) {\n const raw = metadata.params[segment.name].stringify(value);\n\n if (typeof raw !== \"string\") {\n throw new TypeError(\"Redirect codec must stringify to a string\");\n }\n\n parts.push(encode(raw));\n }\n }\n }\n\n return `/${parts.join(\"/\")}`;\n}\n","import { computed, signal, type Signal } from \"@angular/core\";\nimport type { ActivatedRoute, ActivatedRouteSnapshot, Router } from \"@angular/router\";\nimport { routeMetadata } from \"@loozo/angular-router/_core\";\nimport { merge } from \"rxjs\";\nimport { assertIdentity, hasIdentity, parseInputs, type RouteSet } from \"./inputs.js\";\n\nexport interface RouteView {\n readonly params: Signal<Readonly<Record<string, unknown>>>;\n readonly search: Signal<Readonly<Record<string, unknown>>>;\n readonly data: Signal<Readonly<Record<PropertyKey, unknown>>>;\n readonly title: Signal<string | undefined>;\n}\n\n/**\n * Signals of the route's committed snapshot. Angular replaces ActivatedRoute.snapshot\n * only when a navigation activates, so a pending, cancelled or failed navigation\n * leaves the values alone; params, search, data and title change together, from one snapshot.\n * The caller owns the lifetime: `teardown` receives the function that stops following\n * the route, after which the signals keep their last values.\n */\nexport function observeRoute(\n set: RouteSet,\n id: string,\n activated: ActivatedRoute,\n router: Router,\n teardown: (release: () => void) => void,\n): RouteView {\n assertIdentity(activated.snapshot, id);\n\n const snapshot = signal<ActivatedRouteSnapshot>(activated.snapshot);\n // Activation replaces the snapshot, then emits the route's changed observables, then\n // ActivationEnd. Following both keeps reads current inside subscribers registered after\n // the handle; router events cover a new snapshot whose values are all equal.\n const subscription = merge(\n activated.queryParams,\n activated.fragment,\n activated.params,\n activated.url,\n activated.data,\n router.events,\n ).subscribe(() => {\n snapshot.set(activated.snapshot);\n });\n\n teardown(() => subscription.unsubscribe());\n\n // Shares the parse of guards and resolvers for the same snapshot. An error here\n // means a codec rejected a value it accepted while matching, and reading rethrows it.\n const inputs = computed(() => parseInputs(set, id, snapshot()));\n\n return {\n params: computed(() => inputs().params),\n search: computed(() => inputs().search),\n data: computed(() => effectiveData(set, id, snapshot())),\n title: computed(() => effectiveTitle(set, id, snapshot())),\n };\n}\n\n/**\n * The resolver results of the route's whole chain. Each owner's keys come from its\n * own record of the snapshot, so the result doesn't depend on Angular's\n * paramsInheritanceStrategy; nearer owners overwrite outer ones.\n */\nfunction effectiveData(\n set: RouteSet,\n id: string,\n snapshot: ActivatedRouteSnapshot,\n): Readonly<Record<PropertyKey, unknown>> {\n const data: Record<PropertyKey, unknown> = Object.create(null);\n\n for (const ownerId of set.records[id].owners) {\n const resolvers = routeMetadata(set.records[ownerId].definition()).config.resolve;\n\n if (!resolvers) {\n continue;\n }\n\n const owner = snapshot.pathFromRoot.findLast((record) => hasIdentity(record, ownerId));\n\n if (!owner) {\n throw new Error(`Generated route ancestor missing: ${ownerId}`);\n }\n\n for (const key of Reflect.ownKeys(resolvers)) {\n if (Object.hasOwn(owner.data, key)) {\n data[key] = owner.data[key];\n }\n }\n }\n\n return Object.freeze(data);\n}\n\n/**\n * The resolved title of the nearest declaration in the route's chain that declares one,\n * read from that declaration's own record of the snapshot, so the result doesn't depend\n * on Angular's paramsInheritanceStrategy.\n */\nfunction effectiveTitle(\n set: RouteSet,\n id: string,\n snapshot: ActivatedRouteSnapshot,\n): string | undefined {\n const owners = set.records[id].owners;\n\n for (let index = owners.length - 1; index >= 0; index -= 1) {\n const ownerId = owners[index];\n\n if (routeMetadata(set.records[ownerId].definition()).config.title === undefined) {\n continue;\n }\n\n const owner = snapshot.pathFromRoot.findLast((record) => hasIdentity(record, ownerId));\n\n if (!owner) {\n throw new Error(`Generated route ancestor missing: ${ownerId}`);\n }\n\n return owner.title;\n }\n\n return undefined;\n}\n","import { DestroyRef, computed, inject, untracked, type Signal } from \"@angular/core\";\nimport {\n NavigationEnd,\n Router,\n type ActivatedRoute,\n type ActivatedRouteSnapshot,\n type Navigation,\n} from \"@angular/router\";\nimport type { AnyRoute } from \"@loozo/angular-router/_core\";\nimport { observeRoute } from \"./handle.js\";\nimport { createHandle, destinationId, type Handle } from \"./index.js\";\nimport { routeIdentity, type RouteSet } from \"./inputs.js\";\n\ninterface ActiveEntryHandle extends Handle<AnyRoute> {\n is(destination: unknown): boolean;\n contains(destination: unknown): boolean;\n readonly chain: readonly ActiveEntryHandle[];\n}\n\ninterface Entry {\n readonly handle: ActiveEntryHandle;\n readonly parent: Entry | undefined;\n /** The declarations of the chain, top-level first, ending with this one. */\n readonly ids: readonly string[];\n readonly release: () => void;\n}\n\n/**\n * One reader of the deepest active primary-outlet route of the set.\n *\n * @remarks\n * The selection changes only after a navigation commits: the computed tracks\n * `lastSuccessfulNavigation`, and a reader first read before its tree has committed\n * returns undefined until the running navigation ends. The reader reads itself at each\n * `NavigationEnd`, so it reconciles every commit even without a live consumer. It owns\n * one entry per active ActivatedRoute, reused while that record stays in the chain;\n * departed entries are released at the commit and keep their last values. Destroying\n * the injection context releases the rest; the reader then keeps its last selection.\n */\nexport function injectGeneratedActiveRoute<Active>(set: RouteSet): Signal<Active | undefined> {\n const router = inject(Router);\n const destroyRef = inject(DestroyRef);\n const entries = new Map<ActivatedRoute, Entry>();\n let destroyed = false;\n let selected: ActiveEntryHandle | undefined;\n\n function createEntry(route: ActivatedRoute, id: string, parent: Entry | undefined): Entry {\n const releases: (() => void)[] = [];\n const view = observeRoute(set, id, route, router, (release) => {\n releases.push(release);\n });\n const ids = [...(parent?.ids ?? []), id];\n const chain: ActiveEntryHandle[] = [...(parent?.handle.chain ?? [])];\n const base: Handle<AnyRoute> = createHandle(set, id, router, view);\n const handle: ActiveEntryHandle = {\n ...base,\n is: (destination) => destinationId(set, destination) === id,\n contains: (destination) => ids.includes(destinationId(set, destination)),\n chain,\n };\n\n chain.push(handle);\n Object.freeze(chain);\n\n return {\n handle,\n parent,\n ids,\n release: () => {\n for (const release of releases) {\n release();\n }\n },\n };\n }\n\n // Reuses each entry whose record and parent entry are unchanged, then releases the rest.\n function reconcile(live: readonly ActivatedRoute[]): ActiveEntryHandle | undefined {\n const kept = new Set<ActivatedRoute>();\n let parent: Entry | undefined;\n\n for (const route of live) {\n const id = routeIdentity(route);\n\n if (typeof id !== \"string\" || !Object.hasOwn(set.records, id)) {\n continue;\n }\n\n let entry = entries.get(route);\n\n if (entry && entry.parent !== parent) {\n entry.release();\n entry = undefined;\n }\n\n if (!entry) {\n entry = createEntry(route, id, parent);\n entries.set(route, entry);\n }\n\n kept.add(route);\n parent = entry;\n }\n\n for (const [route, entry] of entries) {\n if (!kept.has(route)) {\n entry.release();\n entries.delete(route);\n }\n }\n\n return parent?.handle;\n }\n\n const selection = computed(() => {\n // Reads no signal, so a destroyed reader never recomputes or subscribes again.\n if (destroyed) {\n return selected as Active | undefined;\n }\n\n const success = router.lastSuccessfulNavigation();\n const live = untracked(() => committedChain(router, success));\n\n if (!live) {\n // Wait for the running navigation to end, whether it succeeds, cancels or fails.\n router.currentNavigation();\n selected = undefined;\n\n return undefined;\n }\n\n // Subscriptions start here, outside the reactive context.\n selected = untracked(() => reconcile(live));\n\n return selected as Active | undefined;\n });\n\n // Without this, a reader nobody read at a commit would first recompute during a later\n // activation, against a tree that hasn't committed yet.\n const commits = router.events.subscribe((event) => {\n if (event instanceof NavigationEnd) {\n selection();\n }\n });\n\n destroyRef.onDestroy(() => {\n destroyed = true;\n commits.unsubscribe();\n\n for (const entry of entries.values()) {\n entry.release();\n }\n\n entries.clear();\n });\n\n return selection;\n}\n\n/**\n * The live primary chain, root first, when it is the committed tree; otherwise undefined.\n *\n * @remarks\n * Angular replaces the router state and advances each route's snapshot during\n * activation, before the navigation succeeds. The tree is committed when every live\n * route holds the state's snapshot at its depth, and either no navigation runs or the\n * state is the last successful navigation's final URL.\n */\nfunction committedChain(router: Router, success: Navigation | null): ActivatedRoute[] | undefined {\n const state = router.routerState;\n const live: ActivatedRoute[] = [];\n let route: ActivatedRoute | null = state.root;\n let snapshot: ActivatedRouteSnapshot | null = state.snapshot.root;\n\n while (route) {\n if (route.snapshot !== snapshot) {\n return undefined;\n }\n\n live.push(route);\n route = route.firstChild;\n snapshot = snapshot.firstChild;\n }\n\n if (snapshot !== null) {\n return undefined;\n }\n\n if (router.currentNavigation() === null) {\n return live;\n }\n\n if (success?.finalUrl && router.serializeUrl(success.finalUrl) === state.snapshot.url) {\n return live;\n }\n\n return undefined;\n}\n","export { callbacks, redirect } from \"./callbacks.js\";\nexport { assertIdentity, parseInputs } from \"./inputs.js\";\nexport type { RecordBinding, RouteSet } from \"./inputs.js\";\nexport { injectGeneratedActiveRoute } from \"./active.js\";\n\nimport { DestroyRef, inject, type Signal } from \"@angular/core\";\nimport {\n ActivatedRoute,\n Router,\n type NavigationBehaviorOptions,\n type UrlTree,\n} from \"@angular/router\";\nimport {\n createUrl,\n routeMetadata,\n type AnyRoute,\n type ParamsOf,\n type SearchOf,\n type UrlValues,\n type PathlessUrlValues,\n type PathUrlValues,\n type SearchFieldOf,\n type FragmentsOf,\n type TitleOf,\n} from \"@loozo/angular-router/_core\";\nimport { observeRoute, type RouteView } from \"./handle.js\";\nimport type { RouteSet } from \"./inputs.js\";\n\ndeclare const destinationBrand: unique symbol;\n\n/**\n * An inert nominal reference exported as `destination` by a route-local facade.\n *\n * @remarks\n * Pass the exported object as `to` in a handle's link/navigate record; no injection\n * context or target activation is needed. It selects its exact declaration,\n * not a canonical pathless winner. Only references from the caller's generated\n * set are accepted. Copies, spreads, fakes and authored routes are not references.\n * Do not construct this type or pass it to the root createUrl function.\n *\n * @typeParam Target - The referenced route's input and fragment schema.\n * @typeParam Set - The generator's nominal route-set identity.\n * @typeParam Id - The exact generated declaration identity.\n */\nexport declare class Destination<\n Target extends AnyRoute,\n Set = unknown,\n Id extends string = string,\n> {\n // Type-only identity, intentionally not read; object spreads must lose this member.\n // oxlint-disable-next-line no-unused-private-class-members\n readonly #nominal: void;\n /** Type-only nominal brand carrying the reference's identity; not populated at runtime. */\n readonly [destinationBrand]: {\n /** Type-only: the referenced route's input and fragment schema. */\n readonly target: Target;\n /** Type-only: the generator's nominal route-set identity. */\n readonly set: Set;\n /** Type-only: the exact generated declaration identity. */\n readonly id: Id;\n };\n}\n\nconst destinations = new WeakMap<object, { getSet: () => RouteSet; id: string }>();\n\nexport function createDestination<Target extends AnyRoute, Set, Id extends string>(\n getSet: () => RouteSet,\n id: Id,\n): Destination<Target, Set, Id> {\n const destination = Object.freeze({});\n\n destinations.set(destination, { getSet, id });\n\n return destination as Destination<Target, Set, Id>;\n}\n\n/** The declaration a genuine destination of this set references. */\nexport function destinationId(set: RouteSet, destination: unknown): string {\n let identity;\n\n if (typeof destination === \"object\" && destination !== null) {\n identity = destinations.get(destination);\n }\n\n if (!identity || identity.getSet() !== set || !Object.hasOwn(set.records, identity.id)) {\n throw new TypeError(\"Expected a destination from the same generated set\");\n }\n\n return identity.id;\n}\n\ntype DataOf<Target> = Target extends { readonly __data?: infer Data } ? Readonly<Data> : never;\ntype IsUnion<Value, Whole = Value> = Value extends unknown\n ? [Whole] extends [Value]\n ? false\n : true\n : never;\n\n// Own-route records are single object types, not intersections, so a bare\n// string cannot match through String's own `search` method (see UrlValues).\ninterface PathlessOwnLink<Search, Fragment> extends PathlessUrlValues<Search, Fragment> {\n /** Omitted: this exact declaration, not its pathless winner. */\n readonly to?: undefined;\n}\ninterface PathOwnLink<Params, Search, Fragment> extends PathUrlValues<Params, Search, Fragment> {\n /** Omitted: this exact declaration, not its pathless winner. */\n readonly to?: undefined;\n}\ninterface PathlessOwnNavigation<Search, Fragment>\n extends PathlessOwnLink<Search, Fragment>, NavigationBehaviorOptions {}\ninterface PathOwnNavigation<Params, Search, Fragment>\n extends PathOwnLink<Params, Search, Fragment>, NavigationBehaviorOptions {}\ntype OwnLink<\n Target extends AnyRoute,\n Keys extends keyof SearchOf<Target>,\n> = keyof ParamsOf<Target> extends never\n ? PathlessOwnLink<SearchFieldOf<Target, Keys>, FragmentsOf<Target>>\n : PathOwnLink<ParamsOf<Target>, SearchFieldOf<Target, Keys>, FragmentsOf<Target>>;\ntype OwnNavigation<\n Target extends AnyRoute,\n Keys extends keyof SearchOf<Target>,\n> = keyof ParamsOf<Target> extends never\n ? PathlessOwnNavigation<SearchFieldOf<Target, Keys>, FragmentsOf<Target>>\n : PathOwnNavigation<ParamsOf<Target>, SearchFieldOf<Target, Keys>, FragmentsOf<Target>>;\n\n/**\n * The noncallable typed handle returned by a colocated `injectRoute()` facade.\n *\n * @remarks\n * Obtain it in an Angular injection context for the exact generated activated\n * record. Params, search and data are readonly Angular Signals of the route's\n * committed snapshot, with values from the first read. Data holds the resolver\n * results of the route's whole chain, ancestors first, a nearer key hiding an\n * ancestor's key of the same name; native static data and the title stay out.\n * Title is the resolved title of the nearest route in the chain that declares\n * one: `string` when the route or an ancestor declares it, else `undefined`.\n *\n * While a navigation is pending, the signals keep the current snapshot's values;\n * when a navigation commits a new snapshot for this route, they change\n * together. Cancellation, failure or a same-URL skip leaves them unchanged. For\n * pending-navigation UI, read Angular's `Router.currentNavigation()`. There is no\n * resolved-data cache. Reading params or search throws only when a path codec\n * rejects a value it accepted while matching, a programming error.\n *\n * `link` and `navigate` take one target record. Its `to` selects the\n * destination: omitted for this exact declaration, not its pathless winner; a\n * single known absolute authored literal, never a concrete URL or string union;\n * `..` for the parent route, the nearest ancestor with a shorter path or the\n * root for a top-level route; or a same-set generated destination. Literal and\n * `..` selectors use generated canonical targets. `params`, `search` and\n * `fragment` are the destination's complete explicit values; there is no\n * current-value reuse or implicit query merge. For `navigate`, the record's\n * other keys are Angular's `NavigationBehaviorOptions`, passed to\n * `Router.navigateByUrl` as given; `link` takes none, so set them on RouterLink.\n *\n * @typeParam Target - The bound declaration's schema.\n * @typeParam Absolute - Absolute literal selectors and their target schemas.\n * @typeParam Parent - The `..` target's schema, or never when there is none.\n * @typeParam Set - The generator's nominal route-set identity.\n * @typeParam References - Exact declarations addressable by same-set references.\n *\n * @example\n * ```ts\n * import { injectRoute } from \"./route.generated.js\";\n * import { destination as report } from \"../report/route.generated.js\";\n *\n * const route = injectRoute(); // Inside an Angular injection context.\n * const link = route.link({ to: report, params: { reportId: 42 } });\n * // Bind link to Angular RouterLink, or navigate with the same record and behavior options:\n * void route.navigate({ to: report, params: { reportId: 42 }, replaceUrl: true });\n * ```\n */\nexport interface Handle<\n Target extends AnyRoute,\n Absolute extends Record<string, AnyRoute> = {},\n Parent extends AnyRoute = never,\n Set = unknown,\n References extends Record<string, AnyRoute> = {},\n> {\n /** Effective positional params of the committed snapshot; kept while a navigation is pending. */\n readonly params: Signal<Readonly<ParamsOf<Target>>>;\n /** Effective declared search, from the same snapshot as params; absent keys have no value or fallback. */\n readonly search: Signal<Readonly<SearchOf<Target>>>;\n /** Effective resolver results of the route and its ancestors, from the same snapshot as params. */\n readonly data: Signal<DataOf<Target>>;\n /** Resolved title of the nearest route in the chain that declares one, from the same snapshot as params. */\n readonly title: Signal<TitleOf<Target>>;\n /**\n * Creates a native UrlTree for this exact declaration, not its pathless winner, without navigating.\n *\n * @param target - No `to`, and this declaration's explicit params/search and optional own fragment.\n * @returns Router.parseUrl's result, suitable for Angular RouterLink.\n * @throws Synchronous URL validation, encoding or stringifier failures, or a behavior option, before Angular.\n */\n link<Keys extends keyof SearchOf<Target> = never>(target: OwnLink<Target, Keys>): UrlTree;\n /**\n * Creates a UrlTree for a statically admitted absolute authored selector.\n *\n * @param target - `to`, a single known template literal, never a concrete URL, and the selected target's complete explicit values.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronously for unknown selectors, invalid URL values or a behavior option.\n */\n link<\n const Selector extends keyof Absolute & string,\n Keys extends keyof SearchOf<NoInfer<Absolute[Selector]>> = never,\n >(\n target: {\n /** A single known absolute authored selector, never a concrete URL. */\n readonly to: Selector & (IsUnion<Selector> extends true ? never : unknown);\n } & UrlValues<NoInfer<Absolute[Selector]>, Keys>,\n ): UrlTree;\n /**\n * Creates a UrlTree for the parent route, when the bound declaration has one.\n *\n * @param target - `to: \"..\"` and the parent route's complete explicit values.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronously for invalid URL values or a behavior option.\n */\n link<Keys extends keyof SearchOf<Parent> = never>(\n target: {\n /** The parent route: the nearest ancestor with a shorter path, or the root. */\n readonly to: [Parent] extends [never] ? never : \"..\";\n } & UrlValues<Parent, Keys>,\n ): UrlTree;\n /**\n * Creates a UrlTree for a generated reference's own exact declaration.\n *\n * @param target - `to`, a genuine destination exported by a facade in this route set, and the referenced declaration's complete explicit values.\n * @returns Router.parseUrl's result without navigating.\n * @throws Synchronously for foreign/fake references, invalid URL values or a behavior option.\n */\n link<\n const Id extends keyof References & string,\n Keys extends keyof SearchOf<NoInfer<References[Id]>> = never,\n >(\n target: {\n /** A genuine destination exported by a facade in this route set. */\n readonly to: Destination<NoInfer<References[Id]>, Set, Id> &\n (IsUnion<Id> extends true ? never : unknown);\n } & UrlValues<NoInfer<References[Id]>, Keys>,\n ): UrlTree;\n /**\n * Navigates to this exact declaration, not its pathless winner, via Router.navigateByUrl.\n *\n * @param target - No `to`; this declaration's explicit params/search and optional own fragment; and Angular's navigation behavior options.\n * @returns Angular's identical Promise; Angular owns skips, redirects and failures.\n * @throws Synchronous URL validation, encoding or stringifier failures before Angular.\n */\n navigate<Keys extends keyof SearchOf<Target> = never>(\n target: OwnNavigation<Target, Keys>,\n ): Promise<boolean>;\n /**\n * Navigates to a statically admitted absolute authored selector.\n *\n * @param target - `to`, a single known template literal, never a concrete URL; the selected target's complete explicit values; and Angular's navigation behavior options.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronously for unknown selectors or invalid URL values.\n */\n navigate<\n const Selector extends keyof Absolute & string,\n Keys extends keyof SearchOf<NoInfer<Absolute[Selector]>> = never,\n >(\n target: {\n /** A single known absolute authored selector, never a concrete URL. */\n readonly to: Selector & (IsUnion<Selector> extends true ? never : unknown);\n } & UrlValues<NoInfer<Absolute[Selector]>, Keys> &\n NavigationBehaviorOptions,\n ): Promise<boolean>;\n /**\n * Navigates to the parent route, when the bound declaration has one.\n *\n * @param target - `to: \"..\"`, the parent route's complete explicit values and Angular's navigation behavior options.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronously for invalid URL values.\n */\n navigate<Keys extends keyof SearchOf<Parent> = never>(\n target: {\n /** The parent route: the nearest ancestor with a shorter path, or the root. */\n readonly to: [Parent] extends [never] ? never : \"..\";\n } & UrlValues<Parent, Keys> &\n NavigationBehaviorOptions,\n ): Promise<boolean>;\n /**\n * Navigates to a generated reference's own exact declaration.\n *\n * @param target - `to`, a genuine destination exported by a facade in this route set; the referenced declaration's complete explicit values; and Angular's navigation behavior options.\n * @returns Angular's identical Router.navigateByUrl Promise.\n * @throws Synchronously for foreign/fake references or invalid URL values.\n */\n navigate<\n const Id extends keyof References & string,\n Keys extends keyof SearchOf<NoInfer<References[Id]>> = never,\n >(\n target: {\n /** A genuine destination exported by a facade in this route set. */\n readonly to: Destination<NoInfer<References[Id]>, Set, Id> &\n (IsUnion<Id> extends true ? never : unknown);\n } & UrlValues<NoInfer<References[Id]>, Keys> &\n NavigationBehaviorOptions,\n ): Promise<boolean>;\n}\n\nexport function nativeOptions(definition: AnyRoute): Record<string, unknown> {\n const config = routeMetadata(definition).config;\n const options: Record<string, unknown> = {};\n\n for (const key of [\"providers\", \"data\", \"canDeactivate\", \"runGuardsAndResolvers\"]) {\n if (Object.hasOwn(config, key)) {\n options[key] = config[key];\n }\n }\n\n return options;\n}\n\nexport function injectGeneratedRoute<\n Target extends AnyRoute,\n Absolute extends Record<string, AnyRoute>,\n Parent extends AnyRoute,\n Set,\n References extends Record<string, AnyRoute>,\n>(set: RouteSet, id: string): Handle<Target, Absolute, Parent, Set, References> {\n const router = inject(Router);\n const destroy = inject(DestroyRef);\n const view = observeRoute(set, id, inject(ActivatedRoute), router, (release) => {\n destroy.onDestroy(release);\n });\n\n return createHandle(set, id, router, view);\n}\n\n/** A declaration's handle over the signals of one activated record. */\nexport function createHandle<\n Target extends AnyRoute,\n Absolute extends Record<string, AnyRoute>,\n Parent extends AnyRoute,\n Set,\n References extends Record<string, AnyRoute>,\n>(\n set: RouteSet,\n id: string,\n router: Router,\n view: RouteView,\n): Handle<Target, Absolute, Parent, Set, References> {\n // Splits the one target record into its destination, URL values and behavior options.\n function resolve(args: readonly unknown[]): { url: string; options: Record<string, unknown> } {\n const record = args[0];\n\n if (\n args.length !== 1 ||\n typeof record !== \"object\" ||\n record === null ||\n Array.isArray(record)\n ) {\n throw new TypeError(\"Expected one navigation target record\");\n }\n\n const values: Record<string, unknown> = {};\n const rest: [string, unknown][] = [];\n let to: unknown;\n\n for (const [key, value] of Object.entries(record)) {\n if (key === \"to\") {\n to = value;\n } else if (key === \"params\" || key === \"search\" || key === \"fragment\") {\n values[key] = value;\n } else {\n rest.push([key, value]);\n }\n }\n\n // fromEntries defines own properties, so an own `__proto__` key stays a key, not a prototype.\n const options: Record<string, unknown> = Object.fromEntries(rest);\n\n let target = id;\n\n if (typeof to === \"string\") {\n if (Object.hasOwn(set.absolute, to)) {\n target = set.absolute[to];\n } else if (to === \"..\" && Object.hasOwn(set.parents, id)) {\n target = set.parents[id];\n } else {\n throw new TypeError(`Unknown generated selector: ${to}`);\n }\n } else if (to !== undefined) {\n target = destinationId(set, to);\n }\n\n return {\n url: createUrl(set.destinations[target](), values as unknown as UrlValues<AnyRoute>),\n options,\n };\n }\n\n return {\n params: view.params as Signal<Readonly<ParamsOf<Target>>>,\n search: view.search as Signal<Readonly<SearchOf<Target>>>,\n data: view.data as Signal<DataOf<Target>>,\n title: view.title as Signal<TitleOf<Target>>,\n link: ((...args: unknown[]) => {\n const { url, options } = resolve(args);\n const behavior = Object.keys(options);\n\n if (behavior.length > 0) {\n throw new TypeError(\n `link takes no navigation behavior options (${behavior.join(\", \")}); set them on RouterLink`,\n );\n }\n\n return router.parseUrl(url);\n }) as Handle<Target, Absolute, Parent, Set, References>[\"link\"],\n navigate: ((...args: unknown[]) => {\n const { url, options } = resolve(args);\n\n return router.navigateByUrl(url, options);\n }) as Handle<Target, Absolute, Parent, Set, References>[\"navigate\"],\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { booleanParam, createUrl, defineRoute, enumParam, numberParam, stringParam, withFallback } from "@loozo/angular-router/_core";
2
- export { booleanParam, createUrl, defineRoute, enumParam, numberParam, stringParam, withFallback };
1
+ import { arrayParam, booleanParam, createUrl, defineRoute, enumParam, numberParam, stringParam, withFallback } from "@loozo/angular-router/_core";
2
+ export { arrayParam, booleanParam, createUrl, defineRoute, enumParam, numberParam, stringParam, withFallback };
@@ -4,7 +4,7 @@ import { Observable } from "rxjs";
4
4
  import { dirname, resolve, basename } from "node:path";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { generate } from "../generation/generate.js";
7
- import { workspacePaths, markers } from "../generation/files.js";
7
+ import { workspacePaths, marked, markers } from "../generation/files.js";
8
8
  import { CompilerState } from "../generation/compiler.js";
9
9
  import { AnchorState } from "../generation/anchor.js";
10
10
  import { buildTargetOptions, message } from "./options.js";
@@ -14,14 +14,17 @@ const defaultPoll = 1000;
14
14
  const devServerBuilder = "@angular/build:dev-server";
15
15
  export default createBuilder((options, context) => new Observable((subscriber) => {
16
16
  let stopped = false;
17
- let initial = true;
18
- let dirty = false;
19
- let watcher;
20
- let acquisition;
21
- let forwarding;
22
- let active;
17
+ // One pass at a time. "pending" lasts until the initial generation starts, which covers the
18
+ // edits before it. An edit during a "running" pass makes it "rerun": exactly one more pass
19
+ // follows it, however many edits arrive.
20
+ let regeneration = "pending";
21
+ // The latest pass, settled once idle; stopping waits for it.
22
+ let pass = Promise.resolve();
23
+ // What start() acquired, released last-acquired first. Each release runs as stop() is called;
24
+ // the promises they return settle together, with the latest pass, before the compiler state
25
+ // clears.
26
+ const releases = [];
23
27
  let cleanup;
24
- let cancelReady;
25
28
  // Resolved from the build target before anything reads it.
26
29
  let router;
27
30
  const state = {
@@ -50,42 +53,60 @@ export default createBuilder((options, context) => new Observable((subscriber) =
50
53
  }
51
54
  }
52
55
  function stop() {
56
+ if (cleanup) {
57
+ return cleanup;
58
+ }
53
59
  stopped = true;
54
- dirty = false;
55
- cancelReady?.();
56
- forwarding?.unsubscribe();
57
- if (!cleanup) {
58
- const close = watcher?.close();
59
- const delegateStop = acquisition?.then((run) => run.stop(), () => undefined);
60
- const drain = active?.catch(() => undefined);
61
- cleanup = Promise.all([close, delegateStop, drain]).then(() => clear());
60
+ const settling = [pass.catch(() => undefined)];
61
+ for (let index = releases.length - 1; index >= 0; index--) {
62
+ settling.push(releases[index]());
62
63
  }
64
+ cleanup = Promise.all(settling).then(() => clear());
63
65
  return cleanup;
64
66
  }
65
67
  context.addTeardown(stop);
66
- function pump() {
67
- if (stopped || initial || active || !dirty) {
68
+ // Later passes log their failures and keep serving.
69
+ function startPass() {
70
+ regeneration = "running";
71
+ pass = regenerate().catch((error) => context.logger.error(message(error)));
72
+ void pass.then(passEnded);
73
+ }
74
+ function passEnded() {
75
+ if (regeneration === "rerun" && !stopped) {
76
+ startPass();
68
77
  return;
69
78
  }
70
- const work = (async () => {
71
- while (dirty && !stopped) {
72
- dirty = false;
73
- try {
74
- await regenerate();
75
- }
76
- catch (error) {
77
- context.logger.error(message(error));
79
+ regeneration = "idle";
80
+ }
81
+ function edited() {
82
+ if (regeneration === "idle") {
83
+ startPass();
84
+ }
85
+ else if (regeneration === "running") {
86
+ regeneration = "rerun";
87
+ }
88
+ }
89
+ async function changed(path) {
90
+ if (stopped) {
91
+ return;
92
+ }
93
+ if (basename(path) === "route.generated.ts") {
94
+ try {
95
+ const bytes = await readFile(path, "utf8");
96
+ if (marked(bytes, markers["route.generated.ts"])) {
97
+ return;
78
98
  }
79
99
  }
80
- })();
81
- active = work;
82
- void work.finally(() => {
83
- if (active === work) {
84
- active = undefined;
100
+ catch (error) {
101
+ if (error.code !== "ENOENT") {
102
+ throw error;
103
+ }
85
104
  }
86
- // An edit between the loop's last check and finalization must not be lost.
87
- pump();
88
- });
105
+ }
106
+ if (stopped) {
107
+ return;
108
+ }
109
+ edited();
89
110
  }
90
111
  async function start() {
91
112
  const { poll = defaultPoll, ...devServer } = options;
@@ -108,54 +129,32 @@ export default createBuilder((options, context) => new Observable((subscriber) =
108
129
  if (stopped) {
109
130
  return;
110
131
  }
111
- watcher = chokidar.watch(roots, { ignoreInitial: true, followSymlinks: false });
112
- const ready = new Promise((resolveReady, rejectReady) => {
113
- cancelReady = () => resolveReady(false);
114
- watcher.once("ready", () => resolveReady(true));
115
- watcher.on("error", (error) => {
116
- if (initial) {
117
- rejectReady(error);
118
- }
119
- else {
120
- context.logger.error(String(error));
121
- }
122
- });
123
- async function changed(path) {
124
- if (stopped) {
125
- return;
126
- }
127
- if (basename(path) === "route.generated.ts") {
128
- try {
129
- const bytes = await readFile(path, "utf8");
130
- if (bytes.split("\n", 1)[0] === markers["route.generated.ts"]) {
131
- return;
132
- }
133
- }
134
- catch (error) {
135
- if (error.code !== "ENOENT") {
136
- throw error;
137
- }
138
- }
139
- }
140
- if (stopped) {
141
- return;
142
- }
143
- dirty = true;
144
- pump();
145
- }
146
- watcher.on("all", (_event, path) => {
147
- void changed(path).catch((error) => context.logger.error(String(error)));
148
- });
132
+ const watcher = chokidar.watch(roots, { ignoreInitial: true, followSymlinks: false });
133
+ const ready = Promise.withResolvers();
134
+ releases.push(() => {
135
+ ready.resolve(false);
136
+ return watcher.close();
137
+ });
138
+ // A watcher error fails startup until the watcher is ready. From then on, including during
139
+ // the initial generation, it's logged and serve carries on.
140
+ watcher.on("error", ready.reject);
141
+ watcher.once("ready", () => {
142
+ watcher.off("error", ready.reject);
143
+ watcher.on("error", (error) => context.logger.error(String(error)));
144
+ ready.resolve(true);
145
+ });
146
+ watcher.on("all", (_event, path) => {
147
+ void changed(path).catch((error) => context.logger.error(String(error)));
149
148
  });
150
- const readyForGeneration = await ready;
149
+ const readyForGeneration = await ready.promise;
151
150
  if (stopped || !readyForGeneration) {
152
151
  return;
153
152
  }
154
- dirty = false;
155
- const generation = regenerate();
156
- active = generation;
153
+ regeneration = "running";
154
+ const initial = regenerate();
155
+ pass = initial;
157
156
  try {
158
- await generation;
157
+ await initial;
159
158
  }
160
159
  catch (error) {
161
160
  // The dev server never starts on a failed initial generation; report it as a build failure.
@@ -165,28 +164,23 @@ export default createBuilder((options, context) => new Observable((subscriber) =
165
164
  subscriber.complete();
166
165
  return;
167
166
  }
168
- finally {
169
- if (active === generation) {
170
- active = undefined;
171
- }
172
- }
173
167
  if (stopped) {
174
168
  return;
175
169
  }
176
- initial = false;
177
- pump();
170
+ passEnded();
178
171
  if (stopped) {
179
172
  return;
180
173
  }
181
- acquisition = context.scheduleBuilder(devServerBuilder, devServer, {
174
+ const acquisition = context.scheduleBuilder(devServerBuilder, devServer, {
182
175
  target: context.target,
183
176
  });
177
+ // The release stops the run, including one acquired after a stop.
178
+ releases.push(() => acquisition.then((run) => run.stop(), () => undefined));
184
179
  const run = await acquisition;
185
180
  if (stopped) {
186
- await run.stop();
187
181
  return;
188
182
  }
189
- forwarding = run.output.subscribe({
183
+ const forwarding = run.output.subscribe({
190
184
  next: (output) => subscriber.next(output),
191
185
  error: (error) => {
192
186
  void stop().then(() => subscriber.error(error), () => subscriber.error(error));
@@ -195,6 +189,7 @@ export default createBuilder((options, context) => new Observable((subscriber) =
195
189
  void stop().then(() => subscriber.complete(), (error) => subscriber.error(error));
196
190
  },
197
191
  });
192
+ releases.push(async () => forwarding.unsubscribe());
198
193
  }
199
194
  void start().catch((error) => {
200
195
  void stop().then(() => subscriber.error(error), () => subscriber.error(error));
@@ -208,7 +208,8 @@ export const routes: Routes = [${[...before, ...ordinary, ...after].map(record).
208
208
  }
209
209
  const shared = [
210
210
  markers["destinations.generated.ts"],
211
- "import type { RouteSet, Handle as GeneratedHandle, Destination as GeneratedDestination } from '@loozo/angular-router/_generated';",
211
+ "import type { Signal } from '@angular/core';",
212
+ "import { injectGeneratedActiveRoute, type RouteSet, type Handle as GeneratedHandle, type Destination as GeneratedDestination } from '@loozo/angular-router/_generated';",
212
213
  ];
213
214
  for (const declaration of declarations) {
214
215
  shared.push(`import ${names.get(declaration)} from ${JSON.stringify(specifier(sharedFile, declaration.file))};`);
@@ -240,6 +241,29 @@ type ParentOf<Id extends Retained> = Id extends keyof Parents ? Definitions[Pare
240
241
  export type Handle<Id extends Retained> = GeneratedHandle<Definitions[Id], AbsoluteSelected, ParentOf<Id>, typeof setIdentity, Pick<Definitions, Retained>>;
241
242
  /** A same-set nominal reference; use the destination exported by a colocated facade. */
242
243
  export type Destination<Id extends Retained> = GeneratedDestination<Definitions[Id], typeof setIdentity, Id>;
244
+ interface ActiveBase {
245
+ /** Exactly this route, not an ancestor. Narrows to that route's handle. */
246
+ is<Other extends Retained>(destination: Destination<Other>): this is ActiveHandle<Other>;
247
+ /** This route or one of its active ancestors. */
248
+ contains(destination: Destination<Retained>): boolean;
249
+ /** Active generated routes, top-level first, ending with this one. */
250
+ readonly chain: readonly ActiveRoute[];
251
+ }
252
+ /** An active route narrowed by is(): that declaration's handle. */
253
+ export interface ActiveHandle<Id extends Retained> extends Handle<Id>, ActiveBase {}
254
+ /** An active route before narrowing: params, search, data and title read as the union of the set's values; link and navigate need is(). */
255
+ export interface ActiveRoute extends Pick<Handle<Retained>, "params" | "search" | "data" | "title">, ActiveBase {}
256
+ /**
257
+ * Injects a signal of the deepest active generated route in the primary outlet.
258
+ *
259
+ * @remarks
260
+ * Call in an Angular injection context. The value changes only when a navigation
261
+ * commits, and is undefined before the first one or when no route of this set is
262
+ * active. A route keeps its handle while it stays active; each call is its own reader.
263
+ */
264
+ export function injectActiveRoute(): Signal<ActiveRoute | undefined> {
265
+ return injectGeneratedActiveRoute(getRouteSet());
266
+ }
243
267
  const routeSet: RouteSet = { records: {`);
244
268
  for (const declaration of retained) {
245
269
  const owners = [];
@@ -283,8 +307,10 @@ const routeSet: RouteSet = { records: {`);
283
307
  *
284
308
  * @remarks
285
309
  * Call in an Angular injection context. The handle exposes readonly native
286
- * params/search/data Signals and destination-typed link/navigate methods.
287
- * Data includes local resolver results only, not static or inherited data.
310
+ * params/search/data/title Signals and destination-typed link/navigate methods.
311
+ * Data holds the resolver results of this route and its ancestors, a nearer
312
+ * key hiding an ancestor's; native static data and the title stay out. Title
313
+ * is the nearest declared title in the chain, or undefined when none declares one.
288
314
  * All navigation values are explicit; current inputs are never reused implicitly.
289
315
  *
290
316
  * @returns A noncallable handle for this declaration.
@@ -8,6 +8,7 @@ export declare const launchMarkers: {
8
8
  readonly "context.generated.d.ts": "// @loozo/angular-launch: owned context.generated.d.ts";
9
9
  readonly "server.generated.ts": "// @loozo/angular-launch: owned server.generated.ts";
10
10
  };
11
+ export declare function marked(bytes: string, marker: string): boolean;
11
12
  export declare function contains(directory: string, file: string): boolean;
12
13
  export declare function metadata(path: string): Promise<import("fs").Stats | undefined>;
13
14
  export declare function workspacePaths(workspace: string): Promise<{
@@ -11,6 +11,16 @@ export const launchMarkers = {
11
11
  "context.generated.d.ts": "// @loozo/angular-launch: owned context.generated.d.ts",
12
12
  "server.generated.ts": "// @loozo/angular-launch: owned server.generated.ts",
13
13
  };
14
+ // Git may check committed generated files out with CRLF line endings (core.autocrlf on
15
+ // Windows). Such a file is still owned output, and current when that conversion is all that
16
+ // separates it from the desired bytes; the generator itself always writes LF.
17
+ export function marked(bytes, marker) {
18
+ const line = bytes.split("\n", 1)[0];
19
+ return line === marker || line === `${marker}\r`;
20
+ }
21
+ function current(bytes, expected) {
22
+ return bytes === expected || bytes === expected.replaceAll("\n", "\r\n");
23
+ }
14
24
  export function contains(directory, file) {
15
25
  const suffix = relative(directory, file);
16
26
  return (suffix === "" || (!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`)));
@@ -146,7 +156,8 @@ export async function planOutputs(desired, encountered, globals, mode) {
146
156
  Object.assign(ownership, launchMarkers);
147
157
  }
148
158
  const name = path.slice(path.lastIndexOf(sep) + 1);
149
- const owned = bytes.split("\n", 1)[0] === ownership[name];
159
+ const marker = ownership[name];
160
+ const owned = marker !== undefined && marked(bytes, marker);
150
161
  if (!owned) {
151
162
  if (expected !== undefined) {
152
163
  throw new Error(`Unmarked generated-output collision: ${path}`);
@@ -156,7 +167,7 @@ export async function planOutputs(desired, encountered, globals, mode) {
156
167
  if (expected === undefined) {
157
168
  stale.push(path);
158
169
  }
159
- else if (bytes !== expected) {
170
+ else if (!current(bytes, expected)) {
160
171
  changed.push(path);
161
172
  }
162
173
  }