@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.
@@ -1,41 +0,0 @@
1
- import type { ActiveRouteSourceOptions } from "./types.js";
2
-
3
- /**
4
- * Normalized options shape — booleans are required (filled with defaults),
5
- * `hash` stays `string | undefined` because `undefined` is the meaningful
6
- * "ignore hash" sentinel that callers pass intentionally.
7
- */
8
- export interface NormalizedActiveOptions {
9
- strict: boolean;
10
- ignoreQueryParams: boolean;
11
- hash: string | undefined;
12
- }
13
-
14
- /**
15
- * Default options for `createActiveRouteSource` and adapter-level helpers.
16
- *
17
- * Frozen to prevent accidental mutation by consumers.
18
- */
19
- export const DEFAULT_ACTIVE_OPTIONS: Readonly<NormalizedActiveOptions> =
20
- Object.freeze({
21
- strict: false,
22
- ignoreQueryParams: true,
23
- hash: undefined,
24
- });
25
-
26
- /**
27
- * Normalizes partial `ActiveRouteSourceOptions` into a fully-defaulted object.
28
- *
29
- * Use this to produce a stable options record for comparison, caching, or
30
- * downstream consumers that require all fields present.
31
- */
32
- export function normalizeActiveOptions(
33
- options?: ActiveRouteSourceOptions,
34
- ): NormalizedActiveOptions {
35
- return {
36
- strict: options?.strict ?? DEFAULT_ACTIVE_OPTIONS.strict,
37
- ignoreQueryParams:
38
- options?.ignoreQueryParams ?? DEFAULT_ACTIVE_OPTIONS.ignoreQueryParams,
39
- hash: options?.hash,
40
- };
41
- }
@@ -1,75 +0,0 @@
1
- import { readContextHash } from "./internal/readContextHash.js";
2
-
3
- import type { State } from "@real-router/core";
4
-
5
- /**
6
- * State-aware stabilization for route snapshots.
7
- *
8
- * Compares `path` (canonical name+params), `state.context.url.hash`
9
- * (URL fragment, #532), and `state.transition.reload` (#605). When all
10
- * three match (idempotent navigation), returns `prev` (preserving
11
- * reference) so frameworks can skip re-renders. When any of them flips,
12
- * returns `next` so consumers subscribing through `useRoute()` see the
13
- * new state.
14
- *
15
- * `transition.reload === true` is the user's explicit signal for a
16
- * non-idempotent navigation — `router.navigate(name, params, { reload:
17
- * true })` is the canonical pairing for `invalidate(router, namespace)`
18
- * and any cache-bust pattern. Bypassing stabilization for reloads makes
19
- * `useRoute()` consumers see fresh `state.context.<namespace>` values
20
- * written by the SSR loader plugin's `subscribeLeave` handler.
21
- *
22
- * Ignores `meta` (internal: auto-increment id), other `transition` fields
23
- * (`from`, `segments`, `redirected`), and `state.context.navigation` /
24
- * `state.context.browser` (transient transition metadata) — they don't
25
- * affect render identity for idempotent navigations.
26
- *
27
- * Accepts `null` for compatibility with `RouterTransitionSnapshot`
28
- * (toRoute/fromRoute are `State | null`).
29
- *
30
- * @internal Not exported from package public API.
31
- */
32
- export function stabilizeState<T extends State | null | undefined>(
33
- prev: T,
34
- next: T,
35
- ): T {
36
- if (prev === next) {
37
- return prev;
38
- }
39
- if (prev?.path !== next?.path) {
40
- return next;
41
- }
42
-
43
- // After the path check, both must be the same non-null State (paths
44
- // matched, prev !== next reference). Read context.url.hash to detect
45
- // same-path-different-hash navigation (#532) — render-relevant for
46
- // tab-style UIs that subscribe via useRoute(). Optional chaining keeps
47
- // the access null-safe without forbidden non-null assertions.
48
- if (readContextHash(prev) !== readContextHash(next)) {
49
- return next;
50
- }
51
-
52
- // Explicit reload navigation (#605) — caller asked to bypass dedupe so
53
- // observers see fresh `state.context` written by `invalidate()`-driven
54
- // loader re-runs. The path equality above guarantees both prev and next
55
- // are either non-null with matching paths or both nullish; only the
56
- // non-null branch can carry a meaningful `transition.reload`.
57
- if (readReloadFlag(next)) {
58
- return next;
59
- }
60
-
61
- return prev;
62
- }
63
-
64
- function readReloadFlag(state: State | null | undefined): boolean {
65
- // Defensive read: `transition` is mandatory in the public State type, but a
66
- // plugin returning a malformed state (or a future fork) shouldn't crash the
67
- // stabilizer with a TypeError. We cast to a structurally-loose shape so the
68
- // optional chain is permitted; the runtime guard preserves dedup (false =
69
- // not-a-reload) for malformed inputs.
70
- const transition = (
71
- state as { transition?: { reload?: boolean } } | null | undefined
72
- )?.transition;
73
-
74
- return transition?.reload === true;
75
- }
package/src/types.ts DELETED
@@ -1,63 +0,0 @@
1
- import type { Params, RouterError, State } from "@real-router/core";
2
-
3
- export interface RouteSnapshot<P extends Params = Params> {
4
- route: State<P> | undefined;
5
- previousRoute: State | undefined;
6
- }
7
-
8
- export interface RouteNodeSnapshot<P extends Params = Params> {
9
- route: State<P> | undefined;
10
- previousRoute: State | undefined;
11
- }
12
-
13
- export interface RouterSource<T> {
14
- subscribe: (listener: () => void) => () => void;
15
- getSnapshot: () => T;
16
- destroy: () => void;
17
- }
18
-
19
- export interface ActiveRouteSourceOptions {
20
- strict?: boolean;
21
- ignoreQueryParams?: boolean;
22
- /**
23
- * URL fragment match (#532). When defined, the source is active iff the
24
- * route matches AND `state.context.url.hash` (decoded, populated by
25
- * browser/navigation URL plugins) equals this value:
26
- *
27
- * - `undefined` (default): hash is ignored — legacy route-only matching.
28
- * - `""`: active only when the current URL has no fragment (or empty).
29
- * - `"value"`: active only when the current fragment equals `"value"`.
30
- *
31
- * Hash-plugin runtimes leave `state.context.url` undefined, so any non-
32
- * undefined `hash` option will produce `false` there — consistent with the
33
- * documented limitation that hash-plugin doesn't support URL fragments.
34
- */
35
- hash?: string;
36
- }
37
-
38
- export interface RouterTransitionSnapshot {
39
- isTransitioning: boolean;
40
- isLeaveApproved: boolean;
41
- toRoute: State | null;
42
- fromRoute: State | null;
43
- }
44
-
45
- export interface RouterErrorSnapshot {
46
- error: RouterError | null;
47
- toRoute: State | null;
48
- fromRoute: State | null;
49
- version: number;
50
- }
51
-
52
- export interface DismissableErrorSnapshot {
53
- /** Currently visible error, or `null` if none (never seen or dismissed). */
54
- error: RouterError | null;
55
- /** Target route of the failed navigation. */
56
- toRoute: State | null;
57
- /** Source route at the time of failure. */
58
- fromRoute: State | null;
59
- /** Monotonic version counter from the underlying error source. */
60
- version: number;
61
- /** Dismisses the current error. Next error (new version) becomes visible again. */
62
- resetError: () => void;
63
- }