@real-router/sources 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -14
- package/dist/cjs/index.d.ts +167 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.mts +167 -1
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/BaseSource.ts +10 -3
- package/src/canonicalJson.ts +36 -0
- package/src/createActiveNameSelector.ts +170 -0
- package/src/createActiveRouteSource.ts +98 -9
- package/src/createDismissableError.ts +95 -0
- package/src/createErrorSource.ts +45 -0
- package/src/createRouteNodeSource.ts +50 -3
- package/src/createTransitionSource.ts +45 -0
- package/src/index.ts +19 -2
- package/src/normalizeActiveOptions.ts +29 -0
- package/src/types.ts +13 -0
- package/src/shouldUpdateCache.ts +0 -27
|
@@ -1,19 +1,105 @@
|
|
|
1
1
|
import { areRoutesRelated } from "@real-router/route-utils";
|
|
2
2
|
|
|
3
3
|
import { BaseSource } from "./BaseSource";
|
|
4
|
+
import { canonicalJson } from "./canonicalJson.js";
|
|
5
|
+
import { normalizeActiveOptions } from "./normalizeActiveOptions.js";
|
|
4
6
|
|
|
5
7
|
import type { ActiveRouteSourceOptions, RouterSource } from "./types.js";
|
|
6
8
|
import type { Params, Router } from "@real-router/core";
|
|
7
9
|
|
|
10
|
+
const activeSourceCache = new WeakMap<
|
|
11
|
+
Router,
|
|
12
|
+
Map<string, RouterSource<boolean>>
|
|
13
|
+
>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a source tracking whether a route (with given params/options) is active.
|
|
17
|
+
*
|
|
18
|
+
* **Per-router + canonical-args cache:** repeated calls with equivalent
|
|
19
|
+
* arguments return the same shared instance. Param key order doesn't matter
|
|
20
|
+
* (`{ a:1, b:2 }` and `{ b:2, a:1 }` hit the same cache entry via
|
|
21
|
+
* `canonicalJson`).
|
|
22
|
+
*
|
|
23
|
+
* `destroy()` is a no-op — shared sources live with the router. The router
|
|
24
|
+
* subscription stays active while any consumer subscribes; when the router
|
|
25
|
+
* is garbage-collected, the WeakMap entry releases automatically.
|
|
26
|
+
*
|
|
27
|
+
* Edge cases: `Symbol`/`BigInt` in params bypass `canonicalJson` and produce
|
|
28
|
+
* an unstable cache key — these will simply miss the cache and create a new
|
|
29
|
+
* source on each call. Practical params are primitives, so this is not a
|
|
30
|
+
* concern in real usage.
|
|
31
|
+
*/
|
|
8
32
|
export function createActiveRouteSource(
|
|
9
33
|
router: Router,
|
|
10
34
|
routeName: string,
|
|
11
35
|
params?: Params,
|
|
12
36
|
options?: ActiveRouteSourceOptions,
|
|
13
37
|
): RouterSource<boolean> {
|
|
14
|
-
const strict = options
|
|
15
|
-
|
|
38
|
+
const { strict, ignoreQueryParams } = normalizeActiveOptions(options);
|
|
39
|
+
|
|
40
|
+
// BigInt/Symbol/circular refs cannot be serialized — fall back to creating
|
|
41
|
+
// a fresh (non-cached) source. Callers pass these edge-case params rarely;
|
|
42
|
+
// the extra allocation is acceptable.
|
|
43
|
+
let key: string | undefined;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
key = `${routeName}|${canonicalJson(params)}|${String(strict)}|${String(ignoreQueryParams)}`;
|
|
47
|
+
} catch {
|
|
48
|
+
key = undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (key === undefined) {
|
|
52
|
+
const source = buildActiveRouteSource(
|
|
53
|
+
router,
|
|
54
|
+
routeName,
|
|
55
|
+
params,
|
|
56
|
+
strict,
|
|
57
|
+
ignoreQueryParams,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
subscribe: source.subscribe,
|
|
62
|
+
getSnapshot: source.getSnapshot,
|
|
63
|
+
destroy: noopDestroy,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let perRouter = activeSourceCache.get(router);
|
|
68
|
+
|
|
69
|
+
if (!perRouter) {
|
|
70
|
+
perRouter = new Map();
|
|
71
|
+
activeSourceCache.set(router, perRouter);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let cached = perRouter.get(key);
|
|
75
|
+
|
|
76
|
+
if (!cached) {
|
|
77
|
+
const source = buildActiveRouteSource(
|
|
78
|
+
router,
|
|
79
|
+
routeName,
|
|
80
|
+
params,
|
|
81
|
+
strict,
|
|
82
|
+
ignoreQueryParams,
|
|
83
|
+
);
|
|
16
84
|
|
|
85
|
+
cached = {
|
|
86
|
+
subscribe: source.subscribe,
|
|
87
|
+
getSnapshot: source.getSnapshot,
|
|
88
|
+
destroy: noopDestroy,
|
|
89
|
+
};
|
|
90
|
+
perRouter.set(key, cached);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return cached;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildActiveRouteSource(
|
|
97
|
+
router: Router,
|
|
98
|
+
routeName: string,
|
|
99
|
+
params: Params | undefined,
|
|
100
|
+
strict: boolean,
|
|
101
|
+
ignoreQueryParams: boolean,
|
|
102
|
+
): RouterSource<boolean> {
|
|
17
103
|
const initialValue = router.isActiveRoute(
|
|
18
104
|
routeName,
|
|
19
105
|
params,
|
|
@@ -21,14 +107,13 @@ export function createActiveRouteSource(
|
|
|
21
107
|
ignoreQueryParams,
|
|
22
108
|
);
|
|
23
109
|
|
|
24
|
-
const source = new BaseSource(initialValue
|
|
25
|
-
onDestroy: () => {
|
|
26
|
-
unsubscribe();
|
|
27
|
-
},
|
|
28
|
-
});
|
|
110
|
+
const source = new BaseSource(initialValue);
|
|
29
111
|
|
|
30
|
-
// Eager connection: subscribe to router immediately
|
|
31
|
-
|
|
112
|
+
// Eager connection: subscribe to router immediately. This source is only
|
|
113
|
+
// ever reached through the cached public `createActiveRouteSource`, whose
|
|
114
|
+
// returned wrapper has a no-op destroy. The source lives with the router;
|
|
115
|
+
// the router.subscribe handle is released on router GC.
|
|
116
|
+
router.subscribe((next) => {
|
|
32
117
|
const isNewRelated = areRoutesRelated(routeName, next.route.name);
|
|
33
118
|
const isPrevRelated =
|
|
34
119
|
next.previousRoute &&
|
|
@@ -51,3 +136,7 @@ export function createActiveRouteSource(
|
|
|
51
136
|
|
|
52
137
|
return source;
|
|
53
138
|
}
|
|
139
|
+
|
|
140
|
+
function noopDestroy(): void {
|
|
141
|
+
// Shared cached source — external destroy() is a no-op.
|
|
142
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BaseSource } from "./BaseSource";
|
|
2
|
+
import { getErrorSource } from "./createErrorSource";
|
|
3
|
+
|
|
4
|
+
import type { DismissableErrorSnapshot, RouterSource } from "./types.js";
|
|
5
|
+
import type { Router } from "@real-router/core";
|
|
6
|
+
|
|
7
|
+
const dismissableCache = new WeakMap<
|
|
8
|
+
Router,
|
|
9
|
+
RouterSource<DismissableErrorSnapshot>
|
|
10
|
+
>();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns a per-router cached source that wraps `getErrorSource(router)` with
|
|
14
|
+
* an integrated "dismissed" version counter, exposing a single reactive
|
|
15
|
+
* snapshot `{ error, toRoute, fromRoute, version, resetError }`.
|
|
16
|
+
*
|
|
17
|
+
* Each `RouterErrorBoundary` in a framework adapter subscribes to this one
|
|
18
|
+
* source instead of re-implementing the `dismissedVersion` state pattern
|
|
19
|
+
* locally. The 6-copy duplicate across adapters collapses to this helper.
|
|
20
|
+
*
|
|
21
|
+
* **Semantics:**
|
|
22
|
+
* - `error` is non-null only when `underlying.version > dismissedVersion`.
|
|
23
|
+
* - `resetError()` sets `dismissedVersion = current underlying version`,
|
|
24
|
+
* immediately clearing `error` to `null` and notifying all listeners.
|
|
25
|
+
* - A subsequent `TRANSITION_ERROR` advances `version` beyond `dismissedVersion`,
|
|
26
|
+
* so `error` becomes non-null again — no additional plumbing needed.
|
|
27
|
+
*
|
|
28
|
+
* **Cached:** one instance per router. `destroy()` on the returned source is
|
|
29
|
+
* a no-op. Shared across all `RouterErrorBoundary` consumers.
|
|
30
|
+
*/
|
|
31
|
+
export function createDismissableError(
|
|
32
|
+
router: Router,
|
|
33
|
+
): RouterSource<DismissableErrorSnapshot> {
|
|
34
|
+
const cached = dismissableCache.get(router);
|
|
35
|
+
|
|
36
|
+
if (cached) {
|
|
37
|
+
return cached;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const errorSource = getErrorSource(router);
|
|
41
|
+
|
|
42
|
+
let dismissedVersion = -1;
|
|
43
|
+
|
|
44
|
+
const computeSnapshot = (): DismissableErrorSnapshot => {
|
|
45
|
+
const snap = errorSource.getSnapshot();
|
|
46
|
+
const isDismissed = snap.version <= dismissedVersion;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
error: isDismissed ? null : snap.error,
|
|
50
|
+
toRoute: isDismissed ? null : snap.toRoute,
|
|
51
|
+
fromRoute: isDismissed ? null : snap.fromRoute,
|
|
52
|
+
version: snap.version,
|
|
53
|
+
resetError,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const source = new BaseSource<DismissableErrorSnapshot>(computeSnapshot(), {
|
|
58
|
+
onFirstSubscribe: () => {
|
|
59
|
+
unsubFromError = errorSource.subscribe(() => {
|
|
60
|
+
source.updateSnapshot(computeSnapshot());
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
onLastUnsubscribe: () => {
|
|
64
|
+
disconnect();
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let unsubFromError: (() => void) | null = null;
|
|
69
|
+
|
|
70
|
+
function resetError(): void {
|
|
71
|
+
dismissedVersion = errorSource.getSnapshot().version;
|
|
72
|
+
source.updateSnapshot(computeSnapshot());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function disconnect(): void {
|
|
76
|
+
const unsub = unsubFromError;
|
|
77
|
+
|
|
78
|
+
unsubFromError = null;
|
|
79
|
+
unsub?.();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const wrapper: RouterSource<DismissableErrorSnapshot> = {
|
|
83
|
+
subscribe: source.subscribe,
|
|
84
|
+
getSnapshot: source.getSnapshot,
|
|
85
|
+
destroy: noopDestroy,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
dismissableCache.set(router, wrapper);
|
|
89
|
+
|
|
90
|
+
return wrapper;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function noopDestroy(): void {
|
|
94
|
+
// Shared cached source — external destroy() is a no-op.
|
|
95
|
+
}
|
package/src/createErrorSource.ts
CHANGED
|
@@ -13,6 +13,11 @@ const INITIAL_SNAPSHOT: RouterErrorSnapshot = {
|
|
|
13
13
|
version: 0,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const errorSourceCache = new WeakMap<
|
|
17
|
+
Router,
|
|
18
|
+
RouterSource<RouterErrorSnapshot>
|
|
19
|
+
>();
|
|
20
|
+
|
|
16
21
|
export function createErrorSource(
|
|
17
22
|
router: Router,
|
|
18
23
|
): RouterSource<RouterErrorSnapshot> {
|
|
@@ -64,3 +69,43 @@ export function createErrorSource(
|
|
|
64
69
|
|
|
65
70
|
return source;
|
|
66
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns a per-router cached error source shared across all consumers.
|
|
75
|
+
*
|
|
76
|
+
* Safe to call destroy() — the cached source ignores external destroy() calls
|
|
77
|
+
* and lives until the router itself is garbage-collected (the WeakMap entry
|
|
78
|
+
* releases automatically).
|
|
79
|
+
*
|
|
80
|
+
* Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to
|
|
81
|
+
* share a single ErrorSource instance across all mount/unmount cycles.
|
|
82
|
+
*
|
|
83
|
+
* For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call
|
|
84
|
+
* `createErrorSource(router)` directly — it returns a fresh instance with a
|
|
85
|
+
* working `destroy()`.
|
|
86
|
+
*/
|
|
87
|
+
export function getErrorSource(
|
|
88
|
+
router: Router,
|
|
89
|
+
): RouterSource<RouterErrorSnapshot> {
|
|
90
|
+
let cached = errorSourceCache.get(router);
|
|
91
|
+
|
|
92
|
+
if (!cached) {
|
|
93
|
+
const source = createErrorSource(router);
|
|
94
|
+
|
|
95
|
+
// Wrap with no-op destroy. The underlying source is shared across all
|
|
96
|
+
// consumers; letting any one consumer call destroy() would tear it down
|
|
97
|
+
// for the rest. The source lives as long as the router (WeakMap key).
|
|
98
|
+
cached = {
|
|
99
|
+
subscribe: source.subscribe,
|
|
100
|
+
getSnapshot: source.getSnapshot,
|
|
101
|
+
destroy: noopDestroy,
|
|
102
|
+
};
|
|
103
|
+
errorSourceCache.set(router, cached);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return cached;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function noopDestroy(): void {
|
|
110
|
+
// Shared cached source — external destroy() is a no-op.
|
|
111
|
+
}
|
|
@@ -1,24 +1,68 @@
|
|
|
1
1
|
import { BaseSource } from "./BaseSource";
|
|
2
2
|
import { computeSnapshot } from "./computeSnapshot.js";
|
|
3
|
-
import { getCachedShouldUpdate } from "./shouldUpdateCache.js";
|
|
4
3
|
|
|
5
4
|
import type { RouteNodeSnapshot, RouterSource } from "./types.js";
|
|
6
5
|
import type { Router } from "@real-router/core";
|
|
7
6
|
|
|
7
|
+
const nodeSourceCache = new WeakMap<
|
|
8
|
+
Router,
|
|
9
|
+
Map<string, RouterSource<RouteNodeSnapshot>>
|
|
10
|
+
>();
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* Creates a source scoped to a specific route node.
|
|
10
14
|
*
|
|
15
|
+
* **Per-router + per-nodeName cache:** repeated calls with the same
|
|
16
|
+
* `(router, nodeName)` return the same shared instance. `N` consumers
|
|
17
|
+
* calling `createRouteNodeSource(r, "users")` produce one router subscription
|
|
18
|
+
* shared across all of them.
|
|
19
|
+
*
|
|
11
20
|
* Uses a lazy-connection pattern: the router subscription is created when the
|
|
12
21
|
* first listener subscribes and removed when the last listener unsubscribes.
|
|
13
22
|
* This is compatible with React's useSyncExternalStore and Strict Mode.
|
|
23
|
+
*
|
|
24
|
+
* `destroy()` on the returned source is a no-op — the shared instance lives
|
|
25
|
+
* as long as the router itself (the WeakMap entry releases automatically on
|
|
26
|
+
* router GC). Callers that need an isolated instance with working teardown
|
|
27
|
+
* can use `buildRouteNodeSource` internally (not exported).
|
|
14
28
|
*/
|
|
15
29
|
export function createRouteNodeSource(
|
|
16
30
|
router: Router,
|
|
17
31
|
nodeName: string,
|
|
32
|
+
): RouterSource<RouteNodeSnapshot> {
|
|
33
|
+
let perRouter = nodeSourceCache.get(router);
|
|
34
|
+
|
|
35
|
+
if (!perRouter) {
|
|
36
|
+
perRouter = new Map();
|
|
37
|
+
nodeSourceCache.set(router, perRouter);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let cached = perRouter.get(nodeName);
|
|
41
|
+
|
|
42
|
+
if (!cached) {
|
|
43
|
+
const source = buildRouteNodeSource(router, nodeName);
|
|
44
|
+
|
|
45
|
+
// Wrap with no-op destroy. The shared source lives with the router.
|
|
46
|
+
cached = {
|
|
47
|
+
subscribe: source.subscribe,
|
|
48
|
+
getSnapshot: source.getSnapshot,
|
|
49
|
+
destroy: noopDestroy,
|
|
50
|
+
};
|
|
51
|
+
perRouter.set(nodeName, cached);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildRouteNodeSource(
|
|
58
|
+
router: Router,
|
|
59
|
+
nodeName: string,
|
|
18
60
|
): RouterSource<RouteNodeSnapshot> {
|
|
19
61
|
let routerUnsubscribe: (() => void) | null = null;
|
|
20
62
|
|
|
21
|
-
|
|
63
|
+
// Built once per cached source instance; safe — createRouteNodeSource is
|
|
64
|
+
// itself per-(router, nodeName) cached, so shouldUpdate is called once.
|
|
65
|
+
const shouldUpdate = router.shouldUpdateNode(nodeName);
|
|
22
66
|
|
|
23
67
|
const initialSnapshot: RouteNodeSnapshot = {
|
|
24
68
|
route: undefined,
|
|
@@ -68,9 +112,12 @@ export function createRouteNodeSource(
|
|
|
68
112
|
});
|
|
69
113
|
},
|
|
70
114
|
onLastUnsubscribe: disconnect,
|
|
71
|
-
onDestroy: disconnect,
|
|
72
115
|
},
|
|
73
116
|
);
|
|
74
117
|
|
|
75
118
|
return source;
|
|
76
119
|
}
|
|
120
|
+
|
|
121
|
+
function noopDestroy(): void {
|
|
122
|
+
// Shared cached source — external destroy() is a no-op.
|
|
123
|
+
}
|
|
@@ -14,6 +14,11 @@ const IDLE_SNAPSHOT: RouterTransitionSnapshot = {
|
|
|
14
14
|
fromRoute: null,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const transitionSourceCache = new WeakMap<
|
|
18
|
+
Router,
|
|
19
|
+
RouterSource<RouterTransitionSnapshot>
|
|
20
|
+
>();
|
|
21
|
+
|
|
17
22
|
export function createTransitionSource(
|
|
18
23
|
router: Router,
|
|
19
24
|
): RouterSource<RouterTransitionSnapshot> {
|
|
@@ -74,3 +79,43 @@ export function createTransitionSource(
|
|
|
74
79
|
|
|
75
80
|
return source;
|
|
76
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns a per-router cached transition source shared across all consumers.
|
|
85
|
+
*
|
|
86
|
+
* Safe to call destroy() — the cached source ignores external destroy() calls
|
|
87
|
+
* and lives until the router itself is garbage-collected (the WeakMap entry
|
|
88
|
+
* releases automatically).
|
|
89
|
+
*
|
|
90
|
+
* Use this in framework adapters (React/Preact/Solid/Vue/Svelte/Angular) to
|
|
91
|
+
* share a single TransitionSource instance across all mount/unmount cycles.
|
|
92
|
+
*
|
|
93
|
+
* For isolated/advanced use (ad-hoc, short-lived, per-owner teardown), call
|
|
94
|
+
* `createTransitionSource(router)` directly — it returns a fresh instance with
|
|
95
|
+
* a working `destroy()`.
|
|
96
|
+
*/
|
|
97
|
+
export function getTransitionSource(
|
|
98
|
+
router: Router,
|
|
99
|
+
): RouterSource<RouterTransitionSnapshot> {
|
|
100
|
+
let cached = transitionSourceCache.get(router);
|
|
101
|
+
|
|
102
|
+
if (!cached) {
|
|
103
|
+
const source = createTransitionSource(router);
|
|
104
|
+
|
|
105
|
+
// Wrap with no-op destroy. The underlying source is shared across all
|
|
106
|
+
// consumers; letting any one consumer call destroy() would tear it down
|
|
107
|
+
// for the rest. The source lives as long as the router (WeakMap key).
|
|
108
|
+
cached = {
|
|
109
|
+
subscribe: source.subscribe,
|
|
110
|
+
getSnapshot: source.getSnapshot,
|
|
111
|
+
destroy: noopDestroy,
|
|
112
|
+
};
|
|
113
|
+
transitionSourceCache.set(router, cached);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return cached;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function noopDestroy(): void {
|
|
120
|
+
// Shared cached source — external destroy() is a no-op.
|
|
121
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type {
|
|
|
5
5
|
ActiveRouteSourceOptions,
|
|
6
6
|
RouterTransitionSnapshot,
|
|
7
7
|
RouterErrorSnapshot,
|
|
8
|
+
DismissableErrorSnapshot,
|
|
8
9
|
} from "./types.js";
|
|
9
10
|
|
|
10
11
|
export { createRouteSource } from "./createRouteSource";
|
|
@@ -13,6 +14,22 @@ export { createRouteNodeSource } from "./createRouteNodeSource";
|
|
|
13
14
|
|
|
14
15
|
export { createActiveRouteSource } from "./createActiveRouteSource";
|
|
15
16
|
|
|
16
|
-
export {
|
|
17
|
+
export {
|
|
18
|
+
createTransitionSource,
|
|
19
|
+
getTransitionSource,
|
|
20
|
+
} from "./createTransitionSource";
|
|
17
21
|
|
|
18
|
-
export { createErrorSource } from "./createErrorSource";
|
|
22
|
+
export { createErrorSource, getErrorSource } from "./createErrorSource";
|
|
23
|
+
|
|
24
|
+
export { createDismissableError } from "./createDismissableError";
|
|
25
|
+
|
|
26
|
+
export { createActiveNameSelector } from "./createActiveNameSelector";
|
|
27
|
+
|
|
28
|
+
export type { ActiveNameSelector } from "./createActiveNameSelector";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
DEFAULT_ACTIVE_OPTIONS,
|
|
32
|
+
normalizeActiveOptions,
|
|
33
|
+
} from "./normalizeActiveOptions";
|
|
34
|
+
|
|
35
|
+
export { canonicalJson } from "./canonicalJson";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ActiveRouteSourceOptions } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default options for `createActiveRouteSource` and adapter-level helpers.
|
|
5
|
+
*
|
|
6
|
+
* Frozen to prevent accidental mutation by consumers.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_ACTIVE_OPTIONS: Readonly<
|
|
9
|
+
Required<ActiveRouteSourceOptions>
|
|
10
|
+
> = Object.freeze({
|
|
11
|
+
strict: false,
|
|
12
|
+
ignoreQueryParams: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes partial `ActiveRouteSourceOptions` into a fully-defaulted object.
|
|
17
|
+
*
|
|
18
|
+
* Use this to produce a stable options record for comparison, caching, or
|
|
19
|
+
* downstream consumers that require all fields present.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeActiveOptions(
|
|
22
|
+
options?: ActiveRouteSourceOptions,
|
|
23
|
+
): Required<ActiveRouteSourceOptions> {
|
|
24
|
+
return {
|
|
25
|
+
strict: options?.strict ?? DEFAULT_ACTIVE_OPTIONS.strict,
|
|
26
|
+
ignoreQueryParams:
|
|
27
|
+
options?.ignoreQueryParams ?? DEFAULT_ACTIVE_OPTIONS.ignoreQueryParams,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -34,3 +34,16 @@ export interface RouterErrorSnapshot {
|
|
|
34
34
|
fromRoute: State | null;
|
|
35
35
|
version: number;
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
export interface DismissableErrorSnapshot {
|
|
39
|
+
/** Currently visible error, or `null` if none (never seen or dismissed). */
|
|
40
|
+
error: RouterError | null;
|
|
41
|
+
/** Target route of the failed navigation. */
|
|
42
|
+
toRoute: State | null;
|
|
43
|
+
/** Source route at the time of failure. */
|
|
44
|
+
fromRoute: State | null;
|
|
45
|
+
/** Monotonic version counter from the underlying error source. */
|
|
46
|
+
version: number;
|
|
47
|
+
/** Dismisses the current error. Next error (new version) becomes visible again. */
|
|
48
|
+
resetError: () => void;
|
|
49
|
+
}
|
package/src/shouldUpdateCache.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Router, State } from "@real-router/core";
|
|
2
|
-
|
|
3
|
-
const shouldUpdateCache = new WeakMap<
|
|
4
|
-
Router,
|
|
5
|
-
Map<string, (toState: State, fromState?: State) => boolean>
|
|
6
|
-
>();
|
|
7
|
-
|
|
8
|
-
export function getCachedShouldUpdate(
|
|
9
|
-
router: Router,
|
|
10
|
-
nodeName: string,
|
|
11
|
-
): (toState: State, fromState?: State) => boolean {
|
|
12
|
-
let routerCache = shouldUpdateCache.get(router);
|
|
13
|
-
|
|
14
|
-
if (!routerCache) {
|
|
15
|
-
routerCache = new Map();
|
|
16
|
-
shouldUpdateCache.set(router, routerCache);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let fn = routerCache.get(nodeName);
|
|
20
|
-
|
|
21
|
-
if (!fn) {
|
|
22
|
-
fn = router.shouldUpdateNode(nodeName);
|
|
23
|
-
routerCache.set(nodeName, fn);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return fn;
|
|
27
|
-
}
|