@real-router/sources 0.8.4 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/BaseSource.ts +0 -95
- package/src/canonicalJson.ts +0 -110
- package/src/computeSnapshot.ts +0 -44
- package/src/createActiveNameSelector.ts +0 -182
- package/src/createActiveRouteSource.ts +0 -223
- package/src/createDismissableError.ts +0 -108
- package/src/createErrorSource.ts +0 -111
- package/src/createRouteNodeSource.ts +0 -130
- package/src/createRouteSource.ts +0 -56
- package/src/createTransitionSource.ts +0 -192
- package/src/index.ts +0 -35
- package/src/internal/noopDestroy.ts +0 -19
- package/src/internal/readContextHash.ts +0 -32
- package/src/normalizeActiveOptions.ts +0 -41
- package/src/stabilizeState.ts +0 -75
- package/src/types.ts +0 -63
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#listeners","#onFirstSubscribe","#onLastUnsubscribe","#onDestroy","#currentSnapshot","#destroyed","events","events"],"sources":["../../src/BaseSource.ts","../../src/internal/readContextHash.ts","../../src/stabilizeState.ts","../../src/createRouteSource.ts","../../src/computeSnapshot.ts","../../src/internal/noopDestroy.ts","../../src/createRouteNodeSource.ts","../../src/canonicalJson.ts","../../src/normalizeActiveOptions.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts","../../src/createErrorSource.ts","../../src/createDismissableError.ts","../../src/createActiveNameSelector.ts"],"sourcesContent":["export interface BaseSourceOptions {\n onFirstSubscribe?: () => void;\n onLastUnsubscribe?: () => void;\n onDestroy?: () => void;\n}\n\nexport class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n readonly #onFirstSubscribe: (() => void) | undefined;\n readonly #onLastUnsubscribe: (() => void) | undefined;\n readonly #onDestroy: (() => void) | undefined;\n\n constructor(initialSnapshot: T, options?: BaseSourceOptions) {\n this.#currentSnapshot = initialSnapshot;\n this.#onFirstSubscribe = options?.onFirstSubscribe;\n this.#onLastUnsubscribe = options?.onLastUnsubscribe;\n this.#onDestroy = options?.onDestroy;\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n const wasFirst = this.#listeners.size === 0;\n\n // Add listener BEFORE onFirstSubscribe so that if the reconciliation in\n // onFirstSubscribe calls updateSnapshot(), this listener receives the\n // notification. Critical for useSyncExternalStore in adapters — without\n // this the post-reconnection snapshot is missed and consumers render\n // stale data. (See Preact RouteView nested remount test.)\n this.#listeners.add(listener);\n\n if (wasFirst && this.#onFirstSubscribe) {\n this.#onFirstSubscribe();\n }\n\n return () => {\n this.#listeners.delete(listener);\n\n if (\n !this.#destroyed &&\n this.#listeners.size === 0 &&\n this.#onLastUnsubscribe\n ) {\n this.#onLastUnsubscribe();\n }\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n // Isolate listener exceptions so a single throwing subscriber (e.g. React\n // error-boundary fallback throwing inside `onStoreChange`) does not block\n // the remaining subscribers — the invariant \"after updateSnapshot all\n // listeners see the new snapshot\" must hold. Re-throw asynchronously via\n // queueMicrotask so global error handlers / test harnesses still surface\n // the bug without breaking the synchronous notification fan-out.\n for (const listener of this.#listeners) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#onDestroy?.();\n this.#listeners.clear();\n }\n}\n","import type { State } from \"@real-router/core\";\n\n/**\n * @internal\n *\n * Reads the URL fragment published by `browser-plugin` / `navigation-plugin`\n * on a router state. The plugins claim the `\"url\"` namespace via\n * `state.context.url` and the `hash` field carries the **decoded** fragment.\n *\n * Returns:\n * - `undefined` — no plugin claimed the `\"url\"` namespace (hash-plugin runtime,\n * memory-plugin, SSR before hydration) OR the state itself is nullish;\n * - `\"\"` — the URL namespace exists but the fragment is empty\n * (browser-plugin on a hash-less URL).\n *\n * Callers that need the \"no namespace at all\" branch (e.g. `stabilizeState`\n * comparing cross-plugin transitions) read the raw `undefined`. Callers that\n * collapse \"no namespace\" to \"no hash\" (e.g. `createActiveRouteSource`'s\n * hash-equality check) coalesce with `?? \"\"` themselves.\n *\n * Centralising the context cast removes the previous duplicate definitions in\n * `stabilizeState.ts` and `createActiveRouteSource.ts` that drifted in\n * signature (state vs router) and default-value (undefined vs \"\") — both\n * variants are reconstructible from this single helper at the callsite.\n */\nexport function readContextHash(\n state: State | null | undefined,\n): string | undefined {\n const ctx = state?.context as { url?: { hash?: string } } | undefined;\n\n return ctx?.url?.hash;\n}\n","import { readContextHash } from \"./internal/readContextHash.js\";\n\nimport type { State } from \"@real-router/core\";\n\n/**\n * State-aware stabilization for route snapshots.\n *\n * Compares `path` (canonical name+params), `state.context.url.hash`\n * (URL fragment, #532), and `state.transition.reload` (#605). When all\n * three match (idempotent navigation), returns `prev` (preserving\n * reference) so frameworks can skip re-renders. When any of them flips,\n * returns `next` so consumers subscribing through `useRoute()` see the\n * new state.\n *\n * `transition.reload === true` is the user's explicit signal for a\n * non-idempotent navigation — `router.navigate(name, params, { reload:\n * true })` is the canonical pairing for `invalidate(router, namespace)`\n * and any cache-bust pattern. Bypassing stabilization for reloads makes\n * `useRoute()` consumers see fresh `state.context.<namespace>` values\n * written by the SSR loader plugin's `subscribeLeave` handler.\n *\n * Ignores `meta` (internal: auto-increment id), other `transition` fields\n * (`from`, `segments`, `redirected`), and `state.context.navigation` /\n * `state.context.browser` (transient transition metadata) — they don't\n * affect render identity for idempotent navigations.\n *\n * Accepts `null` for compatibility with `RouterTransitionSnapshot`\n * (toRoute/fromRoute are `State | null`).\n *\n * @internal Not exported from package public API.\n */\nexport function stabilizeState<T extends State | null | undefined>(\n prev: T,\n next: T,\n): T {\n if (prev === next) {\n return prev;\n }\n if (prev?.path !== next?.path) {\n return next;\n }\n\n // After the path check, both must be the same non-null State (paths\n // matched, prev !== next reference). Read context.url.hash to detect\n // same-path-different-hash navigation (#532) — render-relevant for\n // tab-style UIs that subscribe via useRoute(). Optional chaining keeps\n // the access null-safe without forbidden non-null assertions.\n if (readContextHash(prev) !== readContextHash(next)) {\n return next;\n }\n\n // Explicit reload navigation (#605) — caller asked to bypass dedupe so\n // observers see fresh `state.context` written by `invalidate()`-driven\n // loader re-runs. The path equality above guarantees both prev and next\n // are either non-null with matching paths or both nullish; only the\n // non-null branch can carry a meaningful `transition.reload`.\n if (readReloadFlag(next)) {\n return next;\n }\n\n return prev;\n}\n\nfunction readReloadFlag(state: State | null | undefined): boolean {\n // Defensive read: `transition` is mandatory in the public State type, but a\n // plugin returning a malformed state (or a future fork) shouldn't crash the\n // stabilizer with a TypeError. We cast to a structurally-loose shape so the\n // optional chain is permitted; the runtime guard preserves dedup (false =\n // not-a-reload) for malformed inputs.\n const transition = (\n state as { transition?: { reload?: boolean } } | null | undefined\n )?.transition;\n\n return transition?.reload === true;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteSnapshot>(\n {\n route: router.getState(),\n previousRoute: undefined,\n },\n {\n onFirstSubscribe: () => {\n routerUnsubscribe = router.subscribe((next) => {\n const prev = source.getSnapshot();\n const newRoute = stabilizeState(prev.route, next.route);\n const newPreviousRoute = stabilizeState(\n prev.previousRoute,\n next.previousRoute,\n );\n\n if (\n newRoute !== prev.route ||\n newPreviousRoute !== prev.previousRoute\n ) {\n source.updateSnapshot({\n route: newRoute,\n previousRoute: newPreviousRoute,\n });\n }\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n const newRoute = stabilizeState(currentSnapshot.route, route);\n const newPreviousRoute = stabilizeState(\n currentSnapshot.previousRoute,\n previousRoute,\n );\n\n if (\n newRoute === currentSnapshot.route &&\n newPreviousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route: newRoute, previousRoute: newPreviousRoute };\n}\n","/**\n * @internal\n *\n * Shared no-op `destroy()` for cached `RouterSource` wrappers.\n *\n * Cached factories (`getTransitionSource`, `getErrorSource`, `createDismissableError`,\n * `createActiveNameSelector`, cache hit in `createRouteNodeSource`,\n * `createActiveRouteSource`) return a wrapper whose `destroy()` is a no-op —\n * the underlying source is shared across all consumers and lives as long as\n * the router (WeakMap entry releases on router GC).\n *\n * One module-level function shared by every cached factory keeps the wrapper\n * shape (`{ subscribe, getSnapshot, destroy: noopDestroy }`) byte-stable and\n * eliminates the previous six copies. (Bundlers inline a stand-alone arrow\n * just as readily, so the cost is purely on the maintenance side.)\n */\nexport function noopDestroy(): void {\n // Shared cached source — external destroy() is a no-op.\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst nodeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<RouteNodeSnapshot>>\n>();\n\n/**\n * Creates a source scoped to a specific route node.\n *\n * **Per-router + per-nodeName cache:** repeated calls with the same\n * `(router, nodeName)` return the same shared instance. `N` consumers\n * calling `createRouteNodeSource(r, \"users\")` produce one router subscription\n * shared across all of them.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n *\n * `destroy()` on the returned source is a no-op — the shared instance lives\n * as long as the router itself (the WeakMap entry releases automatically on\n * router GC). Callers that need an isolated instance with working teardown\n * can use `buildRouteNodeSource` internally (not exported).\n */\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let perRouter = nodeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n nodeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(nodeName);\n\n if (!cached) {\n const source = buildRouteNodeSource(router, nodeName);\n\n // Wrap with no-op destroy. The shared source lives with the router.\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(nodeName, cached);\n }\n\n return cached;\n}\n\nfunction buildRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n // Built once per cached source instance; safe — createRouteNodeSource is\n // itself per-(router, nodeName) cached, so shouldUpdate is called once.\n const shouldUpdate = router.shouldUpdateNode(nodeName);\n\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteNodeSnapshot>(\n computeSnapshot(initialSnapshot, router, nodeName),\n {\n onFirstSubscribe: () => {\n // Reconcile snapshot with current router state before connecting.\n // Covers reconnection after Activity hide/show cycles where the\n // source was disconnected and missed navigation events.\n const reconciled = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n );\n\n if (!Object.is(reconciled, source.getSnapshot())) {\n source.updateSnapshot(reconciled);\n }\n\n // Connect to router on first subscription\n routerUnsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n // computeSnapshot returns the SAME currentSnapshot reference when\n // both route and previousRoute stabilize to prev — guard against\n // emitting redundant updates to listeners (matters for signal-\n // based adapters that re-run effects on every set).\n /* v8 ignore next 3 -- @preserve: structurally unreachable after #605\n — reload navs always return fresh refs via stabilizeState, and\n within-node non-reload navs short-circuit at shouldUpdate. Guard\n kept for defensive correctness against future stabilizer changes. */\n if (Object.is(source.getSnapshot(), newSnapshot)) {\n return;\n }\n\n source.updateSnapshot(newSnapshot);\n });\n },\n onLastUnsubscribe: disconnect,\n },\n );\n\n return source;\n}\n","/**\n * Serializes a value into a stable JSON string — object keys are sorted at\n * every level so that `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` produce the same\n * output.\n *\n * Used as a cache key for `createActiveRouteSource` so that equivalent params\n * objects share the same cached source regardless of key order.\n *\n * **Divergence from `shared/dom-utils/scroll-restore.canonicalJson` — by\n * design.** That sibling implementation is the cheap navigation-hot-path\n * variant (uses `localeCompare`, plain-object accumulator, native cycle\n * detector) and pairs with `safeKeyOf` for crash-tolerance. This one is the\n * strict cache-key variant: byte-order compare, prototype-less accumulator,\n * bespoke cycle detection — eagerly throws on inputs that would cause\n * collisions or pollution, so callers can fall back to non-cached sources.\n * They are NOT interchangeable; cross-package equivalence is explicitly not\n * a goal (audit-2 / audit-2026-05-17 §2).\n *\n * Edge cases:\n * - Arrays preserve order (canonical: index-ordered already).\n * - `undefined` values are dropped (standard JSON behaviour).\n * - `Date` is serialized via its `toJSON` method (ISO string).\n * - `Symbol` becomes `undefined` (standard JSON behaviour).\n * - `BigInt` throws via `JSON.stringify` defaults.\n * - `Map`, `Set`, `RegExp`, `WeakMap`, `WeakSet` would silently collapse to\n * `\"{}\"` (no enumerable own keys), which would cause **different inputs to\n * share the same cache key**. We detect these explicitly and throw — callers\n * then take the non-cached fallback path (same behaviour as `BigInt`).\n * - Circular references throw `TypeError` (parity with native `JSON.stringify`).\n * The replacer copies each object level into a fresh prototype-less record,\n * so we must run our own cycle detection — the native detector never sees\n * the original object graph.\n * - `__proto__` keys are preserved as own properties (no prototype pollution\n * and no silent collision between `{ __proto__: x, b: 1 }` and `{ b: 1 }`).\n *\n * In practice, route params carry primitives (`string | number | boolean`);\n * the cache-key-collision edge cases above are defensive bugs caught loudly.\n */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(canonicalize(value, new Set<object>()));\n}\n\n// Key comparison uses byte-order (`<` / `>`) instead of `localeCompare()` so\n// the output is independent of the Node.js ICU build / system locale. Same\n// canonical form on every machine — required for cache-key stability.\nfunction compareKeys(left: string, right: string): number {\n return left < right ? -1 : 1;\n}\n\n/**\n * Returns a structural clone with object keys sorted at every level. Path-based\n * cycle detection (`path` set) matches the semantics of native `JSON.stringify`\n * — a `TypeError` is thrown on a true cycle, but the same object reachable via\n * two independent branches (DAG) serialises fine.\n *\n * `Date` instances pass through unchanged so `JSON.stringify` can invoke their\n * `toJSON` hook. Other built-ins (`Map`, `Set`, `WeakMap`, `WeakSet`, `RegExp`)\n * throw eagerly to avoid silent cache-key collisions.\n */\nfunction canonicalize(value: unknown, path: Set<object>): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value;\n }\n\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof RegExp\n ) {\n throw new TypeError(\n `canonicalJson: cannot serialize ${(value as { constructor: { name: string } }).constructor.name} — non-enumerable own keys collapse to \"{}\" and would cause cache-key collisions. Pass primitive params (string | number | boolean) instead.`,\n );\n }\n\n if (path.has(value)) {\n throw new TypeError(\n \"canonicalJson: cannot serialize circular structure (cycle detected during traversal).\",\n );\n }\n\n path.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, path));\n }\n\n // Use a null-prototype record so `__proto__` is treated as a regular\n // own property — assigning to a plain `{}` would set the prototype\n // chain instead and silently collide with inputs that omit the key.\n const sorted: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n const keys = Object.keys(value).toSorted(compareKeys);\n\n for (const key of keys) {\n sorted[key] = canonicalize((value as Record<string, unknown>)[key], path);\n }\n\n return sorted;\n } finally {\n path.delete(value);\n }\n}\n","import type { ActiveRouteSourceOptions } from \"./types.js\";\n\n/**\n * Normalized options shape — booleans are required (filled with defaults),\n * `hash` stays `string | undefined` because `undefined` is the meaningful\n * \"ignore hash\" sentinel that callers pass intentionally.\n */\nexport interface NormalizedActiveOptions {\n strict: boolean;\n ignoreQueryParams: boolean;\n hash: string | undefined;\n}\n\n/**\n * Default options for `createActiveRouteSource` and adapter-level helpers.\n *\n * Frozen to prevent accidental mutation by consumers.\n */\nexport const DEFAULT_ACTIVE_OPTIONS: Readonly<NormalizedActiveOptions> =\n Object.freeze({\n strict: false,\n ignoreQueryParams: true,\n hash: undefined,\n });\n\n/**\n * Normalizes partial `ActiveRouteSourceOptions` into a fully-defaulted object.\n *\n * Use this to produce a stable options record for comparison, caching, or\n * downstream consumers that require all fields present.\n */\nexport function normalizeActiveOptions(\n options?: ActiveRouteSourceOptions,\n): NormalizedActiveOptions {\n return {\n strict: options?.strict ?? DEFAULT_ACTIVE_OPTIONS.strict,\n ignoreQueryParams:\n options?.ignoreQueryParams ?? DEFAULT_ACTIVE_OPTIONS.ignoreQueryParams,\n hash: options?.hash,\n };\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { canonicalJson } from \"./canonicalJson.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { readContextHash } from \"./internal/readContextHash.js\";\nimport { normalizeActiveOptions } from \"./normalizeActiveOptions.js\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nconst activeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<boolean>>\n>();\n\n/**\n * Creates a source tracking whether a route (with given params/options) is active.\n *\n * **Per-router + canonical-args cache:** repeated calls with equivalent\n * arguments return the same shared instance. Param key order doesn't matter\n * (`{ a:1, b:2 }` and `{ b:2, a:1 }` hit the same cache entry via\n * `canonicalJson`).\n *\n * For cached entries `destroy()` is a no-op — shared sources live with the\n * router and release automatically on router GC (WeakMap entry).\n *\n * `BigInt`/circular params can't be serialized → the source bypasses the cache\n * and `destroy()` becomes a real teardown that detaches the underlying\n * `router.subscribe` handle.\n */\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n const { strict, ignoreQueryParams, hash } = normalizeActiveOptions(options);\n\n // BigInt/Symbol/circular refs cannot be serialized — fall back to creating\n // a fresh (non-cached) source. Callers pass these edge-case params rarely;\n // the extra allocation is acceptable.\n let key: string | undefined;\n\n try {\n // `hash === undefined` produces \"\" via String(undefined) → \"undefined\";\n // we encode it as the empty string sentinel to keep the key short and\n // distinct from the literal \"undefined\" hash value (which is a valid,\n // if unusual, fragment).\n const hashKey = hash === undefined ? \"\" : `#${hash}`;\n\n // `params === undefined` is the common Link case (`<Link to=\"users\">`\n // with no params). Skip canonicalJson(undefined) — it returns the literal\n // string \"undefined\" and template interpolation would just embed it. An\n // explicit empty sentinel avoids the call and shaves the cache-key by 9\n // characters per Link.\n const paramsKey = params === undefined ? \"\" : canonicalJson(params);\n\n // Delimiter `|` is safe because route names use `.` as the segment\n // separator (`users.list`, not `users|list`) and canonicalJson-encoded\n // params escape `\"` (so any literal `|` inside params lives inside a\n // quoted JSON string and can't be confused with our delimiter). If route\n // names ever grow a `|` character, this composite key would become\n // ambiguous — change the separator to a control char or hash-encode each\n // field.\n key = `${routeName}|${paramsKey}|${String(strict)}|${String(ignoreQueryParams)}|${hashKey}`;\n } catch {\n key = undefined;\n }\n\n if (key === undefined) {\n // Non-cached fallback (canonicalJson threw on BigInt / circular / etc.).\n // Return the real source — `destroy()` must unwind the router subscription;\n // otherwise the wrapper leaks for the lifetime of the router.\n return buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n }\n\n let perRouter = activeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n activeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(key);\n\n if (!cached) {\n const source = buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(key, cached);\n }\n\n return cached;\n}\n\n/**\n * Combines route-name match with optional hash match (#532).\n *\n * - Route-name match: `router.isActiveRoute(name, params, strict, ignoreQueryParams)`.\n * - Hash match (only when `hash !== undefined`): `state.context.url.hash` must\n * equal the requested fragment exactly. With hash-plugin (no `url`\n * namespace), this returns `false` — the documented limitation.\n */\nfunction computeActive(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): boolean {\n const routeActive = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n if (!routeActive) {\n return false;\n }\n if (hash === undefined) {\n return true;\n }\n\n // `readContextHash` returns `undefined` when no URL plugin claimed the\n // namespace (hash-plugin runtime, memory-plugin, SSR). For hash-equality\n // matching we collapse that to `\"\"` — a hash-aware Link with no URL plugin\n // can only match when the consumer also asked for `hash: \"\"`.\n return (readContextHash(router.getState()) ?? \"\") === hash;\n}\n\nfunction buildActiveRouteSource(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): RouterSource<boolean> {\n const initialValue = computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n let routerUnsubscribe: (() => void) | undefined;\n\n const source = new BaseSource(initialValue, {\n onDestroy: () => {\n routerUnsubscribe?.();\n routerUnsubscribe = undefined;\n },\n });\n\n // Eager connection: subscribe to router immediately. For the cached path,\n // the returned wrapper has a no-op destroy and the handle lives with the\n // router (released on router GC). For the non-cached fallback (BigInt /\n // circular params), the handle is unwound through `onDestroy` above.\n routerUnsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n // Hash-aware sources also flip on same-path-different-hash transitions.\n // The route comparison alone misses these (route is identical), but the\n // hash claim updated, so we must re-evaluate. Detect via the `hashChanged`\n // flag published by URL plugins.\n const hashFlip =\n hash !== undefined &&\n ((next.route.context as { url?: { hashChanged?: boolean } } | undefined)\n ?.url?.hashChanged ??\n false);\n\n if (!isNewRelated && !isPrevRelated && !hashFlip) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization. (Hash check would\n // also fail without route-match, so this short-circuit holds for\n // hash-aware sources too.)\n const newValue = isNewRelated\n ? computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n )\n : false;\n\n if (!Object.is(source.getSnapshot(), newValue)) {\n source.updateSnapshot(newValue);\n }\n });\n\n return source;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\n// Frozen so accidental consumer mutation (`source.getSnapshot().toRoute = X`)\n// throws in strict mode. The singleton ref is shared across every IDLE state\n// for the lifetime of the process — mutating it would corrupt the contract\n// \"all IDLE snapshots are the same object reference\" relied on by every\n// adapter's useSyncExternalStore equivalent.\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = Object.freeze({\n isTransitioning: false,\n isLeaveApproved: false,\n toRoute: null,\n fromRoute: null,\n});\n\nconst transitionSourceCache = new WeakMap<\n Router,\n RouterSource<RouterTransitionSnapshot>\n>();\n\n/**\n * @internal test-only export — returns the next snapshot for a TRANSITION_START\n * payload, or `null` when the same-paths dedup guard should suppress the\n * update. Exported so the (structurally-unreachable after #605) guard can be\n * exercised by unit tests without resorting to private-API hacks.\n */\nexport function nextTransitionStartSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isTransitioning &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: false,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\n/**\n * @internal test-only export — analogous to {@link nextTransitionStartSnapshot}\n * for the LEAVE_APPROVE payload. The guard is structurally unreachable in\n * practice (router emits LEAVE_APPROVE exactly once per pipeline) but stays\n * for plugin-driven re-entrant flows.\n */\nexport function nextLeaveApproveSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isLeaveApproved &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: true,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n const source = new BaseSource(IDLE_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n // The same-paths dedup branch inside nextTransitionStartSnapshot is\n // structurally unreachable after #605 (every router-emitted\n // TRANSITION_START carries a fresh State per navigate()), but the\n // helper is kept testable for future stabilizer changes — see the\n // direct unit test in createTransitionSource.test.ts.\n const next = nextTransitionStartSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow; covered directly via nextTransitionStartSnapshot\n unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(\n events.TRANSITION_LEAVE_APPROVE,\n (toState: State, fromState?: State) => {\n const next = nextLeaveApproveSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow (LEAVE_APPROVE fires once per pipeline); covered\n directly via nextLeaveApproveSnapshot unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached transition source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single TransitionSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createTransitionSource(router)` directly — it returns a fresh instance with\n * a working `destroy()`.\n */\nexport function getTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n let cached = transitionSourceCache.get(router);\n\n if (!cached) {\n const source = createTransitionSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n transitionSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouterErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State, RouterError } from \"@real-router/core\";\n\nconst INITIAL_SNAPSHOT: RouterErrorSnapshot = {\n error: null,\n toRoute: null,\n fromRoute: null,\n version: 0,\n};\n\nconst errorSourceCache = new WeakMap<\n Router,\n RouterSource<RouterErrorSnapshot>\n>();\n\nexport function createErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let errorVersion = 0;\n let hasError = false;\n\n const source = new BaseSource(INITIAL_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_ERROR,\n (\n toState: State | undefined,\n fromState: State | undefined,\n err: RouterError,\n ) => {\n errorVersion++;\n hasError = true;\n source.updateSnapshot({\n error: err,\n toRoute: toState ?? null,\n /* v8 ignore next -- @preserve: fromState undefined only during start() error; unreachable via navigate() */\n fromRoute: fromState ?? null,\n version: errorVersion,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, () => {\n // Skip if no error — avoids unnecessary re-renders.\n // BaseSource.updateSnapshot() always notifies listeners (new object = new ref),\n // and useSyncExternalStore compares via Object.is().\n if (hasError) {\n hasError = false;\n source.updateSnapshot({\n error: null,\n toRoute: null,\n fromRoute: null,\n version: errorVersion,\n });\n }\n }),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached error source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single ErrorSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createErrorSource(router)` directly — it returns a fresh instance with a\n * working `destroy()`.\n */\nexport function getErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let cached = errorSourceCache.get(router);\n\n if (!cached) {\n const source = createErrorSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n errorSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { getErrorSource } from \"./createErrorSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { DismissableErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst dismissableCache = new WeakMap<\n Router,\n RouterSource<DismissableErrorSnapshot>\n>();\n\n/**\n * Returns a per-router cached source that wraps `getErrorSource(router)` with\n * an integrated \"dismissed\" version counter, exposing a single reactive\n * snapshot `{ error, toRoute, fromRoute, version, resetError }`.\n *\n * Each `RouterErrorBoundary` in a framework adapter subscribes to this one\n * source instead of re-implementing the `dismissedVersion` state pattern\n * locally. The 6-copy duplicate across adapters collapses to this helper.\n *\n * **Semantics:**\n * - `error` is non-null only when `underlying.version > dismissedVersion`.\n * - `resetError()` sets `dismissedVersion = current underlying version`,\n * immediately clearing `error` to `null` and notifying all listeners.\n * - A subsequent `TRANSITION_ERROR` advances `version` beyond `dismissedVersion`,\n * so `error` becomes non-null again — no additional plumbing needed.\n *\n * **Cached:** one instance per router. `destroy()` on the returned source is\n * a no-op. Shared across all `RouterErrorBoundary` consumers.\n */\nexport function createDismissableError(\n router: Router,\n): RouterSource<DismissableErrorSnapshot> {\n const cached = dismissableCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const errorSource = getErrorSource(router);\n\n let dismissedVersion = -1;\n // Hoisted up here so the `onFirstSubscribe` closure below can read/write\n // it before `disconnect()`'s declaration. JS hoisting makes the original\n // post-declaration order legal, but reading top-to-bottom is clearer.\n let unsubFromError: (() => void) | null = null;\n\n const buildDismissableSnapshot = (): DismissableErrorSnapshot => {\n const snap = errorSource.getSnapshot();\n const isDismissed = snap.version <= dismissedVersion;\n\n return {\n error: isDismissed ? null : snap.error,\n toRoute: isDismissed ? null : snap.toRoute,\n fromRoute: isDismissed ? null : snap.fromRoute,\n version: snap.version,\n resetError,\n };\n };\n\n const source = new BaseSource<DismissableErrorSnapshot>(\n buildDismissableSnapshot(),\n {\n onFirstSubscribe: () => {\n unsubFromError = errorSource.subscribe(() => {\n source.updateSnapshot(buildDismissableSnapshot());\n });\n },\n onLastUnsubscribe: () => {\n disconnect();\n },\n },\n );\n\n function resetError(): void {\n const currentVersion = errorSource.getSnapshot().version;\n\n // No-op guard: if we already dismissed at this version (or are even ahead\n // of the live error stream), there's nothing to clear. Skipping prevents\n // a redundant snapshot allocation + listener notification under tight\n // resetError(); resetError() patterns — common when a RouterErrorBoundary\n // user clicks \"dismiss\" while another dismiss is already in flight.\n if (currentVersion <= dismissedVersion) {\n return;\n }\n\n dismissedVersion = currentVersion;\n source.updateSnapshot(buildDismissableSnapshot());\n }\n\n function disconnect(): void {\n const unsub = unsubFromError;\n\n unsubFromError = null;\n unsub?.();\n }\n\n const wrapper: RouterSource<DismissableErrorSnapshot> = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n\n dismissableCache.set(router, wrapper);\n\n return wrapper;\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport interface ActiveNameSelector {\n /**\n * Subscribes to active-state changes of a specific route name.\n * Listener is called only when `isActive(routeName)` for this name transitions.\n * Returns an unsubscribe function.\n */\n subscribe: (routeName: string, listener: () => void) => () => void;\n /**\n * O(1) active check for the given route name (non-strict by default —\n * matches descendants). Uses the shared underlying router subscription.\n */\n isActive: (routeName: string) => boolean;\n /** No-op on the cached wrapper. */\n destroy: () => void;\n}\n\nconst selectorCache = new WeakMap<Router, ActiveNameSelector>();\n\n/**\n * Per-router cached selector providing O(1) active-route checks with one\n * shared router subscription for any number of distinct `routeName`\n * consumers.\n *\n * **When to use:** framework `Link` components that need an active boolean\n * without custom `params` / `activeStrict` / `ignoreQueryParams` — e.g. the\n * common navigation-link case. Multiple `<Link>` components with different\n * `routeName` share ONE `router.subscribe` handle instead of creating one\n * per Link (which is what `createActiveRouteSource(router, name)` does — it\n * caches per-name, so N names = N subscriptions).\n *\n * **When NOT to use:** Link needs `activeStrict: true`, custom `routeParams`,\n * or `ignoreQueryParams: false`. Fall back to `createActiveRouteSource` —\n * its cache handles the full argument surface.\n *\n * Based on the `routeSelector` pattern pioneered by `@real-router/solid`'s\n * `RouterProvider` (`createSelector` + `areRoutesRelated`). This helper\n * ports it to framework-agnostic API so Vue / React / Preact / Svelte /\n * Angular Link components can adopt the same fast path.\n *\n * @see Solid reference implementation — `packages/solid/src/RouterProvider.tsx`\n */\nexport function createActiveNameSelector(router: Router): ActiveNameSelector {\n const cached = selectorCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n // listeners per-name — re-evaluated on every router transition\n const listenersByName = new Map<string, Set<() => void>>();\n // cached active state per-name — used to diff before notifying\n const activeByName = new Map<string, boolean>();\n\n let routerUnsubscribe: (() => void) | null = null;\n\n const isActiveNonStrict = (routeName: string): boolean => {\n const current = router.getState();\n\n if (!current) {\n return false;\n }\n\n // Empty string represents the root of the name hierarchy — every named\n // route is its descendant. Without this short-circuit, `current.name`\n // would have to equal `\"\"` or start with `\".\"` (both impossible for\n // valid route names), breaking symmetry with `createRouteNodeSource(\"\")`\n // which is always-active when a route is current.\n if (routeName === \"\") {\n return true;\n }\n\n return (\n current.name === routeName || current.name.startsWith(`${routeName}.`)\n );\n };\n\n const connect = (): void => {\n routerUnsubscribe = router.subscribe((next) => {\n for (const [routeName, listeners] of listenersByName) {\n // Cheap pre-filter: if neither new nor previous route is related\n // to this name, its active state cannot have changed. Empty\n // routeName is the implicit root — every route is its descendant,\n // so the filter would falsely exclude it (`areRoutesRelated`\n // doesn't treat `\"\"` specially). Skip the filter for the root.\n if (routeName !== \"\") {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n continue;\n }\n }\n\n // activeByName always has an entry for names present in listenersByName —\n // subscribe() seeds it, and we only iterate over listenersByName.\n const prevActive = activeByName.get(routeName) === true;\n const nextActive = isActiveNonStrict(routeName);\n\n if (prevActive === nextActive) {\n continue;\n }\n\n activeByName.set(routeName, nextActive);\n for (const listener of listeners) {\n listener();\n }\n }\n });\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const subscribe = (routeName: string, listener: () => void): (() => void) => {\n let listeners = listenersByName.get(routeName);\n\n if (!listeners) {\n listeners = new Set();\n listenersByName.set(routeName, listeners);\n activeByName.set(routeName, isActiveNonStrict(routeName));\n }\n\n listeners.add(listener);\n\n if (!routerUnsubscribe) {\n connect();\n }\n\n let unsubscribed = false;\n\n return () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n listenersByName.delete(routeName);\n activeByName.delete(routeName);\n }\n\n if (listenersByName.size === 0) {\n disconnect();\n }\n };\n };\n\n const isActive = (routeName: string): boolean => {\n const cachedActive = activeByName.get(routeName);\n\n if (cachedActive !== undefined) {\n return cachedActive;\n }\n\n // Not subscribed — compute on demand.\n return isActiveNonStrict(routeName);\n };\n\n const selector: ActiveNameSelector = {\n subscribe,\n isActive,\n destroy: noopDestroy,\n };\n\n selectorCache.set(router, selector);\n\n return selector;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#listeners","#onFirstSubscribe","#onLastUnsubscribe","#onDestroy","#currentSnapshot","#destroyed","events","events"],"sources":["../../src/BaseSource.ts","../../src/internal/readContextHash.ts","../../src/stabilizeState.ts","../../src/createRouteSource.ts","../../src/computeSnapshot.ts","../../src/internal/noopDestroy.ts","../../src/createRouteNodeSource.ts","../../src/canonicalJson.ts","../../src/normalizeActiveOptions.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts","../../src/createErrorSource.ts","../../src/createDismissableError.ts","../../src/createActiveNameSelector.ts"],"sourcesContent":["export interface BaseSourceOptions {\n onFirstSubscribe?: () => void;\n onLastUnsubscribe?: () => void;\n onDestroy?: () => void;\n}\n\nexport class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n readonly #onFirstSubscribe: (() => void) | undefined;\n readonly #onLastUnsubscribe: (() => void) | undefined;\n readonly #onDestroy: (() => void) | undefined;\n\n constructor(initialSnapshot: T, options?: BaseSourceOptions) {\n this.#currentSnapshot = initialSnapshot;\n this.#onFirstSubscribe = options?.onFirstSubscribe;\n this.#onLastUnsubscribe = options?.onLastUnsubscribe;\n this.#onDestroy = options?.onDestroy;\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n const wasFirst = this.#listeners.size === 0;\n\n // Add listener BEFORE onFirstSubscribe so that if the reconciliation in\n // onFirstSubscribe calls updateSnapshot(), this listener receives the\n // notification. Critical for useSyncExternalStore in adapters — without\n // this the post-reconnection snapshot is missed and consumers render\n // stale data. (See Preact RouteView nested remount test.)\n this.#listeners.add(listener);\n\n if (wasFirst && this.#onFirstSubscribe) {\n this.#onFirstSubscribe();\n }\n\n return () => {\n this.#listeners.delete(listener);\n\n if (\n !this.#destroyed &&\n this.#listeners.size === 0 &&\n this.#onLastUnsubscribe\n ) {\n this.#onLastUnsubscribe();\n }\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n // Isolate listener exceptions so a single throwing subscriber (e.g. React\n // error-boundary fallback throwing inside `onStoreChange`) does not block\n // the remaining subscribers — the invariant \"after updateSnapshot all\n // listeners see the new snapshot\" must hold. Re-throw asynchronously via\n // queueMicrotask so global error handlers / test harnesses still surface\n // the bug without breaking the synchronous notification fan-out.\n for (const listener of this.#listeners) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#onDestroy?.();\n this.#listeners.clear();\n }\n}\n","import type { State } from \"@real-router/core\";\n\n/**\n * @internal\n *\n * Reads the URL fragment published by `browser-plugin` / `navigation-plugin`\n * on a router state. The plugins claim the `\"url\"` namespace via\n * `state.context.url` and the `hash` field carries the **decoded** fragment.\n *\n * Returns:\n * - `undefined` — no plugin claimed the `\"url\"` namespace (hash-plugin runtime,\n * memory-plugin, SSR before hydration) OR the state itself is nullish;\n * - `\"\"` — the URL namespace exists but the fragment is empty\n * (browser-plugin on a hash-less URL).\n *\n * Callers that need the \"no namespace at all\" branch (e.g. `stabilizeState`\n * comparing cross-plugin transitions) read the raw `undefined`. Callers that\n * collapse \"no namespace\" to \"no hash\" (e.g. `createActiveRouteSource`'s\n * hash-equality check) coalesce with `?? \"\"` themselves.\n *\n * Centralising the context cast removes the previous duplicate definitions in\n * `stabilizeState.ts` and `createActiveRouteSource.ts` that drifted in\n * signature (state vs router) and default-value (undefined vs \"\") — both\n * variants are reconstructible from this single helper at the callsite.\n */\nexport function readContextHash(\n state: State | null | undefined,\n): string | undefined {\n const ctx = state?.context as { url?: { hash?: string } } | undefined;\n\n return ctx?.url?.hash;\n}\n","import { readContextHash } from \"./internal/readContextHash.js\";\n\nimport type { State } from \"@real-router/core\";\n\n/**\n * State-aware stabilization for route snapshots.\n *\n * Compares `path` (canonical name+params), `state.context.url.hash`\n * (URL fragment, #532), and `state.transition.reload` (#605). When all\n * three match (idempotent navigation), returns `prev` (preserving\n * reference) so frameworks can skip re-renders. When any of them flips,\n * returns `next` so consumers subscribing through `useRoute()` see the\n * new state.\n *\n * `transition.reload === true` is the user's explicit signal for a\n * non-idempotent navigation — `router.navigate(name, params, { reload:\n * true })` is the canonical pairing for `invalidate(router, namespace)`\n * and any cache-bust pattern. Bypassing stabilization for reloads makes\n * `useRoute()` consumers see fresh `state.context.<namespace>` values\n * written by the SSR loader plugin's `subscribeLeave` handler.\n *\n * Ignores `meta` (internal: auto-increment id), other `transition` fields\n * (`from`, `segments`, `redirected`), and `state.context.navigation` /\n * `state.context.browser` (transient transition metadata) — they don't\n * affect render identity for idempotent navigations.\n *\n * Accepts `null` for compatibility with `RouterTransitionSnapshot`\n * (toRoute/fromRoute are `State | null`).\n *\n * @internal Not exported from package public API.\n */\nexport function stabilizeState<T extends State | null | undefined>(\n prev: T,\n next: T,\n): T {\n if (prev === next) {\n return prev;\n }\n if (prev?.path !== next?.path) {\n return next;\n }\n\n // After the path check, both must be the same non-null State (paths\n // matched, prev !== next reference). Read context.url.hash to detect\n // same-path-different-hash navigation (#532) — render-relevant for\n // tab-style UIs that subscribe via useRoute(). Optional chaining keeps\n // the access null-safe without forbidden non-null assertions.\n if (readContextHash(prev) !== readContextHash(next)) {\n return next;\n }\n\n // Explicit reload navigation (#605) — caller asked to bypass dedupe so\n // observers see fresh `state.context` written by `invalidate()`-driven\n // loader re-runs. The path equality above guarantees both prev and next\n // are either non-null with matching paths or both nullish; only the\n // non-null branch can carry a meaningful `transition.reload`.\n if (readReloadFlag(next)) {\n return next;\n }\n\n return prev;\n}\n\nfunction readReloadFlag(state: State | null | undefined): boolean {\n // Defensive read: `transition` is mandatory in the public State type, but a\n // plugin returning a malformed state (or a future fork) shouldn't crash the\n // stabilizer with a TypeError. We cast to a structurally-loose shape so the\n // optional chain is permitted; the runtime guard preserves dedup (false =\n // not-a-reload) for malformed inputs.\n const transition = (\n state as { transition?: { reload?: boolean } } | null | undefined\n )?.transition;\n\n return transition?.reload === true;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteSnapshot>(\n {\n route: router.getState(),\n previousRoute: undefined,\n },\n {\n onFirstSubscribe: () => {\n routerUnsubscribe = router.subscribe((next) => {\n const prev = source.getSnapshot();\n const newRoute = stabilizeState(prev.route, next.route);\n const newPreviousRoute = stabilizeState(\n prev.previousRoute,\n next.previousRoute,\n );\n\n if (\n newRoute !== prev.route ||\n newPreviousRoute !== prev.previousRoute\n ) {\n source.updateSnapshot({\n route: newRoute,\n previousRoute: newPreviousRoute,\n });\n }\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n const newRoute = stabilizeState(currentSnapshot.route, route);\n const newPreviousRoute = stabilizeState(\n currentSnapshot.previousRoute,\n previousRoute,\n );\n\n if (\n newRoute === currentSnapshot.route &&\n newPreviousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route: newRoute, previousRoute: newPreviousRoute };\n}\n","/**\n * @internal\n *\n * Shared no-op `destroy()` for cached `RouterSource` wrappers.\n *\n * Cached factories (`getTransitionSource`, `getErrorSource`, `createDismissableError`,\n * `createActiveNameSelector`, cache hit in `createRouteNodeSource`,\n * `createActiveRouteSource`) return a wrapper whose `destroy()` is a no-op —\n * the underlying source is shared across all consumers and lives as long as\n * the router (WeakMap entry releases on router GC).\n *\n * One module-level function shared by every cached factory keeps the wrapper\n * shape (`{ subscribe, getSnapshot, destroy: noopDestroy }`) byte-stable and\n * eliminates the previous six copies. (Bundlers inline a stand-alone arrow\n * just as readily, so the cost is purely on the maintenance side.)\n */\nexport function noopDestroy(): void {\n // Shared cached source — external destroy() is a no-op.\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst nodeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<RouteNodeSnapshot>>\n>();\n\n/**\n * Creates a source scoped to a specific route node.\n *\n * **Per-router + per-nodeName cache:** repeated calls with the same\n * `(router, nodeName)` return the same shared instance. `N` consumers\n * calling `createRouteNodeSource(r, \"users\")` produce one router subscription\n * shared across all of them.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n *\n * `destroy()` on the returned source is a no-op — the shared instance lives\n * as long as the router itself (the WeakMap entry releases automatically on\n * router GC). Callers that need an isolated instance with working teardown\n * can use `buildRouteNodeSource` internally (not exported).\n */\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let perRouter = nodeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n nodeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(nodeName);\n\n if (!cached) {\n const source = buildRouteNodeSource(router, nodeName);\n\n // Wrap with no-op destroy. The shared source lives with the router.\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(nodeName, cached);\n }\n\n return cached;\n}\n\nfunction buildRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n // Built once per cached source instance; safe — createRouteNodeSource is\n // itself per-(router, nodeName) cached, so shouldUpdate is called once.\n const shouldUpdate = router.shouldUpdateNode(nodeName);\n\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteNodeSnapshot>(\n computeSnapshot(initialSnapshot, router, nodeName),\n {\n onFirstSubscribe: () => {\n // Reconcile snapshot with current router state before connecting.\n // Covers reconnection after Activity hide/show cycles where the\n // source was disconnected and missed navigation events.\n const reconciled = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n );\n\n if (!Object.is(reconciled, source.getSnapshot())) {\n source.updateSnapshot(reconciled);\n }\n\n // Connect to router on first subscription\n routerUnsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n // computeSnapshot returns the SAME currentSnapshot reference when\n // both route and previousRoute stabilize to prev — guard against\n // emitting redundant updates to listeners (matters for signal-\n // based adapters that re-run effects on every set).\n /* v8 ignore next 3 -- @preserve: structurally unreachable after #605\n — reload navs always return fresh refs via stabilizeState, and\n within-node non-reload navs short-circuit at shouldUpdate. Guard\n kept for defensive correctness against future stabilizer changes. */\n if (Object.is(source.getSnapshot(), newSnapshot)) {\n return;\n }\n\n source.updateSnapshot(newSnapshot);\n });\n },\n onLastUnsubscribe: disconnect,\n },\n );\n\n return source;\n}\n","/**\n * Serializes a value into a stable JSON string — object keys are sorted at\n * every level so that `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` produce the same\n * output.\n *\n * Used as a cache key for `createActiveRouteSource` so that equivalent params\n * objects share the same cached source regardless of key order.\n *\n * **Divergence from `shared/dom-utils/scroll-restore.canonicalJson` — by\n * design.** That sibling implementation is the cheap navigation-hot-path\n * variant (uses `localeCompare`, plain-object accumulator, native cycle\n * detector) and pairs with `safeKeyOf` for crash-tolerance. This one is the\n * strict cache-key variant: byte-order compare, prototype-less accumulator,\n * bespoke cycle detection — eagerly throws on inputs that would cause\n * collisions or pollution, so callers can fall back to non-cached sources.\n * They are NOT interchangeable; cross-package equivalence is explicitly not\n * a goal (audit-2 / audit-2026-05-17 §2).\n *\n * Edge cases:\n * - Arrays preserve order (canonical: index-ordered already).\n * - `undefined` values are dropped (standard JSON behaviour).\n * - `Date` is serialized via its `toJSON` method (ISO string).\n * - `Symbol` becomes `undefined` (standard JSON behaviour).\n * - `BigInt` throws via `JSON.stringify` defaults.\n * - `Map`, `Set`, `RegExp`, `WeakMap`, `WeakSet` would silently collapse to\n * `\"{}\"` (no enumerable own keys), which would cause **different inputs to\n * share the same cache key**. We detect these explicitly and throw — callers\n * then take the non-cached fallback path (same behaviour as `BigInt`).\n * - Circular references throw `TypeError` (parity with native `JSON.stringify`).\n * The replacer copies each object level into a fresh prototype-less record,\n * so we must run our own cycle detection — the native detector never sees\n * the original object graph.\n * - `__proto__` keys are preserved as own properties (no prototype pollution\n * and no silent collision between `{ __proto__: x, b: 1 }` and `{ b: 1 }`).\n *\n * In practice, route params carry primitives (`string | number | boolean`);\n * the cache-key-collision edge cases above are defensive bugs caught loudly.\n */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(canonicalize(value, new Set<object>()));\n}\n\n// Key comparison uses byte-order (`<` / `>`) instead of `localeCompare()` so\n// the output is independent of the Node.js ICU build / system locale. Same\n// canonical form on every machine — required for cache-key stability.\nfunction compareKeys(left: string, right: string): number {\n return left < right ? -1 : 1;\n}\n\n/**\n * Returns a structural clone with object keys sorted at every level. Path-based\n * cycle detection (`path` set) matches the semantics of native `JSON.stringify`\n * — a `TypeError` is thrown on a true cycle, but the same object reachable via\n * two independent branches (DAG) serialises fine.\n *\n * `Date` instances pass through unchanged so `JSON.stringify` can invoke their\n * `toJSON` hook. Other built-ins (`Map`, `Set`, `WeakMap`, `WeakSet`, `RegExp`)\n * throw eagerly to avoid silent cache-key collisions.\n */\nfunction canonicalize(value: unknown, path: Set<object>): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value;\n }\n\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof RegExp\n ) {\n throw new TypeError(\n `canonicalJson: cannot serialize ${(value as { constructor: { name: string } }).constructor.name} — non-enumerable own keys collapse to \"{}\" and would cause cache-key collisions. Pass primitive params (string | number | boolean) instead.`,\n );\n }\n\n if (path.has(value)) {\n throw new TypeError(\n \"canonicalJson: cannot serialize circular structure (cycle detected during traversal).\",\n );\n }\n\n path.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, path));\n }\n\n // Use a null-prototype record so `__proto__` is treated as a regular\n // own property — assigning to a plain `{}` would set the prototype\n // chain instead and silently collide with inputs that omit the key.\n const sorted: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n const keys = Object.keys(value).toSorted(compareKeys);\n\n for (const key of keys) {\n sorted[key] = canonicalize((value as Record<string, unknown>)[key], path);\n }\n\n return sorted;\n } finally {\n path.delete(value);\n }\n}\n","import type { ActiveRouteSourceOptions } from \"./types.js\";\n\n/**\n * Normalized options shape — booleans are required (filled with defaults),\n * `hash` stays `string | undefined` because `undefined` is the meaningful\n * \"ignore hash\" sentinel that callers pass intentionally.\n */\nexport interface NormalizedActiveOptions {\n strict: boolean;\n ignoreQueryParams: boolean;\n hash: string | undefined;\n}\n\n/**\n * Default options for `createActiveRouteSource` and adapter-level helpers.\n *\n * Frozen to prevent accidental mutation by consumers.\n */\nexport const DEFAULT_ACTIVE_OPTIONS: Readonly<NormalizedActiveOptions> =\n Object.freeze({\n strict: false,\n ignoreQueryParams: true,\n hash: undefined,\n });\n\n/**\n * Normalizes partial `ActiveRouteSourceOptions` into a fully-defaulted object.\n *\n * Use this to produce a stable options record for comparison, caching, or\n * downstream consumers that require all fields present.\n */\nexport function normalizeActiveOptions(\n options?: ActiveRouteSourceOptions,\n): NormalizedActiveOptions {\n return {\n strict: options?.strict ?? DEFAULT_ACTIVE_OPTIONS.strict,\n ignoreQueryParams:\n options?.ignoreQueryParams ?? DEFAULT_ACTIVE_OPTIONS.ignoreQueryParams,\n hash: options?.hash,\n };\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { canonicalJson } from \"./canonicalJson.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { readContextHash } from \"./internal/readContextHash.js\";\nimport { normalizeActiveOptions } from \"./normalizeActiveOptions.js\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nconst activeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<boolean>>\n>();\n\n/**\n * Creates a source tracking whether a route (with given params/options) is active.\n *\n * **Per-router + canonical-args cache:** repeated calls with equivalent\n * arguments return the same shared instance. Param key order doesn't matter\n * (`{ a:1, b:2 }` and `{ b:2, a:1 }` hit the same cache entry via\n * `canonicalJson`).\n *\n * For cached entries `destroy()` is a no-op — shared sources live with the\n * router and release automatically on router GC (WeakMap entry).\n *\n * `BigInt`/circular params can't be serialized → the source bypasses the cache\n * and `destroy()` becomes a real teardown that detaches the underlying\n * `router.subscribe` handle.\n */\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n const { strict, ignoreQueryParams, hash } = normalizeActiveOptions(options);\n\n // BigInt/Symbol/circular refs cannot be serialized — fall back to creating\n // a fresh (non-cached) source. Callers pass these edge-case params rarely;\n // the extra allocation is acceptable.\n let key: string | undefined;\n\n try {\n // `hash === undefined` produces \"\" via String(undefined) → \"undefined\";\n // we encode it as the empty string sentinel to keep the key short and\n // distinct from the literal \"undefined\" hash value (which is a valid,\n // if unusual, fragment).\n const hashKey = hash === undefined ? \"\" : `#${hash}`;\n\n // `params === undefined` is the common Link case (`<Link to=\"users\">`\n // with no params). Skip canonicalJson(undefined) — it returns the literal\n // string \"undefined\" and template interpolation would just embed it. An\n // explicit empty sentinel avoids the call and shaves the cache-key by 9\n // characters per Link.\n const paramsKey = params === undefined ? \"\" : canonicalJson(params);\n\n // Delimiter `|` is safe because route names use `.` as the segment\n // separator (`users.list`, not `users|list`) and canonicalJson-encoded\n // params escape `\"` (so any literal `|` inside params lives inside a\n // quoted JSON string and can't be confused with our delimiter). If route\n // names ever grow a `|` character, this composite key would become\n // ambiguous — change the separator to a control char or hash-encode each\n // field.\n key = `${routeName}|${paramsKey}|${String(strict)}|${String(ignoreQueryParams)}|${hashKey}`;\n } catch {\n key = undefined;\n }\n\n if (key === undefined) {\n // Non-cached fallback (canonicalJson threw on BigInt / circular / etc.).\n // Return the real source — `destroy()` must unwind the router subscription;\n // otherwise the wrapper leaks for the lifetime of the router.\n return buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n }\n\n let perRouter = activeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n activeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(key);\n\n if (!cached) {\n const source = buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(key, cached);\n }\n\n return cached;\n}\n\n/**\n * Combines route-name match with optional hash match (#532).\n *\n * - Route-name match: `router.isActiveRoute(name, params, strict, ignoreQueryParams)`.\n * - Hash match (only when `hash !== undefined`): `state.context.url.hash` must\n * equal the requested fragment exactly. With hash-plugin (no `url`\n * namespace), this returns `false` — the documented limitation.\n */\nfunction computeActive(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): boolean {\n const routeActive = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n if (!routeActive) {\n return false;\n }\n if (hash === undefined) {\n return true;\n }\n\n // `readContextHash` returns `undefined` when no URL plugin claimed the\n // namespace (hash-plugin runtime, memory-plugin, SSR). For hash-equality\n // matching we collapse that to `\"\"` — a hash-aware Link with no URL plugin\n // can only match when the consumer also asked for `hash: \"\"`.\n return (readContextHash(router.getState()) ?? \"\") === hash;\n}\n\nfunction buildActiveRouteSource(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): RouterSource<boolean> {\n const initialValue = computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n let routerUnsubscribe: (() => void) | undefined;\n\n const source = new BaseSource(initialValue, {\n onDestroy: () => {\n routerUnsubscribe?.();\n routerUnsubscribe = undefined;\n },\n });\n\n // Eager connection: subscribe to router immediately. For the cached path,\n // the returned wrapper has a no-op destroy and the handle lives with the\n // router (released on router GC). For the non-cached fallback (BigInt /\n // circular params), the handle is unwound through `onDestroy` above.\n routerUnsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n // Hash-aware sources also flip on same-path-different-hash transitions.\n // The route comparison alone misses these (route is identical), but the\n // hash claim updated, so we must re-evaluate. Detect via the `hashChanged`\n // flag published by URL plugins.\n const hashFlip =\n hash !== undefined &&\n ((next.route.context as { url?: { hashChanged?: boolean } } | undefined)\n ?.url?.hashChanged ??\n false);\n\n if (!isNewRelated && !isPrevRelated && !hashFlip) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization. (Hash check would\n // also fail without route-match, so this short-circuit holds for\n // hash-aware sources too.)\n const newValue = isNewRelated\n ? computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n )\n : false;\n\n if (!Object.is(source.getSnapshot(), newValue)) {\n source.updateSnapshot(newValue);\n }\n });\n\n return source;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\n// Frozen so accidental consumer mutation (`source.getSnapshot().toRoute = X`)\n// throws in strict mode. The singleton ref is shared across every IDLE state\n// for the lifetime of the process — mutating it would corrupt the contract\n// \"all IDLE snapshots are the same object reference\" relied on by every\n// adapter's useSyncExternalStore equivalent.\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = Object.freeze({\n isTransitioning: false,\n isLeaveApproved: false,\n toRoute: null,\n fromRoute: null,\n});\n\nconst transitionSourceCache = new WeakMap<\n Router,\n RouterSource<RouterTransitionSnapshot>\n>();\n\n/**\n * @internal test-only export — returns the next snapshot for a TRANSITION_START\n * payload, or `null` when the same-paths dedup guard should suppress the\n * update. Exported so the (structurally-unreachable after #605) guard can be\n * exercised by unit tests without resorting to private-API hacks.\n */\nexport function nextTransitionStartSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isTransitioning &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: false,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\n/**\n * @internal test-only export — analogous to {@link nextTransitionStartSnapshot}\n * for the LEAVE_APPROVE payload. The guard is structurally unreachable in\n * practice (router emits LEAVE_APPROVE exactly once per pipeline) but stays\n * for plugin-driven re-entrant flows.\n */\nexport function nextLeaveApproveSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isLeaveApproved &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: true,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n const source = new BaseSource(IDLE_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n // The same-paths dedup branch inside nextTransitionStartSnapshot is\n // structurally unreachable after #605 (every router-emitted\n // TRANSITION_START carries a fresh State per navigate()), but the\n // helper is kept testable for future stabilizer changes — see the\n // direct unit test in createTransitionSource.test.ts.\n const next = nextTransitionStartSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow; covered directly via nextTransitionStartSnapshot\n unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(\n events.TRANSITION_LEAVE_APPROVE,\n (toState: State, fromState?: State) => {\n const next = nextLeaveApproveSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow (LEAVE_APPROVE fires once per pipeline); covered\n directly via nextLeaveApproveSnapshot unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached transition source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single TransitionSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createTransitionSource(router)` directly — it returns a fresh instance with\n * a working `destroy()`.\n */\nexport function getTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n let cached = transitionSourceCache.get(router);\n\n if (!cached) {\n const source = createTransitionSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n transitionSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouterErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State, RouterError } from \"@real-router/core\";\n\nconst INITIAL_SNAPSHOT: RouterErrorSnapshot = {\n error: null,\n toRoute: null,\n fromRoute: null,\n version: 0,\n};\n\nconst errorSourceCache = new WeakMap<\n Router,\n RouterSource<RouterErrorSnapshot>\n>();\n\nexport function createErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let errorVersion = 0;\n let hasError = false;\n\n const source = new BaseSource(INITIAL_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_ERROR,\n (\n toState: State | undefined,\n fromState: State | undefined,\n err: RouterError,\n ) => {\n errorVersion++;\n hasError = true;\n source.updateSnapshot({\n error: err,\n toRoute: toState ?? null,\n /* v8 ignore next -- @preserve: fromState undefined only during start() error; unreachable via navigate() */\n fromRoute: fromState ?? null,\n version: errorVersion,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, () => {\n // Skip if no error — avoids unnecessary re-renders.\n // BaseSource.updateSnapshot() always notifies listeners (new object = new ref),\n // and useSyncExternalStore compares via Object.is().\n if (hasError) {\n hasError = false;\n source.updateSnapshot({\n error: null,\n toRoute: null,\n fromRoute: null,\n version: errorVersion,\n });\n }\n }),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached error source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single ErrorSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createErrorSource(router)` directly — it returns a fresh instance with a\n * working `destroy()`.\n */\nexport function getErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let cached = errorSourceCache.get(router);\n\n if (!cached) {\n const source = createErrorSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n errorSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { getErrorSource } from \"./createErrorSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { DismissableErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst dismissableCache = new WeakMap<\n Router,\n RouterSource<DismissableErrorSnapshot>\n>();\n\n/**\n * Returns a per-router cached source that wraps `getErrorSource(router)` with\n * an integrated \"dismissed\" version counter, exposing a single reactive\n * snapshot `{ error, toRoute, fromRoute, version, resetError }`.\n *\n * Each `RouterErrorBoundary` in a framework adapter subscribes to this one\n * source instead of re-implementing the `dismissedVersion` state pattern\n * locally. The 6-copy duplicate across adapters collapses to this helper.\n *\n * **Semantics:**\n * - `error` is non-null only when `underlying.version > dismissedVersion`.\n * - `resetError()` sets `dismissedVersion = current underlying version`,\n * immediately clearing `error` to `null` and notifying all listeners.\n * - A subsequent `TRANSITION_ERROR` advances `version` beyond `dismissedVersion`,\n * so `error` becomes non-null again — no additional plumbing needed.\n *\n * **Cached:** one instance per router. `destroy()` on the returned source is\n * a no-op. Shared across all `RouterErrorBoundary` consumers.\n */\nexport function createDismissableError(\n router: Router,\n): RouterSource<DismissableErrorSnapshot> {\n const cached = dismissableCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const errorSource = getErrorSource(router);\n\n let dismissedVersion = -1;\n // Hoisted up here so the `onFirstSubscribe` closure below can read/write\n // it before `disconnect()`'s declaration. JS hoisting makes the original\n // post-declaration order legal, but reading top-to-bottom is clearer.\n let unsubFromError: (() => void) | null = null;\n\n const buildDismissableSnapshot = (): DismissableErrorSnapshot => {\n const snap = errorSource.getSnapshot();\n const isDismissed = snap.version <= dismissedVersion;\n\n return {\n error: isDismissed ? null : snap.error,\n toRoute: isDismissed ? null : snap.toRoute,\n fromRoute: isDismissed ? null : snap.fromRoute,\n version: snap.version,\n resetError,\n };\n };\n\n const source = new BaseSource<DismissableErrorSnapshot>(\n buildDismissableSnapshot(),\n {\n onFirstSubscribe: () => {\n unsubFromError = errorSource.subscribe(() => {\n source.updateSnapshot(buildDismissableSnapshot());\n });\n },\n onLastUnsubscribe: () => {\n disconnect();\n },\n },\n );\n\n function resetError(): void {\n const currentVersion = errorSource.getSnapshot().version;\n\n // No-op guard: if we already dismissed at this version (or are even ahead\n // of the live error stream), there's nothing to clear. Skipping prevents\n // a redundant snapshot allocation + listener notification under tight\n // resetError(); resetError() patterns — common when a RouterErrorBoundary\n // user clicks \"dismiss\" while another dismiss is already in flight.\n if (currentVersion <= dismissedVersion) {\n return;\n }\n\n dismissedVersion = currentVersion;\n source.updateSnapshot(buildDismissableSnapshot());\n }\n\n function disconnect(): void {\n const unsub = unsubFromError;\n\n unsubFromError = null;\n unsub?.();\n }\n\n const wrapper: RouterSource<DismissableErrorSnapshot> = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n\n dismissableCache.set(router, wrapper);\n\n return wrapper;\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport interface ActiveNameSelector {\n /**\n * Subscribes to active-state changes of a specific route name.\n * Listener is called only when `isActive(routeName)` for this name transitions.\n * Returns an unsubscribe function.\n */\n subscribe: (routeName: string, listener: () => void) => () => void;\n /**\n * O(1) active check for the given route name (non-strict by default —\n * matches descendants). Uses the shared underlying router subscription.\n */\n isActive: (routeName: string) => boolean;\n /** No-op on the cached wrapper. */\n destroy: () => void;\n}\n\nconst selectorCache = new WeakMap<Router, ActiveNameSelector>();\n\n/**\n * Per-router cached selector providing O(1) active-route checks with one\n * shared router subscription for any number of distinct `routeName`\n * consumers.\n *\n * **When to use:** framework `Link` components that need an active boolean\n * without custom `params` / `activeStrict` / `ignoreQueryParams` — e.g. the\n * common navigation-link case. Multiple `<Link>` components with different\n * `routeName` share ONE `router.subscribe` handle instead of creating one\n * per Link (which is what `createActiveRouteSource(router, name)` does — it\n * caches per-name, so N names = N subscriptions).\n *\n * **When NOT to use:** Link needs `activeStrict: true`, custom `routeParams`,\n * or `ignoreQueryParams: false`. Fall back to `createActiveRouteSource` —\n * its cache handles the full argument surface.\n *\n * Based on the `routeSelector` pattern pioneered by `@real-router/solid`'s\n * `RouterProvider` (`createSelector` + `areRoutesRelated`). This helper\n * ports it to framework-agnostic API so Vue / React / Preact / Svelte /\n * Angular Link components can adopt the same fast path.\n *\n * @see Solid reference implementation — `packages/solid/src/RouterProvider.tsx`\n */\nexport function createActiveNameSelector(router: Router): ActiveNameSelector {\n const cached = selectorCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n // listeners per-name — re-evaluated on every router transition\n const listenersByName = new Map<string, Set<() => void>>();\n // cached active state per-name — used to diff before notifying\n const activeByName = new Map<string, boolean>();\n\n let routerUnsubscribe: (() => void) | null = null;\n\n const isActiveNonStrict = (routeName: string): boolean => {\n const current = router.getState();\n\n if (!current) {\n return false;\n }\n\n // Empty string represents the root of the name hierarchy — every named\n // route is its descendant. Without this short-circuit, `current.name`\n // would have to equal `\"\"` or start with `\".\"` (both impossible for\n // valid route names), breaking symmetry with `createRouteNodeSource(\"\")`\n // which is always-active when a route is current.\n if (routeName === \"\") {\n return true;\n }\n\n return (\n current.name === routeName || current.name.startsWith(`${routeName}.`)\n );\n };\n\n const connect = (): void => {\n routerUnsubscribe = router.subscribe((next) => {\n for (const [routeName, listeners] of listenersByName) {\n // Cheap pre-filter: if neither new nor previous route is related\n // to this name, its active state cannot have changed. Empty\n // routeName is the implicit root — every route is its descendant,\n // so the filter would falsely exclude it (`areRoutesRelated`\n // doesn't treat `\"\"` specially). Skip the filter for the root.\n if (routeName !== \"\") {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n continue;\n }\n }\n\n // activeByName always has an entry for names present in listenersByName —\n // subscribe() seeds it, and we only iterate over listenersByName.\n const prevActive = activeByName.get(routeName) === true;\n const nextActive = isActiveNonStrict(routeName);\n\n if (prevActive === nextActive) {\n continue;\n }\n\n activeByName.set(routeName, nextActive);\n for (const listener of listeners) {\n listener();\n }\n }\n });\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const subscribe = (routeName: string, listener: () => void): (() => void) => {\n let listeners = listenersByName.get(routeName);\n\n if (!listeners) {\n listeners = new Set();\n listenersByName.set(routeName, listeners);\n activeByName.set(routeName, isActiveNonStrict(routeName));\n }\n\n listeners.add(listener);\n\n if (!routerUnsubscribe) {\n connect();\n }\n\n let unsubscribed = false;\n\n return () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n listenersByName.delete(routeName);\n activeByName.delete(routeName);\n }\n\n if (listenersByName.size === 0) {\n disconnect();\n }\n };\n };\n\n const isActive = (routeName: string): boolean => {\n const cachedActive = activeByName.get(routeName);\n\n if (cachedActive !== undefined) {\n return cachedActive;\n }\n\n // Not subscribed — compute on demand.\n return isActiveNonStrict(routeName);\n };\n\n const selector: ActiveNameSelector = {\n subscribe,\n isActive,\n destroy: noopDestroy,\n };\n\n selectorCache.set(router, selector);\n\n return selector;\n}\n"],"mappings":"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"}
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["#listeners","#onFirstSubscribe","#onLastUnsubscribe","#onDestroy","#currentSnapshot","#destroyed"],"sources":["../../src/BaseSource.ts","../../src/internal/readContextHash.ts","../../src/stabilizeState.ts","../../src/createRouteSource.ts","../../src/computeSnapshot.ts","../../src/internal/noopDestroy.ts","../../src/createRouteNodeSource.ts","../../src/canonicalJson.ts","../../src/normalizeActiveOptions.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts","../../src/createErrorSource.ts","../../src/createDismissableError.ts","../../src/createActiveNameSelector.ts"],"sourcesContent":["export interface BaseSourceOptions {\n onFirstSubscribe?: () => void;\n onLastUnsubscribe?: () => void;\n onDestroy?: () => void;\n}\n\nexport class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n readonly #onFirstSubscribe: (() => void) | undefined;\n readonly #onLastUnsubscribe: (() => void) | undefined;\n readonly #onDestroy: (() => void) | undefined;\n\n constructor(initialSnapshot: T, options?: BaseSourceOptions) {\n this.#currentSnapshot = initialSnapshot;\n this.#onFirstSubscribe = options?.onFirstSubscribe;\n this.#onLastUnsubscribe = options?.onLastUnsubscribe;\n this.#onDestroy = options?.onDestroy;\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n const wasFirst = this.#listeners.size === 0;\n\n // Add listener BEFORE onFirstSubscribe so that if the reconciliation in\n // onFirstSubscribe calls updateSnapshot(), this listener receives the\n // notification. Critical for useSyncExternalStore in adapters — without\n // this the post-reconnection snapshot is missed and consumers render\n // stale data. (See Preact RouteView nested remount test.)\n this.#listeners.add(listener);\n\n if (wasFirst && this.#onFirstSubscribe) {\n this.#onFirstSubscribe();\n }\n\n return () => {\n this.#listeners.delete(listener);\n\n if (\n !this.#destroyed &&\n this.#listeners.size === 0 &&\n this.#onLastUnsubscribe\n ) {\n this.#onLastUnsubscribe();\n }\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n // Isolate listener exceptions so a single throwing subscriber (e.g. React\n // error-boundary fallback throwing inside `onStoreChange`) does not block\n // the remaining subscribers — the invariant \"after updateSnapshot all\n // listeners see the new snapshot\" must hold. Re-throw asynchronously via\n // queueMicrotask so global error handlers / test harnesses still surface\n // the bug without breaking the synchronous notification fan-out.\n for (const listener of this.#listeners) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#onDestroy?.();\n this.#listeners.clear();\n }\n}\n","import type { State } from \"@real-router/core\";\n\n/**\n * @internal\n *\n * Reads the URL fragment published by `browser-plugin` / `navigation-plugin`\n * on a router state. The plugins claim the `\"url\"` namespace via\n * `state.context.url` and the `hash` field carries the **decoded** fragment.\n *\n * Returns:\n * - `undefined` — no plugin claimed the `\"url\"` namespace (hash-plugin runtime,\n * memory-plugin, SSR before hydration) OR the state itself is nullish;\n * - `\"\"` — the URL namespace exists but the fragment is empty\n * (browser-plugin on a hash-less URL).\n *\n * Callers that need the \"no namespace at all\" branch (e.g. `stabilizeState`\n * comparing cross-plugin transitions) read the raw `undefined`. Callers that\n * collapse \"no namespace\" to \"no hash\" (e.g. `createActiveRouteSource`'s\n * hash-equality check) coalesce with `?? \"\"` themselves.\n *\n * Centralising the context cast removes the previous duplicate definitions in\n * `stabilizeState.ts` and `createActiveRouteSource.ts` that drifted in\n * signature (state vs router) and default-value (undefined vs \"\") — both\n * variants are reconstructible from this single helper at the callsite.\n */\nexport function readContextHash(\n state: State | null | undefined,\n): string | undefined {\n const ctx = state?.context as { url?: { hash?: string } } | undefined;\n\n return ctx?.url?.hash;\n}\n","import { readContextHash } from \"./internal/readContextHash.js\";\n\nimport type { State } from \"@real-router/core\";\n\n/**\n * State-aware stabilization for route snapshots.\n *\n * Compares `path` (canonical name+params), `state.context.url.hash`\n * (URL fragment, #532), and `state.transition.reload` (#605). When all\n * three match (idempotent navigation), returns `prev` (preserving\n * reference) so frameworks can skip re-renders. When any of them flips,\n * returns `next` so consumers subscribing through `useRoute()` see the\n * new state.\n *\n * `transition.reload === true` is the user's explicit signal for a\n * non-idempotent navigation — `router.navigate(name, params, { reload:\n * true })` is the canonical pairing for `invalidate(router, namespace)`\n * and any cache-bust pattern. Bypassing stabilization for reloads makes\n * `useRoute()` consumers see fresh `state.context.<namespace>` values\n * written by the SSR loader plugin's `subscribeLeave` handler.\n *\n * Ignores `meta` (internal: auto-increment id), other `transition` fields\n * (`from`, `segments`, `redirected`), and `state.context.navigation` /\n * `state.context.browser` (transient transition metadata) — they don't\n * affect render identity for idempotent navigations.\n *\n * Accepts `null` for compatibility with `RouterTransitionSnapshot`\n * (toRoute/fromRoute are `State | null`).\n *\n * @internal Not exported from package public API.\n */\nexport function stabilizeState<T extends State | null | undefined>(\n prev: T,\n next: T,\n): T {\n if (prev === next) {\n return prev;\n }\n if (prev?.path !== next?.path) {\n return next;\n }\n\n // After the path check, both must be the same non-null State (paths\n // matched, prev !== next reference). Read context.url.hash to detect\n // same-path-different-hash navigation (#532) — render-relevant for\n // tab-style UIs that subscribe via useRoute(). Optional chaining keeps\n // the access null-safe without forbidden non-null assertions.\n if (readContextHash(prev) !== readContextHash(next)) {\n return next;\n }\n\n // Explicit reload navigation (#605) — caller asked to bypass dedupe so\n // observers see fresh `state.context` written by `invalidate()`-driven\n // loader re-runs. The path equality above guarantees both prev and next\n // are either non-null with matching paths or both nullish; only the\n // non-null branch can carry a meaningful `transition.reload`.\n if (readReloadFlag(next)) {\n return next;\n }\n\n return prev;\n}\n\nfunction readReloadFlag(state: State | null | undefined): boolean {\n // Defensive read: `transition` is mandatory in the public State type, but a\n // plugin returning a malformed state (or a future fork) shouldn't crash the\n // stabilizer with a TypeError. We cast to a structurally-loose shape so the\n // optional chain is permitted; the runtime guard preserves dedup (false =\n // not-a-reload) for malformed inputs.\n const transition = (\n state as { transition?: { reload?: boolean } } | null | undefined\n )?.transition;\n\n return transition?.reload === true;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteSnapshot>(\n {\n route: router.getState(),\n previousRoute: undefined,\n },\n {\n onFirstSubscribe: () => {\n routerUnsubscribe = router.subscribe((next) => {\n const prev = source.getSnapshot();\n const newRoute = stabilizeState(prev.route, next.route);\n const newPreviousRoute = stabilizeState(\n prev.previousRoute,\n next.previousRoute,\n );\n\n if (\n newRoute !== prev.route ||\n newPreviousRoute !== prev.previousRoute\n ) {\n source.updateSnapshot({\n route: newRoute,\n previousRoute: newPreviousRoute,\n });\n }\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n const newRoute = stabilizeState(currentSnapshot.route, route);\n const newPreviousRoute = stabilizeState(\n currentSnapshot.previousRoute,\n previousRoute,\n );\n\n if (\n newRoute === currentSnapshot.route &&\n newPreviousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route: newRoute, previousRoute: newPreviousRoute };\n}\n","/**\n * @internal\n *\n * Shared no-op `destroy()` for cached `RouterSource` wrappers.\n *\n * Cached factories (`getTransitionSource`, `getErrorSource`, `createDismissableError`,\n * `createActiveNameSelector`, cache hit in `createRouteNodeSource`,\n * `createActiveRouteSource`) return a wrapper whose `destroy()` is a no-op —\n * the underlying source is shared across all consumers and lives as long as\n * the router (WeakMap entry releases on router GC).\n *\n * One module-level function shared by every cached factory keeps the wrapper\n * shape (`{ subscribe, getSnapshot, destroy: noopDestroy }`) byte-stable and\n * eliminates the previous six copies. (Bundlers inline a stand-alone arrow\n * just as readily, so the cost is purely on the maintenance side.)\n */\nexport function noopDestroy(): void {\n // Shared cached source — external destroy() is a no-op.\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst nodeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<RouteNodeSnapshot>>\n>();\n\n/**\n * Creates a source scoped to a specific route node.\n *\n * **Per-router + per-nodeName cache:** repeated calls with the same\n * `(router, nodeName)` return the same shared instance. `N` consumers\n * calling `createRouteNodeSource(r, \"users\")` produce one router subscription\n * shared across all of them.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n *\n * `destroy()` on the returned source is a no-op — the shared instance lives\n * as long as the router itself (the WeakMap entry releases automatically on\n * router GC). Callers that need an isolated instance with working teardown\n * can use `buildRouteNodeSource` internally (not exported).\n */\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let perRouter = nodeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n nodeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(nodeName);\n\n if (!cached) {\n const source = buildRouteNodeSource(router, nodeName);\n\n // Wrap with no-op destroy. The shared source lives with the router.\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(nodeName, cached);\n }\n\n return cached;\n}\n\nfunction buildRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n // Built once per cached source instance; safe — createRouteNodeSource is\n // itself per-(router, nodeName) cached, so shouldUpdate is called once.\n const shouldUpdate = router.shouldUpdateNode(nodeName);\n\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteNodeSnapshot>(\n computeSnapshot(initialSnapshot, router, nodeName),\n {\n onFirstSubscribe: () => {\n // Reconcile snapshot with current router state before connecting.\n // Covers reconnection after Activity hide/show cycles where the\n // source was disconnected and missed navigation events.\n const reconciled = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n );\n\n if (!Object.is(reconciled, source.getSnapshot())) {\n source.updateSnapshot(reconciled);\n }\n\n // Connect to router on first subscription\n routerUnsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n // computeSnapshot returns the SAME currentSnapshot reference when\n // both route and previousRoute stabilize to prev — guard against\n // emitting redundant updates to listeners (matters for signal-\n // based adapters that re-run effects on every set).\n /* v8 ignore next 3 -- @preserve: structurally unreachable after #605\n — reload navs always return fresh refs via stabilizeState, and\n within-node non-reload navs short-circuit at shouldUpdate. Guard\n kept for defensive correctness against future stabilizer changes. */\n if (Object.is(source.getSnapshot(), newSnapshot)) {\n return;\n }\n\n source.updateSnapshot(newSnapshot);\n });\n },\n onLastUnsubscribe: disconnect,\n },\n );\n\n return source;\n}\n","/**\n * Serializes a value into a stable JSON string — object keys are sorted at\n * every level so that `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` produce the same\n * output.\n *\n * Used as a cache key for `createActiveRouteSource` so that equivalent params\n * objects share the same cached source regardless of key order.\n *\n * **Divergence from `shared/dom-utils/scroll-restore.canonicalJson` — by\n * design.** That sibling implementation is the cheap navigation-hot-path\n * variant (uses `localeCompare`, plain-object accumulator, native cycle\n * detector) and pairs with `safeKeyOf` for crash-tolerance. This one is the\n * strict cache-key variant: byte-order compare, prototype-less accumulator,\n * bespoke cycle detection — eagerly throws on inputs that would cause\n * collisions or pollution, so callers can fall back to non-cached sources.\n * They are NOT interchangeable; cross-package equivalence is explicitly not\n * a goal (audit-2 / audit-2026-05-17 §2).\n *\n * Edge cases:\n * - Arrays preserve order (canonical: index-ordered already).\n * - `undefined` values are dropped (standard JSON behaviour).\n * - `Date` is serialized via its `toJSON` method (ISO string).\n * - `Symbol` becomes `undefined` (standard JSON behaviour).\n * - `BigInt` throws via `JSON.stringify` defaults.\n * - `Map`, `Set`, `RegExp`, `WeakMap`, `WeakSet` would silently collapse to\n * `\"{}\"` (no enumerable own keys), which would cause **different inputs to\n * share the same cache key**. We detect these explicitly and throw — callers\n * then take the non-cached fallback path (same behaviour as `BigInt`).\n * - Circular references throw `TypeError` (parity with native `JSON.stringify`).\n * The replacer copies each object level into a fresh prototype-less record,\n * so we must run our own cycle detection — the native detector never sees\n * the original object graph.\n * - `__proto__` keys are preserved as own properties (no prototype pollution\n * and no silent collision between `{ __proto__: x, b: 1 }` and `{ b: 1 }`).\n *\n * In practice, route params carry primitives (`string | number | boolean`);\n * the cache-key-collision edge cases above are defensive bugs caught loudly.\n */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(canonicalize(value, new Set<object>()));\n}\n\n// Key comparison uses byte-order (`<` / `>`) instead of `localeCompare()` so\n// the output is independent of the Node.js ICU build / system locale. Same\n// canonical form on every machine — required for cache-key stability.\nfunction compareKeys(left: string, right: string): number {\n return left < right ? -1 : 1;\n}\n\n/**\n * Returns a structural clone with object keys sorted at every level. Path-based\n * cycle detection (`path` set) matches the semantics of native `JSON.stringify`\n * — a `TypeError` is thrown on a true cycle, but the same object reachable via\n * two independent branches (DAG) serialises fine.\n *\n * `Date` instances pass through unchanged so `JSON.stringify` can invoke their\n * `toJSON` hook. Other built-ins (`Map`, `Set`, `WeakMap`, `WeakSet`, `RegExp`)\n * throw eagerly to avoid silent cache-key collisions.\n */\nfunction canonicalize(value: unknown, path: Set<object>): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value;\n }\n\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof RegExp\n ) {\n throw new TypeError(\n `canonicalJson: cannot serialize ${(value as { constructor: { name: string } }).constructor.name} — non-enumerable own keys collapse to \"{}\" and would cause cache-key collisions. Pass primitive params (string | number | boolean) instead.`,\n );\n }\n\n if (path.has(value)) {\n throw new TypeError(\n \"canonicalJson: cannot serialize circular structure (cycle detected during traversal).\",\n );\n }\n\n path.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, path));\n }\n\n // Use a null-prototype record so `__proto__` is treated as a regular\n // own property — assigning to a plain `{}` would set the prototype\n // chain instead and silently collide with inputs that omit the key.\n const sorted: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n const keys = Object.keys(value).toSorted(compareKeys);\n\n for (const key of keys) {\n sorted[key] = canonicalize((value as Record<string, unknown>)[key], path);\n }\n\n return sorted;\n } finally {\n path.delete(value);\n }\n}\n","import type { ActiveRouteSourceOptions } from \"./types.js\";\n\n/**\n * Normalized options shape — booleans are required (filled with defaults),\n * `hash` stays `string | undefined` because `undefined` is the meaningful\n * \"ignore hash\" sentinel that callers pass intentionally.\n */\nexport interface NormalizedActiveOptions {\n strict: boolean;\n ignoreQueryParams: boolean;\n hash: string | undefined;\n}\n\n/**\n * Default options for `createActiveRouteSource` and adapter-level helpers.\n *\n * Frozen to prevent accidental mutation by consumers.\n */\nexport const DEFAULT_ACTIVE_OPTIONS: Readonly<NormalizedActiveOptions> =\n Object.freeze({\n strict: false,\n ignoreQueryParams: true,\n hash: undefined,\n });\n\n/**\n * Normalizes partial `ActiveRouteSourceOptions` into a fully-defaulted object.\n *\n * Use this to produce a stable options record for comparison, caching, or\n * downstream consumers that require all fields present.\n */\nexport function normalizeActiveOptions(\n options?: ActiveRouteSourceOptions,\n): NormalizedActiveOptions {\n return {\n strict: options?.strict ?? DEFAULT_ACTIVE_OPTIONS.strict,\n ignoreQueryParams:\n options?.ignoreQueryParams ?? DEFAULT_ACTIVE_OPTIONS.ignoreQueryParams,\n hash: options?.hash,\n };\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { canonicalJson } from \"./canonicalJson.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { readContextHash } from \"./internal/readContextHash.js\";\nimport { normalizeActiveOptions } from \"./normalizeActiveOptions.js\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nconst activeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<boolean>>\n>();\n\n/**\n * Creates a source tracking whether a route (with given params/options) is active.\n *\n * **Per-router + canonical-args cache:** repeated calls with equivalent\n * arguments return the same shared instance. Param key order doesn't matter\n * (`{ a:1, b:2 }` and `{ b:2, a:1 }` hit the same cache entry via\n * `canonicalJson`).\n *\n * For cached entries `destroy()` is a no-op — shared sources live with the\n * router and release automatically on router GC (WeakMap entry).\n *\n * `BigInt`/circular params can't be serialized → the source bypasses the cache\n * and `destroy()` becomes a real teardown that detaches the underlying\n * `router.subscribe` handle.\n */\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n const { strict, ignoreQueryParams, hash } = normalizeActiveOptions(options);\n\n // BigInt/Symbol/circular refs cannot be serialized — fall back to creating\n // a fresh (non-cached) source. Callers pass these edge-case params rarely;\n // the extra allocation is acceptable.\n let key: string | undefined;\n\n try {\n // `hash === undefined` produces \"\" via String(undefined) → \"undefined\";\n // we encode it as the empty string sentinel to keep the key short and\n // distinct from the literal \"undefined\" hash value (which is a valid,\n // if unusual, fragment).\n const hashKey = hash === undefined ? \"\" : `#${hash}`;\n\n // `params === undefined` is the common Link case (`<Link to=\"users\">`\n // with no params). Skip canonicalJson(undefined) — it returns the literal\n // string \"undefined\" and template interpolation would just embed it. An\n // explicit empty sentinel avoids the call and shaves the cache-key by 9\n // characters per Link.\n const paramsKey = params === undefined ? \"\" : canonicalJson(params);\n\n // Delimiter `|` is safe because route names use `.` as the segment\n // separator (`users.list`, not `users|list`) and canonicalJson-encoded\n // params escape `\"` (so any literal `|` inside params lives inside a\n // quoted JSON string and can't be confused with our delimiter). If route\n // names ever grow a `|` character, this composite key would become\n // ambiguous — change the separator to a control char or hash-encode each\n // field.\n key = `${routeName}|${paramsKey}|${String(strict)}|${String(ignoreQueryParams)}|${hashKey}`;\n } catch {\n key = undefined;\n }\n\n if (key === undefined) {\n // Non-cached fallback (canonicalJson threw on BigInt / circular / etc.).\n // Return the real source — `destroy()` must unwind the router subscription;\n // otherwise the wrapper leaks for the lifetime of the router.\n return buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n }\n\n let perRouter = activeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n activeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(key);\n\n if (!cached) {\n const source = buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(key, cached);\n }\n\n return cached;\n}\n\n/**\n * Combines route-name match with optional hash match (#532).\n *\n * - Route-name match: `router.isActiveRoute(name, params, strict, ignoreQueryParams)`.\n * - Hash match (only when `hash !== undefined`): `state.context.url.hash` must\n * equal the requested fragment exactly. With hash-plugin (no `url`\n * namespace), this returns `false` — the documented limitation.\n */\nfunction computeActive(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): boolean {\n const routeActive = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n if (!routeActive) {\n return false;\n }\n if (hash === undefined) {\n return true;\n }\n\n // `readContextHash` returns `undefined` when no URL plugin claimed the\n // namespace (hash-plugin runtime, memory-plugin, SSR). For hash-equality\n // matching we collapse that to `\"\"` — a hash-aware Link with no URL plugin\n // can only match when the consumer also asked for `hash: \"\"`.\n return (readContextHash(router.getState()) ?? \"\") === hash;\n}\n\nfunction buildActiveRouteSource(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): RouterSource<boolean> {\n const initialValue = computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n let routerUnsubscribe: (() => void) | undefined;\n\n const source = new BaseSource(initialValue, {\n onDestroy: () => {\n routerUnsubscribe?.();\n routerUnsubscribe = undefined;\n },\n });\n\n // Eager connection: subscribe to router immediately. For the cached path,\n // the returned wrapper has a no-op destroy and the handle lives with the\n // router (released on router GC). For the non-cached fallback (BigInt /\n // circular params), the handle is unwound through `onDestroy` above.\n routerUnsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n // Hash-aware sources also flip on same-path-different-hash transitions.\n // The route comparison alone misses these (route is identical), but the\n // hash claim updated, so we must re-evaluate. Detect via the `hashChanged`\n // flag published by URL plugins.\n const hashFlip =\n hash !== undefined &&\n ((next.route.context as { url?: { hashChanged?: boolean } } | undefined)\n ?.url?.hashChanged ??\n false);\n\n if (!isNewRelated && !isPrevRelated && !hashFlip) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization. (Hash check would\n // also fail without route-match, so this short-circuit holds for\n // hash-aware sources too.)\n const newValue = isNewRelated\n ? computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n )\n : false;\n\n if (!Object.is(source.getSnapshot(), newValue)) {\n source.updateSnapshot(newValue);\n }\n });\n\n return source;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\n// Frozen so accidental consumer mutation (`source.getSnapshot().toRoute = X`)\n// throws in strict mode. The singleton ref is shared across every IDLE state\n// for the lifetime of the process — mutating it would corrupt the contract\n// \"all IDLE snapshots are the same object reference\" relied on by every\n// adapter's useSyncExternalStore equivalent.\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = Object.freeze({\n isTransitioning: false,\n isLeaveApproved: false,\n toRoute: null,\n fromRoute: null,\n});\n\nconst transitionSourceCache = new WeakMap<\n Router,\n RouterSource<RouterTransitionSnapshot>\n>();\n\n/**\n * @internal test-only export — returns the next snapshot for a TRANSITION_START\n * payload, or `null` when the same-paths dedup guard should suppress the\n * update. Exported so the (structurally-unreachable after #605) guard can be\n * exercised by unit tests without resorting to private-API hacks.\n */\nexport function nextTransitionStartSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isTransitioning &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: false,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\n/**\n * @internal test-only export — analogous to {@link nextTransitionStartSnapshot}\n * for the LEAVE_APPROVE payload. The guard is structurally unreachable in\n * practice (router emits LEAVE_APPROVE exactly once per pipeline) but stays\n * for plugin-driven re-entrant flows.\n */\nexport function nextLeaveApproveSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isLeaveApproved &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: true,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n const source = new BaseSource(IDLE_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n // The same-paths dedup branch inside nextTransitionStartSnapshot is\n // structurally unreachable after #605 (every router-emitted\n // TRANSITION_START carries a fresh State per navigate()), but the\n // helper is kept testable for future stabilizer changes — see the\n // direct unit test in createTransitionSource.test.ts.\n const next = nextTransitionStartSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow; covered directly via nextTransitionStartSnapshot\n unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(\n events.TRANSITION_LEAVE_APPROVE,\n (toState: State, fromState?: State) => {\n const next = nextLeaveApproveSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow (LEAVE_APPROVE fires once per pipeline); covered\n directly via nextLeaveApproveSnapshot unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached transition source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single TransitionSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createTransitionSource(router)` directly — it returns a fresh instance with\n * a working `destroy()`.\n */\nexport function getTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n let cached = transitionSourceCache.get(router);\n\n if (!cached) {\n const source = createTransitionSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n transitionSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouterErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State, RouterError } from \"@real-router/core\";\n\nconst INITIAL_SNAPSHOT: RouterErrorSnapshot = {\n error: null,\n toRoute: null,\n fromRoute: null,\n version: 0,\n};\n\nconst errorSourceCache = new WeakMap<\n Router,\n RouterSource<RouterErrorSnapshot>\n>();\n\nexport function createErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let errorVersion = 0;\n let hasError = false;\n\n const source = new BaseSource(INITIAL_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_ERROR,\n (\n toState: State | undefined,\n fromState: State | undefined,\n err: RouterError,\n ) => {\n errorVersion++;\n hasError = true;\n source.updateSnapshot({\n error: err,\n toRoute: toState ?? null,\n /* v8 ignore next -- @preserve: fromState undefined only during start() error; unreachable via navigate() */\n fromRoute: fromState ?? null,\n version: errorVersion,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, () => {\n // Skip if no error — avoids unnecessary re-renders.\n // BaseSource.updateSnapshot() always notifies listeners (new object = new ref),\n // and useSyncExternalStore compares via Object.is().\n if (hasError) {\n hasError = false;\n source.updateSnapshot({\n error: null,\n toRoute: null,\n fromRoute: null,\n version: errorVersion,\n });\n }\n }),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached error source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single ErrorSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createErrorSource(router)` directly — it returns a fresh instance with a\n * working `destroy()`.\n */\nexport function getErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let cached = errorSourceCache.get(router);\n\n if (!cached) {\n const source = createErrorSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n errorSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { getErrorSource } from \"./createErrorSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { DismissableErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst dismissableCache = new WeakMap<\n Router,\n RouterSource<DismissableErrorSnapshot>\n>();\n\n/**\n * Returns a per-router cached source that wraps `getErrorSource(router)` with\n * an integrated \"dismissed\" version counter, exposing a single reactive\n * snapshot `{ error, toRoute, fromRoute, version, resetError }`.\n *\n * Each `RouterErrorBoundary` in a framework adapter subscribes to this one\n * source instead of re-implementing the `dismissedVersion` state pattern\n * locally. The 6-copy duplicate across adapters collapses to this helper.\n *\n * **Semantics:**\n * - `error` is non-null only when `underlying.version > dismissedVersion`.\n * - `resetError()` sets `dismissedVersion = current underlying version`,\n * immediately clearing `error` to `null` and notifying all listeners.\n * - A subsequent `TRANSITION_ERROR` advances `version` beyond `dismissedVersion`,\n * so `error` becomes non-null again — no additional plumbing needed.\n *\n * **Cached:** one instance per router. `destroy()` on the returned source is\n * a no-op. Shared across all `RouterErrorBoundary` consumers.\n */\nexport function createDismissableError(\n router: Router,\n): RouterSource<DismissableErrorSnapshot> {\n const cached = dismissableCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const errorSource = getErrorSource(router);\n\n let dismissedVersion = -1;\n // Hoisted up here so the `onFirstSubscribe` closure below can read/write\n // it before `disconnect()`'s declaration. JS hoisting makes the original\n // post-declaration order legal, but reading top-to-bottom is clearer.\n let unsubFromError: (() => void) | null = null;\n\n const buildDismissableSnapshot = (): DismissableErrorSnapshot => {\n const snap = errorSource.getSnapshot();\n const isDismissed = snap.version <= dismissedVersion;\n\n return {\n error: isDismissed ? null : snap.error,\n toRoute: isDismissed ? null : snap.toRoute,\n fromRoute: isDismissed ? null : snap.fromRoute,\n version: snap.version,\n resetError,\n };\n };\n\n const source = new BaseSource<DismissableErrorSnapshot>(\n buildDismissableSnapshot(),\n {\n onFirstSubscribe: () => {\n unsubFromError = errorSource.subscribe(() => {\n source.updateSnapshot(buildDismissableSnapshot());\n });\n },\n onLastUnsubscribe: () => {\n disconnect();\n },\n },\n );\n\n function resetError(): void {\n const currentVersion = errorSource.getSnapshot().version;\n\n // No-op guard: if we already dismissed at this version (or are even ahead\n // of the live error stream), there's nothing to clear. Skipping prevents\n // a redundant snapshot allocation + listener notification under tight\n // resetError(); resetError() patterns — common when a RouterErrorBoundary\n // user clicks \"dismiss\" while another dismiss is already in flight.\n if (currentVersion <= dismissedVersion) {\n return;\n }\n\n dismissedVersion = currentVersion;\n source.updateSnapshot(buildDismissableSnapshot());\n }\n\n function disconnect(): void {\n const unsub = unsubFromError;\n\n unsubFromError = null;\n unsub?.();\n }\n\n const wrapper: RouterSource<DismissableErrorSnapshot> = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n\n dismissableCache.set(router, wrapper);\n\n return wrapper;\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport interface ActiveNameSelector {\n /**\n * Subscribes to active-state changes of a specific route name.\n * Listener is called only when `isActive(routeName)` for this name transitions.\n * Returns an unsubscribe function.\n */\n subscribe: (routeName: string, listener: () => void) => () => void;\n /**\n * O(1) active check for the given route name (non-strict by default —\n * matches descendants). Uses the shared underlying router subscription.\n */\n isActive: (routeName: string) => boolean;\n /** No-op on the cached wrapper. */\n destroy: () => void;\n}\n\nconst selectorCache = new WeakMap<Router, ActiveNameSelector>();\n\n/**\n * Per-router cached selector providing O(1) active-route checks with one\n * shared router subscription for any number of distinct `routeName`\n * consumers.\n *\n * **When to use:** framework `Link` components that need an active boolean\n * without custom `params` / `activeStrict` / `ignoreQueryParams` — e.g. the\n * common navigation-link case. Multiple `<Link>` components with different\n * `routeName` share ONE `router.subscribe` handle instead of creating one\n * per Link (which is what `createActiveRouteSource(router, name)` does — it\n * caches per-name, so N names = N subscriptions).\n *\n * **When NOT to use:** Link needs `activeStrict: true`, custom `routeParams`,\n * or `ignoreQueryParams: false`. Fall back to `createActiveRouteSource` —\n * its cache handles the full argument surface.\n *\n * Based on the `routeSelector` pattern pioneered by `@real-router/solid`'s\n * `RouterProvider` (`createSelector` + `areRoutesRelated`). This helper\n * ports it to framework-agnostic API so Vue / React / Preact / Svelte /\n * Angular Link components can adopt the same fast path.\n *\n * @see Solid reference implementation — `packages/solid/src/RouterProvider.tsx`\n */\nexport function createActiveNameSelector(router: Router): ActiveNameSelector {\n const cached = selectorCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n // listeners per-name — re-evaluated on every router transition\n const listenersByName = new Map<string, Set<() => void>>();\n // cached active state per-name — used to diff before notifying\n const activeByName = new Map<string, boolean>();\n\n let routerUnsubscribe: (() => void) | null = null;\n\n const isActiveNonStrict = (routeName: string): boolean => {\n const current = router.getState();\n\n if (!current) {\n return false;\n }\n\n // Empty string represents the root of the name hierarchy — every named\n // route is its descendant. Without this short-circuit, `current.name`\n // would have to equal `\"\"` or start with `\".\"` (both impossible for\n // valid route names), breaking symmetry with `createRouteNodeSource(\"\")`\n // which is always-active when a route is current.\n if (routeName === \"\") {\n return true;\n }\n\n return (\n current.name === routeName || current.name.startsWith(`${routeName}.`)\n );\n };\n\n const connect = (): void => {\n routerUnsubscribe = router.subscribe((next) => {\n for (const [routeName, listeners] of listenersByName) {\n // Cheap pre-filter: if neither new nor previous route is related\n // to this name, its active state cannot have changed. Empty\n // routeName is the implicit root — every route is its descendant,\n // so the filter would falsely exclude it (`areRoutesRelated`\n // doesn't treat `\"\"` specially). Skip the filter for the root.\n if (routeName !== \"\") {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n continue;\n }\n }\n\n // activeByName always has an entry for names present in listenersByName —\n // subscribe() seeds it, and we only iterate over listenersByName.\n const prevActive = activeByName.get(routeName) === true;\n const nextActive = isActiveNonStrict(routeName);\n\n if (prevActive === nextActive) {\n continue;\n }\n\n activeByName.set(routeName, nextActive);\n for (const listener of listeners) {\n listener();\n }\n }\n });\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const subscribe = (routeName: string, listener: () => void): (() => void) => {\n let listeners = listenersByName.get(routeName);\n\n if (!listeners) {\n listeners = new Set();\n listenersByName.set(routeName, listeners);\n activeByName.set(routeName, isActiveNonStrict(routeName));\n }\n\n listeners.add(listener);\n\n if (!routerUnsubscribe) {\n connect();\n }\n\n let unsubscribed = false;\n\n return () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n listenersByName.delete(routeName);\n activeByName.delete(routeName);\n }\n\n if (listenersByName.size === 0) {\n disconnect();\n }\n };\n };\n\n const isActive = (routeName: string): boolean => {\n const cachedActive = activeByName.get(routeName);\n\n if (cachedActive !== undefined) {\n return cachedActive;\n }\n\n // Not subscribed — compute on demand.\n return isActiveNonStrict(routeName);\n };\n\n const selector: ActiveNameSelector = {\n subscribe,\n isActive,\n destroy: noopDestroy,\n };\n\n selectorCache.set(router, selector);\n\n return selector;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#listeners","#onFirstSubscribe","#onLastUnsubscribe","#onDestroy","#currentSnapshot","#destroyed"],"sources":["../../src/BaseSource.ts","../../src/internal/readContextHash.ts","../../src/stabilizeState.ts","../../src/createRouteSource.ts","../../src/computeSnapshot.ts","../../src/internal/noopDestroy.ts","../../src/createRouteNodeSource.ts","../../src/canonicalJson.ts","../../src/normalizeActiveOptions.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts","../../src/createErrorSource.ts","../../src/createDismissableError.ts","../../src/createActiveNameSelector.ts"],"sourcesContent":["export interface BaseSourceOptions {\n onFirstSubscribe?: () => void;\n onLastUnsubscribe?: () => void;\n onDestroy?: () => void;\n}\n\nexport class BaseSource<T> {\n #currentSnapshot: T;\n #destroyed = false;\n\n readonly #listeners = new Set<() => void>();\n readonly #onFirstSubscribe: (() => void) | undefined;\n readonly #onLastUnsubscribe: (() => void) | undefined;\n readonly #onDestroy: (() => void) | undefined;\n\n constructor(initialSnapshot: T, options?: BaseSourceOptions) {\n this.#currentSnapshot = initialSnapshot;\n this.#onFirstSubscribe = options?.onFirstSubscribe;\n this.#onLastUnsubscribe = options?.onLastUnsubscribe;\n this.#onDestroy = options?.onDestroy;\n\n this.subscribe = this.subscribe.bind(this);\n this.getSnapshot = this.getSnapshot.bind(this);\n this.destroy = this.destroy.bind(this);\n }\n\n subscribe(listener: () => void): () => void {\n if (this.#destroyed) {\n return () => {};\n }\n\n const wasFirst = this.#listeners.size === 0;\n\n // Add listener BEFORE onFirstSubscribe so that if the reconciliation in\n // onFirstSubscribe calls updateSnapshot(), this listener receives the\n // notification. Critical for useSyncExternalStore in adapters — without\n // this the post-reconnection snapshot is missed and consumers render\n // stale data. (See Preact RouteView nested remount test.)\n this.#listeners.add(listener);\n\n if (wasFirst && this.#onFirstSubscribe) {\n this.#onFirstSubscribe();\n }\n\n return () => {\n this.#listeners.delete(listener);\n\n if (\n !this.#destroyed &&\n this.#listeners.size === 0 &&\n this.#onLastUnsubscribe\n ) {\n this.#onLastUnsubscribe();\n }\n };\n }\n\n getSnapshot(): T {\n return this.#currentSnapshot;\n }\n\n updateSnapshot(snapshot: T): void {\n /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */\n if (this.#destroyed) {\n return;\n }\n\n this.#currentSnapshot = snapshot;\n // Isolate listener exceptions so a single throwing subscriber (e.g. React\n // error-boundary fallback throwing inside `onStoreChange`) does not block\n // the remaining subscribers — the invariant \"after updateSnapshot all\n // listeners see the new snapshot\" must hold. Re-throw asynchronously via\n // queueMicrotask so global error handlers / test harnesses still surface\n // the bug without breaking the synchronous notification fan-out.\n for (const listener of this.#listeners) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#onDestroy?.();\n this.#listeners.clear();\n }\n}\n","import type { State } from \"@real-router/core\";\n\n/**\n * @internal\n *\n * Reads the URL fragment published by `browser-plugin` / `navigation-plugin`\n * on a router state. The plugins claim the `\"url\"` namespace via\n * `state.context.url` and the `hash` field carries the **decoded** fragment.\n *\n * Returns:\n * - `undefined` — no plugin claimed the `\"url\"` namespace (hash-plugin runtime,\n * memory-plugin, SSR before hydration) OR the state itself is nullish;\n * - `\"\"` — the URL namespace exists but the fragment is empty\n * (browser-plugin on a hash-less URL).\n *\n * Callers that need the \"no namespace at all\" branch (e.g. `stabilizeState`\n * comparing cross-plugin transitions) read the raw `undefined`. Callers that\n * collapse \"no namespace\" to \"no hash\" (e.g. `createActiveRouteSource`'s\n * hash-equality check) coalesce with `?? \"\"` themselves.\n *\n * Centralising the context cast removes the previous duplicate definitions in\n * `stabilizeState.ts` and `createActiveRouteSource.ts` that drifted in\n * signature (state vs router) and default-value (undefined vs \"\") — both\n * variants are reconstructible from this single helper at the callsite.\n */\nexport function readContextHash(\n state: State | null | undefined,\n): string | undefined {\n const ctx = state?.context as { url?: { hash?: string } } | undefined;\n\n return ctx?.url?.hash;\n}\n","import { readContextHash } from \"./internal/readContextHash.js\";\n\nimport type { State } from \"@real-router/core\";\n\n/**\n * State-aware stabilization for route snapshots.\n *\n * Compares `path` (canonical name+params), `state.context.url.hash`\n * (URL fragment, #532), and `state.transition.reload` (#605). When all\n * three match (idempotent navigation), returns `prev` (preserving\n * reference) so frameworks can skip re-renders. When any of them flips,\n * returns `next` so consumers subscribing through `useRoute()` see the\n * new state.\n *\n * `transition.reload === true` is the user's explicit signal for a\n * non-idempotent navigation — `router.navigate(name, params, { reload:\n * true })` is the canonical pairing for `invalidate(router, namespace)`\n * and any cache-bust pattern. Bypassing stabilization for reloads makes\n * `useRoute()` consumers see fresh `state.context.<namespace>` values\n * written by the SSR loader plugin's `subscribeLeave` handler.\n *\n * Ignores `meta` (internal: auto-increment id), other `transition` fields\n * (`from`, `segments`, `redirected`), and `state.context.navigation` /\n * `state.context.browser` (transient transition metadata) — they don't\n * affect render identity for idempotent navigations.\n *\n * Accepts `null` for compatibility with `RouterTransitionSnapshot`\n * (toRoute/fromRoute are `State | null`).\n *\n * @internal Not exported from package public API.\n */\nexport function stabilizeState<T extends State | null | undefined>(\n prev: T,\n next: T,\n): T {\n if (prev === next) {\n return prev;\n }\n if (prev?.path !== next?.path) {\n return next;\n }\n\n // After the path check, both must be the same non-null State (paths\n // matched, prev !== next reference). Read context.url.hash to detect\n // same-path-different-hash navigation (#532) — render-relevant for\n // tab-style UIs that subscribe via useRoute(). Optional chaining keeps\n // the access null-safe without forbidden non-null assertions.\n if (readContextHash(prev) !== readContextHash(next)) {\n return next;\n }\n\n // Explicit reload navigation (#605) — caller asked to bypass dedupe so\n // observers see fresh `state.context` written by `invalidate()`-driven\n // loader re-runs. The path equality above guarantees both prev and next\n // are either non-null with matching paths or both nullish; only the\n // non-null branch can carry a meaningful `transition.reload`.\n if (readReloadFlag(next)) {\n return next;\n }\n\n return prev;\n}\n\nfunction readReloadFlag(state: State | null | undefined): boolean {\n // Defensive read: `transition` is mandatory in the public State type, but a\n // plugin returning a malformed state (or a future fork) shouldn't crash the\n // stabilizer with a TypeError. We cast to a structurally-loose shape so the\n // optional chain is permitted; the runtime guard preserves dedup (false =\n // not-a-reload) for malformed inputs.\n const transition = (\n state as { transition?: { reload?: boolean } } | null | undefined\n )?.transition;\n\n return transition?.reload === true;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\n/**\n * Creates a source for the full route state.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n */\nexport function createRouteSource(router: Router): RouterSource<RouteSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteSnapshot>(\n {\n route: router.getState(),\n previousRoute: undefined,\n },\n {\n onFirstSubscribe: () => {\n routerUnsubscribe = router.subscribe((next) => {\n const prev = source.getSnapshot();\n const newRoute = stabilizeState(prev.route, next.route);\n const newPreviousRoute = stabilizeState(\n prev.previousRoute,\n next.previousRoute,\n );\n\n if (\n newRoute !== prev.route ||\n newPreviousRoute !== prev.previousRoute\n ) {\n source.updateSnapshot({\n route: newRoute,\n previousRoute: newPreviousRoute,\n });\n }\n });\n },\n onLastUnsubscribe: disconnect,\n onDestroy: disconnect,\n },\n );\n\n return source;\n}\n","import { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouteNodeSnapshot } from \"./types.js\";\nimport type { Router, SubscribeState } from \"@real-router/core\";\n\nexport function computeSnapshot(\n currentSnapshot: RouteNodeSnapshot,\n router: Router,\n nodeName: string,\n next?: SubscribeState,\n): RouteNodeSnapshot {\n const currentRoute = next?.route ?? router.getState();\n const previousRoute = next?.previousRoute;\n\n const isNodeActive =\n nodeName === \"\" ||\n (currentRoute !== undefined &&\n (currentRoute.name === nodeName ||\n currentRoute.name.startsWith(`${nodeName}.`)));\n\n const route = isNodeActive ? currentRoute : undefined;\n\n if (\n route === currentSnapshot.route &&\n previousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n const newRoute = stabilizeState(currentSnapshot.route, route);\n const newPreviousRoute = stabilizeState(\n currentSnapshot.previousRoute,\n previousRoute,\n );\n\n if (\n newRoute === currentSnapshot.route &&\n newPreviousRoute === currentSnapshot.previousRoute\n ) {\n return currentSnapshot;\n }\n\n return { route: newRoute, previousRoute: newPreviousRoute };\n}\n","/**\n * @internal\n *\n * Shared no-op `destroy()` for cached `RouterSource` wrappers.\n *\n * Cached factories (`getTransitionSource`, `getErrorSource`, `createDismissableError`,\n * `createActiveNameSelector`, cache hit in `createRouteNodeSource`,\n * `createActiveRouteSource`) return a wrapper whose `destroy()` is a no-op —\n * the underlying source is shared across all consumers and lives as long as\n * the router (WeakMap entry releases on router GC).\n *\n * One module-level function shared by every cached factory keeps the wrapper\n * shape (`{ subscribe, getSnapshot, destroy: noopDestroy }`) byte-stable and\n * eliminates the previous six copies. (Bundlers inline a stand-alone arrow\n * just as readily, so the cost is purely on the maintenance side.)\n */\nexport function noopDestroy(): void {\n // Shared cached source — external destroy() is a no-op.\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { computeSnapshot } from \"./computeSnapshot.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouteNodeSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst nodeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<RouteNodeSnapshot>>\n>();\n\n/**\n * Creates a source scoped to a specific route node.\n *\n * **Per-router + per-nodeName cache:** repeated calls with the same\n * `(router, nodeName)` return the same shared instance. `N` consumers\n * calling `createRouteNodeSource(r, \"users\")` produce one router subscription\n * shared across all of them.\n *\n * Uses a lazy-connection pattern: the router subscription is created when the\n * first listener subscribes and removed when the last listener unsubscribes.\n * This is compatible with React's useSyncExternalStore and Strict Mode.\n *\n * `destroy()` on the returned source is a no-op — the shared instance lives\n * as long as the router itself (the WeakMap entry releases automatically on\n * router GC). Callers that need an isolated instance with working teardown\n * can use `buildRouteNodeSource` internally (not exported).\n */\nexport function createRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let perRouter = nodeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n nodeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(nodeName);\n\n if (!cached) {\n const source = buildRouteNodeSource(router, nodeName);\n\n // Wrap with no-op destroy. The shared source lives with the router.\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(nodeName, cached);\n }\n\n return cached;\n}\n\nfunction buildRouteNodeSource(\n router: Router,\n nodeName: string,\n): RouterSource<RouteNodeSnapshot> {\n let routerUnsubscribe: (() => void) | null = null;\n\n // Built once per cached source instance; safe — createRouteNodeSource is\n // itself per-(router, nodeName) cached, so shouldUpdate is called once.\n const shouldUpdate = router.shouldUpdateNode(nodeName);\n\n const initialSnapshot: RouteNodeSnapshot = {\n route: undefined,\n previousRoute: undefined,\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const source = new BaseSource<RouteNodeSnapshot>(\n computeSnapshot(initialSnapshot, router, nodeName),\n {\n onFirstSubscribe: () => {\n // Reconcile snapshot with current router state before connecting.\n // Covers reconnection after Activity hide/show cycles where the\n // source was disconnected and missed navigation events.\n const reconciled = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n );\n\n if (!Object.is(reconciled, source.getSnapshot())) {\n source.updateSnapshot(reconciled);\n }\n\n // Connect to router on first subscription\n routerUnsubscribe = router.subscribe((next) => {\n if (!shouldUpdate(next.route, next.previousRoute)) {\n return;\n }\n\n const newSnapshot = computeSnapshot(\n source.getSnapshot(),\n router,\n nodeName,\n next,\n );\n\n // computeSnapshot returns the SAME currentSnapshot reference when\n // both route and previousRoute stabilize to prev — guard against\n // emitting redundant updates to listeners (matters for signal-\n // based adapters that re-run effects on every set).\n /* v8 ignore next 3 -- @preserve: structurally unreachable after #605\n — reload navs always return fresh refs via stabilizeState, and\n within-node non-reload navs short-circuit at shouldUpdate. Guard\n kept for defensive correctness against future stabilizer changes. */\n if (Object.is(source.getSnapshot(), newSnapshot)) {\n return;\n }\n\n source.updateSnapshot(newSnapshot);\n });\n },\n onLastUnsubscribe: disconnect,\n },\n );\n\n return source;\n}\n","/**\n * Serializes a value into a stable JSON string — object keys are sorted at\n * every level so that `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` produce the same\n * output.\n *\n * Used as a cache key for `createActiveRouteSource` so that equivalent params\n * objects share the same cached source regardless of key order.\n *\n * **Divergence from `shared/dom-utils/scroll-restore.canonicalJson` — by\n * design.** That sibling implementation is the cheap navigation-hot-path\n * variant (uses `localeCompare`, plain-object accumulator, native cycle\n * detector) and pairs with `safeKeyOf` for crash-tolerance. This one is the\n * strict cache-key variant: byte-order compare, prototype-less accumulator,\n * bespoke cycle detection — eagerly throws on inputs that would cause\n * collisions or pollution, so callers can fall back to non-cached sources.\n * They are NOT interchangeable; cross-package equivalence is explicitly not\n * a goal (audit-2 / audit-2026-05-17 §2).\n *\n * Edge cases:\n * - Arrays preserve order (canonical: index-ordered already).\n * - `undefined` values are dropped (standard JSON behaviour).\n * - `Date` is serialized via its `toJSON` method (ISO string).\n * - `Symbol` becomes `undefined` (standard JSON behaviour).\n * - `BigInt` throws via `JSON.stringify` defaults.\n * - `Map`, `Set`, `RegExp`, `WeakMap`, `WeakSet` would silently collapse to\n * `\"{}\"` (no enumerable own keys), which would cause **different inputs to\n * share the same cache key**. We detect these explicitly and throw — callers\n * then take the non-cached fallback path (same behaviour as `BigInt`).\n * - Circular references throw `TypeError` (parity with native `JSON.stringify`).\n * The replacer copies each object level into a fresh prototype-less record,\n * so we must run our own cycle detection — the native detector never sees\n * the original object graph.\n * - `__proto__` keys are preserved as own properties (no prototype pollution\n * and no silent collision between `{ __proto__: x, b: 1 }` and `{ b: 1 }`).\n *\n * In practice, route params carry primitives (`string | number | boolean`);\n * the cache-key-collision edge cases above are defensive bugs caught loudly.\n */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(canonicalize(value, new Set<object>()));\n}\n\n// Key comparison uses byte-order (`<` / `>`) instead of `localeCompare()` so\n// the output is independent of the Node.js ICU build / system locale. Same\n// canonical form on every machine — required for cache-key stability.\nfunction compareKeys(left: string, right: string): number {\n return left < right ? -1 : 1;\n}\n\n/**\n * Returns a structural clone with object keys sorted at every level. Path-based\n * cycle detection (`path` set) matches the semantics of native `JSON.stringify`\n * — a `TypeError` is thrown on a true cycle, but the same object reachable via\n * two independent branches (DAG) serialises fine.\n *\n * `Date` instances pass through unchanged so `JSON.stringify` can invoke their\n * `toJSON` hook. Other built-ins (`Map`, `Set`, `WeakMap`, `WeakSet`, `RegExp`)\n * throw eagerly to avoid silent cache-key collisions.\n */\nfunction canonicalize(value: unknown, path: Set<object>): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value;\n }\n\n if (\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof RegExp\n ) {\n throw new TypeError(\n `canonicalJson: cannot serialize ${(value as { constructor: { name: string } }).constructor.name} — non-enumerable own keys collapse to \"{}\" and would cause cache-key collisions. Pass primitive params (string | number | boolean) instead.`,\n );\n }\n\n if (path.has(value)) {\n throw new TypeError(\n \"canonicalJson: cannot serialize circular structure (cycle detected during traversal).\",\n );\n }\n\n path.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, path));\n }\n\n // Use a null-prototype record so `__proto__` is treated as a regular\n // own property — assigning to a plain `{}` would set the prototype\n // chain instead and silently collide with inputs that omit the key.\n const sorted: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n const keys = Object.keys(value).toSorted(compareKeys);\n\n for (const key of keys) {\n sorted[key] = canonicalize((value as Record<string, unknown>)[key], path);\n }\n\n return sorted;\n } finally {\n path.delete(value);\n }\n}\n","import type { ActiveRouteSourceOptions } from \"./types.js\";\n\n/**\n * Normalized options shape — booleans are required (filled with defaults),\n * `hash` stays `string | undefined` because `undefined` is the meaningful\n * \"ignore hash\" sentinel that callers pass intentionally.\n */\nexport interface NormalizedActiveOptions {\n strict: boolean;\n ignoreQueryParams: boolean;\n hash: string | undefined;\n}\n\n/**\n * Default options for `createActiveRouteSource` and adapter-level helpers.\n *\n * Frozen to prevent accidental mutation by consumers.\n */\nexport const DEFAULT_ACTIVE_OPTIONS: Readonly<NormalizedActiveOptions> =\n Object.freeze({\n strict: false,\n ignoreQueryParams: true,\n hash: undefined,\n });\n\n/**\n * Normalizes partial `ActiveRouteSourceOptions` into a fully-defaulted object.\n *\n * Use this to produce a stable options record for comparison, caching, or\n * downstream consumers that require all fields present.\n */\nexport function normalizeActiveOptions(\n options?: ActiveRouteSourceOptions,\n): NormalizedActiveOptions {\n return {\n strict: options?.strict ?? DEFAULT_ACTIVE_OPTIONS.strict,\n ignoreQueryParams:\n options?.ignoreQueryParams ?? DEFAULT_ACTIVE_OPTIONS.ignoreQueryParams,\n hash: options?.hash,\n };\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { canonicalJson } from \"./canonicalJson.js\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { readContextHash } from \"./internal/readContextHash.js\";\nimport { normalizeActiveOptions } from \"./normalizeActiveOptions.js\";\n\nimport type { ActiveRouteSourceOptions, RouterSource } from \"./types.js\";\nimport type { Params, Router } from \"@real-router/core\";\n\nconst activeSourceCache = new WeakMap<\n Router,\n Map<string, RouterSource<boolean>>\n>();\n\n/**\n * Creates a source tracking whether a route (with given params/options) is active.\n *\n * **Per-router + canonical-args cache:** repeated calls with equivalent\n * arguments return the same shared instance. Param key order doesn't matter\n * (`{ a:1, b:2 }` and `{ b:2, a:1 }` hit the same cache entry via\n * `canonicalJson`).\n *\n * For cached entries `destroy()` is a no-op — shared sources live with the\n * router and release automatically on router GC (WeakMap entry).\n *\n * `BigInt`/circular params can't be serialized → the source bypasses the cache\n * and `destroy()` becomes a real teardown that detaches the underlying\n * `router.subscribe` handle.\n */\nexport function createActiveRouteSource(\n router: Router,\n routeName: string,\n params?: Params,\n options?: ActiveRouteSourceOptions,\n): RouterSource<boolean> {\n const { strict, ignoreQueryParams, hash } = normalizeActiveOptions(options);\n\n // BigInt/Symbol/circular refs cannot be serialized — fall back to creating\n // a fresh (non-cached) source. Callers pass these edge-case params rarely;\n // the extra allocation is acceptable.\n let key: string | undefined;\n\n try {\n // `hash === undefined` produces \"\" via String(undefined) → \"undefined\";\n // we encode it as the empty string sentinel to keep the key short and\n // distinct from the literal \"undefined\" hash value (which is a valid,\n // if unusual, fragment).\n const hashKey = hash === undefined ? \"\" : `#${hash}`;\n\n // `params === undefined` is the common Link case (`<Link to=\"users\">`\n // with no params). Skip canonicalJson(undefined) — it returns the literal\n // string \"undefined\" and template interpolation would just embed it. An\n // explicit empty sentinel avoids the call and shaves the cache-key by 9\n // characters per Link.\n const paramsKey = params === undefined ? \"\" : canonicalJson(params);\n\n // Delimiter `|` is safe because route names use `.` as the segment\n // separator (`users.list`, not `users|list`) and canonicalJson-encoded\n // params escape `\"` (so any literal `|` inside params lives inside a\n // quoted JSON string and can't be confused with our delimiter). If route\n // names ever grow a `|` character, this composite key would become\n // ambiguous — change the separator to a control char or hash-encode each\n // field.\n key = `${routeName}|${paramsKey}|${String(strict)}|${String(ignoreQueryParams)}|${hashKey}`;\n } catch {\n key = undefined;\n }\n\n if (key === undefined) {\n // Non-cached fallback (canonicalJson threw on BigInt / circular / etc.).\n // Return the real source — `destroy()` must unwind the router subscription;\n // otherwise the wrapper leaks for the lifetime of the router.\n return buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n }\n\n let perRouter = activeSourceCache.get(router);\n\n if (!perRouter) {\n perRouter = new Map();\n activeSourceCache.set(router, perRouter);\n }\n\n let cached = perRouter.get(key);\n\n if (!cached) {\n const source = buildActiveRouteSource(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n perRouter.set(key, cached);\n }\n\n return cached;\n}\n\n/**\n * Combines route-name match with optional hash match (#532).\n *\n * - Route-name match: `router.isActiveRoute(name, params, strict, ignoreQueryParams)`.\n * - Hash match (only when `hash !== undefined`): `state.context.url.hash` must\n * equal the requested fragment exactly. With hash-plugin (no `url`\n * namespace), this returns `false` — the documented limitation.\n */\nfunction computeActive(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): boolean {\n const routeActive = router.isActiveRoute(\n routeName,\n params,\n strict,\n ignoreQueryParams,\n );\n\n if (!routeActive) {\n return false;\n }\n if (hash === undefined) {\n return true;\n }\n\n // `readContextHash` returns `undefined` when no URL plugin claimed the\n // namespace (hash-plugin runtime, memory-plugin, SSR). For hash-equality\n // matching we collapse that to `\"\"` — a hash-aware Link with no URL plugin\n // can only match when the consumer also asked for `hash: \"\"`.\n return (readContextHash(router.getState()) ?? \"\") === hash;\n}\n\nfunction buildActiveRouteSource(\n router: Router,\n routeName: string,\n params: Params | undefined,\n strict: boolean,\n ignoreQueryParams: boolean,\n hash: string | undefined,\n): RouterSource<boolean> {\n const initialValue = computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n );\n\n let routerUnsubscribe: (() => void) | undefined;\n\n const source = new BaseSource(initialValue, {\n onDestroy: () => {\n routerUnsubscribe?.();\n routerUnsubscribe = undefined;\n },\n });\n\n // Eager connection: subscribe to router immediately. For the cached path,\n // the returned wrapper has a no-op destroy and the handle lives with the\n // router (released on router GC). For the non-cached fallback (BigInt /\n // circular params), the handle is unwound through `onDestroy` above.\n routerUnsubscribe = router.subscribe((next) => {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n // Hash-aware sources also flip on same-path-different-hash transitions.\n // The route comparison alone misses these (route is identical), but the\n // hash claim updated, so we must re-evaluate. Detect via the `hashChanged`\n // flag published by URL plugins.\n const hashFlip =\n hash !== undefined &&\n ((next.route.context as { url?: { hashChanged?: boolean } } | undefined)\n ?.url?.hashChanged ??\n false);\n\n if (!isNewRelated && !isPrevRelated && !hashFlip) {\n return;\n }\n\n // If new route is not related, we know the route is inactive —\n // avoid calling isActiveRoute for the optimization. (Hash check would\n // also fail without route-match, so this short-circuit holds for\n // hash-aware sources too.)\n const newValue = isNewRelated\n ? computeActive(\n router,\n routeName,\n params,\n strict,\n ignoreQueryParams,\n hash,\n )\n : false;\n\n if (!Object.is(source.getSnapshot(), newValue)) {\n source.updateSnapshot(newValue);\n }\n });\n\n return source;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\nimport { stabilizeState } from \"./stabilizeState.js\";\n\nimport type { RouterTransitionSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State } from \"@real-router/core\";\n\n// Frozen so accidental consumer mutation (`source.getSnapshot().toRoute = X`)\n// throws in strict mode. The singleton ref is shared across every IDLE state\n// for the lifetime of the process — mutating it would corrupt the contract\n// \"all IDLE snapshots are the same object reference\" relied on by every\n// adapter's useSyncExternalStore equivalent.\nconst IDLE_SNAPSHOT: RouterTransitionSnapshot = Object.freeze({\n isTransitioning: false,\n isLeaveApproved: false,\n toRoute: null,\n fromRoute: null,\n});\n\nconst transitionSourceCache = new WeakMap<\n Router,\n RouterSource<RouterTransitionSnapshot>\n>();\n\n/**\n * @internal test-only export — returns the next snapshot for a TRANSITION_START\n * payload, or `null` when the same-paths dedup guard should suppress the\n * update. Exported so the (structurally-unreachable after #605) guard can be\n * exercised by unit tests without resorting to private-API hacks.\n */\nexport function nextTransitionStartSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isTransitioning &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: false,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\n/**\n * @internal test-only export — analogous to {@link nextTransitionStartSnapshot}\n * for the LEAVE_APPROVE payload. The guard is structurally unreachable in\n * practice (router emits LEAVE_APPROVE exactly once per pipeline) but stays\n * for plugin-driven re-entrant flows.\n */\nexport function nextLeaveApproveSnapshot(\n prev: RouterTransitionSnapshot,\n toState: State,\n fromState: State | undefined,\n): RouterTransitionSnapshot | null {\n const newToRoute = stabilizeState(prev.toRoute, toState);\n const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);\n\n if (\n prev.isLeaveApproved &&\n newToRoute === prev.toRoute &&\n newFromRoute === prev.fromRoute\n ) {\n return null;\n }\n\n return {\n isTransitioning: true,\n isLeaveApproved: true,\n toRoute: newToRoute,\n fromRoute: newFromRoute,\n };\n}\n\nexport function createTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n const source = new BaseSource(IDLE_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n const resetToIdle = (): void => {\n source.updateSnapshot(IDLE_SNAPSHOT);\n };\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_START,\n (toState: State, fromState?: State) => {\n // The same-paths dedup branch inside nextTransitionStartSnapshot is\n // structurally unreachable after #605 (every router-emitted\n // TRANSITION_START carries a fresh State per navigate()), but the\n // helper is kept testable for future stabilizer changes — see the\n // direct unit test in createTransitionSource.test.ts.\n const next = nextTransitionStartSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow; covered directly via nextTransitionStartSnapshot\n unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(\n events.TRANSITION_LEAVE_APPROVE,\n (toState: State, fromState?: State) => {\n const next = nextLeaveApproveSnapshot(\n source.getSnapshot(),\n toState,\n fromState,\n );\n\n /* v8 ignore next 3 -- @preserve: dedup-skip branch unreachable through\n normal router flow (LEAVE_APPROVE fires once per pipeline); covered\n directly via nextLeaveApproveSnapshot unit test. */\n if (next === null) {\n return;\n }\n\n source.updateSnapshot(next);\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),\n api.addEventListener(events.TRANSITION_ERROR, resetToIdle),\n api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached transition source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single TransitionSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createTransitionSource(router)` directly — it returns a fresh instance with\n * a working `destroy()`.\n */\nexport function getTransitionSource(\n router: Router,\n): RouterSource<RouterTransitionSnapshot> {\n let cached = transitionSourceCache.get(router);\n\n if (!cached) {\n const source = createTransitionSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n transitionSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { events } from \"@real-router/core\";\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { BaseSource } from \"./BaseSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { RouterErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router, State, RouterError } from \"@real-router/core\";\n\nconst INITIAL_SNAPSHOT: RouterErrorSnapshot = {\n error: null,\n toRoute: null,\n fromRoute: null,\n version: 0,\n};\n\nconst errorSourceCache = new WeakMap<\n Router,\n RouterSource<RouterErrorSnapshot>\n>();\n\nexport function createErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let errorVersion = 0;\n let hasError = false;\n\n const source = new BaseSource(INITIAL_SNAPSHOT, {\n onDestroy: () => {\n for (const unsub of unsubs) {\n unsub();\n }\n },\n });\n\n const api = getPluginApi(router);\n\n // Eager connection: subscribe to router events immediately\n const unsubs = [\n api.addEventListener(\n events.TRANSITION_ERROR,\n (\n toState: State | undefined,\n fromState: State | undefined,\n err: RouterError,\n ) => {\n errorVersion++;\n hasError = true;\n source.updateSnapshot({\n error: err,\n toRoute: toState ?? null,\n /* v8 ignore next -- @preserve: fromState undefined only during start() error; unreachable via navigate() */\n fromRoute: fromState ?? null,\n version: errorVersion,\n });\n },\n ),\n api.addEventListener(events.TRANSITION_SUCCESS, () => {\n // Skip if no error — avoids unnecessary re-renders.\n // BaseSource.updateSnapshot() always notifies listeners (new object = new ref),\n // and useSyncExternalStore compares via Object.is().\n if (hasError) {\n hasError = false;\n source.updateSnapshot({\n error: null,\n toRoute: null,\n fromRoute: null,\n version: errorVersion,\n });\n }\n }),\n ];\n\n return source;\n}\n\n/**\n * Returns a per-router cached error source shared across all consumers.\n *\n * Safe to call destroy() — the cached source ignores external destroy() calls\n * and lives until the router itself is garbage-collected (the WeakMap entry\n * releases automatically).\n *\n * Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to\n * share a single ErrorSource instance across all mount/unmount cycles.\n *\n * For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call\n * `createErrorSource(router)` directly — it returns a fresh instance with a\n * working `destroy()`.\n */\nexport function getErrorSource(\n router: Router,\n): RouterSource<RouterErrorSnapshot> {\n let cached = errorSourceCache.get(router);\n\n if (!cached) {\n const source = createErrorSource(router);\n\n // Wrap with no-op destroy. The underlying source is shared across all\n // consumers; letting any one consumer call destroy() would tear it down\n // for the rest. The source lives as long as the router (WeakMap key).\n cached = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n errorSourceCache.set(router, cached);\n }\n\n return cached;\n}\n","import { BaseSource } from \"./BaseSource\";\nimport { getErrorSource } from \"./createErrorSource\";\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { DismissableErrorSnapshot, RouterSource } from \"./types.js\";\nimport type { Router } from \"@real-router/core\";\n\nconst dismissableCache = new WeakMap<\n Router,\n RouterSource<DismissableErrorSnapshot>\n>();\n\n/**\n * Returns a per-router cached source that wraps `getErrorSource(router)` with\n * an integrated \"dismissed\" version counter, exposing a single reactive\n * snapshot `{ error, toRoute, fromRoute, version, resetError }`.\n *\n * Each `RouterErrorBoundary` in a framework adapter subscribes to this one\n * source instead of re-implementing the `dismissedVersion` state pattern\n * locally. The 6-copy duplicate across adapters collapses to this helper.\n *\n * **Semantics:**\n * - `error` is non-null only when `underlying.version > dismissedVersion`.\n * - `resetError()` sets `dismissedVersion = current underlying version`,\n * immediately clearing `error` to `null` and notifying all listeners.\n * - A subsequent `TRANSITION_ERROR` advances `version` beyond `dismissedVersion`,\n * so `error` becomes non-null again — no additional plumbing needed.\n *\n * **Cached:** one instance per router. `destroy()` on the returned source is\n * a no-op. Shared across all `RouterErrorBoundary` consumers.\n */\nexport function createDismissableError(\n router: Router,\n): RouterSource<DismissableErrorSnapshot> {\n const cached = dismissableCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const errorSource = getErrorSource(router);\n\n let dismissedVersion = -1;\n // Hoisted up here so the `onFirstSubscribe` closure below can read/write\n // it before `disconnect()`'s declaration. JS hoisting makes the original\n // post-declaration order legal, but reading top-to-bottom is clearer.\n let unsubFromError: (() => void) | null = null;\n\n const buildDismissableSnapshot = (): DismissableErrorSnapshot => {\n const snap = errorSource.getSnapshot();\n const isDismissed = snap.version <= dismissedVersion;\n\n return {\n error: isDismissed ? null : snap.error,\n toRoute: isDismissed ? null : snap.toRoute,\n fromRoute: isDismissed ? null : snap.fromRoute,\n version: snap.version,\n resetError,\n };\n };\n\n const source = new BaseSource<DismissableErrorSnapshot>(\n buildDismissableSnapshot(),\n {\n onFirstSubscribe: () => {\n unsubFromError = errorSource.subscribe(() => {\n source.updateSnapshot(buildDismissableSnapshot());\n });\n },\n onLastUnsubscribe: () => {\n disconnect();\n },\n },\n );\n\n function resetError(): void {\n const currentVersion = errorSource.getSnapshot().version;\n\n // No-op guard: if we already dismissed at this version (or are even ahead\n // of the live error stream), there's nothing to clear. Skipping prevents\n // a redundant snapshot allocation + listener notification under tight\n // resetError(); resetError() patterns — common when a RouterErrorBoundary\n // user clicks \"dismiss\" while another dismiss is already in flight.\n if (currentVersion <= dismissedVersion) {\n return;\n }\n\n dismissedVersion = currentVersion;\n source.updateSnapshot(buildDismissableSnapshot());\n }\n\n function disconnect(): void {\n const unsub = unsubFromError;\n\n unsubFromError = null;\n unsub?.();\n }\n\n const wrapper: RouterSource<DismissableErrorSnapshot> = {\n subscribe: source.subscribe,\n getSnapshot: source.getSnapshot,\n destroy: noopDestroy,\n };\n\n dismissableCache.set(router, wrapper);\n\n return wrapper;\n}\n","import { areRoutesRelated } from \"@real-router/route-utils\";\n\nimport { noopDestroy } from \"./internal/noopDestroy.js\";\n\nimport type { Router } from \"@real-router/core\";\n\nexport interface ActiveNameSelector {\n /**\n * Subscribes to active-state changes of a specific route name.\n * Listener is called only when `isActive(routeName)` for this name transitions.\n * Returns an unsubscribe function.\n */\n subscribe: (routeName: string, listener: () => void) => () => void;\n /**\n * O(1) active check for the given route name (non-strict by default —\n * matches descendants). Uses the shared underlying router subscription.\n */\n isActive: (routeName: string) => boolean;\n /** No-op on the cached wrapper. */\n destroy: () => void;\n}\n\nconst selectorCache = new WeakMap<Router, ActiveNameSelector>();\n\n/**\n * Per-router cached selector providing O(1) active-route checks with one\n * shared router subscription for any number of distinct `routeName`\n * consumers.\n *\n * **When to use:** framework `Link` components that need an active boolean\n * without custom `params` / `activeStrict` / `ignoreQueryParams` — e.g. the\n * common navigation-link case. Multiple `<Link>` components with different\n * `routeName` share ONE `router.subscribe` handle instead of creating one\n * per Link (which is what `createActiveRouteSource(router, name)` does — it\n * caches per-name, so N names = N subscriptions).\n *\n * **When NOT to use:** Link needs `activeStrict: true`, custom `routeParams`,\n * or `ignoreQueryParams: false`. Fall back to `createActiveRouteSource` —\n * its cache handles the full argument surface.\n *\n * Based on the `routeSelector` pattern pioneered by `@real-router/solid`'s\n * `RouterProvider` (`createSelector` + `areRoutesRelated`). This helper\n * ports it to framework-agnostic API so Vue / React / Preact / Svelte /\n * Angular Link components can adopt the same fast path.\n *\n * @see Solid reference implementation — `packages/solid/src/RouterProvider.tsx`\n */\nexport function createActiveNameSelector(router: Router): ActiveNameSelector {\n const cached = selectorCache.get(router);\n\n if (cached) {\n return cached;\n }\n\n // listeners per-name — re-evaluated on every router transition\n const listenersByName = new Map<string, Set<() => void>>();\n // cached active state per-name — used to diff before notifying\n const activeByName = new Map<string, boolean>();\n\n let routerUnsubscribe: (() => void) | null = null;\n\n const isActiveNonStrict = (routeName: string): boolean => {\n const current = router.getState();\n\n if (!current) {\n return false;\n }\n\n // Empty string represents the root of the name hierarchy — every named\n // route is its descendant. Without this short-circuit, `current.name`\n // would have to equal `\"\"` or start with `\".\"` (both impossible for\n // valid route names), breaking symmetry with `createRouteNodeSource(\"\")`\n // which is always-active when a route is current.\n if (routeName === \"\") {\n return true;\n }\n\n return (\n current.name === routeName || current.name.startsWith(`${routeName}.`)\n );\n };\n\n const connect = (): void => {\n routerUnsubscribe = router.subscribe((next) => {\n for (const [routeName, listeners] of listenersByName) {\n // Cheap pre-filter: if neither new nor previous route is related\n // to this name, its active state cannot have changed. Empty\n // routeName is the implicit root — every route is its descendant,\n // so the filter would falsely exclude it (`areRoutesRelated`\n // doesn't treat `\"\"` specially). Skip the filter for the root.\n if (routeName !== \"\") {\n const isNewRelated = areRoutesRelated(routeName, next.route.name);\n const isPrevRelated =\n next.previousRoute &&\n areRoutesRelated(routeName, next.previousRoute.name);\n\n if (!isNewRelated && !isPrevRelated) {\n continue;\n }\n }\n\n // activeByName always has an entry for names present in listenersByName —\n // subscribe() seeds it, and we only iterate over listenersByName.\n const prevActive = activeByName.get(routeName) === true;\n const nextActive = isActiveNonStrict(routeName);\n\n if (prevActive === nextActive) {\n continue;\n }\n\n activeByName.set(routeName, nextActive);\n for (const listener of listeners) {\n listener();\n }\n }\n });\n };\n\n const disconnect = (): void => {\n const unsub = routerUnsubscribe;\n\n routerUnsubscribe = null;\n unsub?.();\n };\n\n const subscribe = (routeName: string, listener: () => void): (() => void) => {\n let listeners = listenersByName.get(routeName);\n\n if (!listeners) {\n listeners = new Set();\n listenersByName.set(routeName, listeners);\n activeByName.set(routeName, isActiveNonStrict(routeName));\n }\n\n listeners.add(listener);\n\n if (!routerUnsubscribe) {\n connect();\n }\n\n let unsubscribed = false;\n\n return () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n listeners.delete(listener);\n\n if (listeners.size === 0) {\n listenersByName.delete(routeName);\n activeByName.delete(routeName);\n }\n\n if (listenersByName.size === 0) {\n disconnect();\n }\n };\n };\n\n const isActive = (routeName: string): boolean => {\n const cachedActive = activeByName.get(routeName);\n\n if (cachedActive !== undefined) {\n return cachedActive;\n }\n\n // Not subscribed — compute on demand.\n return isActiveNonStrict(routeName);\n };\n\n const selector: ActiveNameSelector = {\n subscribe,\n isActive,\n destroy: noopDestroy,\n };\n\n selectorCache.set(router, selector);\n\n return selector;\n}\n"],"mappings":"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"}
|