@real-router/core 0.47.0 → 0.48.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/dist/cjs/{Router-BtWR1xO-.js → Router-But6ZKYG.js} +3 -3
- package/dist/cjs/Router-But6ZKYG.js.map +1 -0
- package/dist/cjs/Router-Dh1xgFLI.d.ts.map +1 -1
- package/dist/cjs/RouterError-CWIGEyPI.js +2 -0
- package/dist/cjs/RouterError-CWIGEyPI.js.map +1 -0
- package/dist/cjs/api.d.ts.map +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/{getPluginApi-1VcDVGjf.js → getPluginApi-DMO1jdNK.js} +2 -2
- package/dist/cjs/getPluginApi-DMO1jdNK.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/internals-na15rxo_.js.map +1 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/validation.d.ts +1 -0
- package/dist/cjs/validation.d.ts.map +1 -1
- package/dist/esm/Router-BPkXwb1J.d.mts.map +1 -1
- package/dist/esm/Router-BThyTcCs.mjs +6 -0
- package/dist/esm/Router-BThyTcCs.mjs.map +1 -0
- package/dist/esm/RouterError-2JY9OfZc.mjs +2 -0
- package/dist/esm/RouterError-2JY9OfZc.mjs.map +1 -0
- package/dist/esm/api.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/{getPluginApi-BvOUPp3g.mjs → getPluginApi-Bwp0MNW9.mjs} +2 -2
- package/dist/esm/getPluginApi-Bwp0MNW9.mjs.map +1 -0
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/internals-CCymabFj.mjs.map +1 -1
- package/dist/esm/utils.mjs +1 -1
- package/dist/esm/validation.d.mts +1 -0
- package/dist/esm/validation.d.mts.map +1 -1
- package/package.json +2 -2
- package/src/Router.ts +4 -0
- package/src/api/getPluginApi.ts +27 -1
- package/src/api/getRoutesApi.ts +16 -4
- package/src/constants.ts +14 -0
- package/src/helpers.ts +14 -49
- package/src/internals.ts +1 -0
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +3 -2
- package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +12 -8
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +4 -2
- package/src/namespaces/StateNamespace/StateNamespace.ts +19 -6
- package/dist/cjs/Router-BtWR1xO-.js.map +0 -1
- package/dist/cjs/RouterError-AQUx-VLW.js +0 -2
- package/dist/cjs/RouterError-AQUx-VLW.js.map +0 -1
- package/dist/cjs/getPluginApi-1VcDVGjf.js.map +0 -1
- package/dist/esm/Router-DmPynezS.mjs +0 -6
- package/dist/esm/Router-DmPynezS.mjs.map +0 -1
- package/dist/esm/RouterError-BOUkCIgf.mjs +0 -2
- package/dist/esm/RouterError-BOUkCIgf.mjs.map +0 -1
- package/dist/esm/getPluginApi-BvOUPp3g.mjs.map +0 -1
package/src/helpers.ts
CHANGED
|
@@ -109,65 +109,30 @@ export function deepFreezeState<T extends State>(state: T): T {
|
|
|
109
109
|
return clonedState;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// WeakSet to track already frozen root objects for O(1) re-freeze check
|
|
113
|
-
const frozenRoots = new WeakSet<object>();
|
|
114
|
-
|
|
115
|
-
// Module-scope recursive freeze function - better JIT optimization, no allocation per call
|
|
116
|
-
function freezeRecursive(obj: unknown): void {
|
|
117
|
-
// Skip primitives, null
|
|
118
|
-
if (obj === null || typeof obj !== "object") {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Skip already frozen objects (handles potential shared refs)
|
|
123
|
-
if (Object.isFrozen(obj)) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Freeze the object/array
|
|
128
|
-
Object.freeze(obj);
|
|
129
|
-
|
|
130
|
-
// Iterate without Object.values() allocation
|
|
131
|
-
if (Array.isArray(obj)) {
|
|
132
|
-
for (const item of obj) {
|
|
133
|
-
freezeRecursive(item);
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
for (const key in obj) {
|
|
137
|
-
freezeRecursive((obj as Record<string, unknown>)[key]);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
112
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
113
|
+
* Shallow-freezes a State object in place.
|
|
114
|
+
*
|
|
115
|
+
* Freezes only the top-level State object (blocks reassignment of `name`,
|
|
116
|
+
* `params`, `path`, `transition`, `context`). Nested objects (`params`,
|
|
117
|
+
* `transition`, `transition.segments`, `transition.segments.{deactivated,activated}`)
|
|
118
|
+
* are expected to be **already frozen at creation time** by their producers:
|
|
119
|
+
*
|
|
120
|
+
* - `params` frozen in `makeState()` / `navigateToNotFound()`
|
|
121
|
+
* - `transition`, `segments`, `deactivated`, `activated` frozen in
|
|
122
|
+
* `buildTransitionMeta()` (or inline in `navigateToNotFound()`)
|
|
145
123
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
124
|
+
* `state.context` is **intentionally not frozen** — plugins write to it via
|
|
125
|
+
* `claim.write(state, value)` after state creation.
|
|
148
126
|
*
|
|
149
|
-
* @param state - The State object to freeze (must be a fresh object)
|
|
150
|
-
* @returns The same state object, now frozen
|
|
151
127
|
* @internal
|
|
152
128
|
*/
|
|
153
129
|
export function freezeStateInPlace<T extends State>(state: T): T {
|
|
154
|
-
//
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive guard against external misuse
|
|
156
131
|
if (!state) {
|
|
157
132
|
return state;
|
|
158
133
|
}
|
|
159
134
|
|
|
160
|
-
|
|
161
|
-
if (frozenRoots.has(state)) {
|
|
162
|
-
return state;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
freezeRecursive(state);
|
|
166
|
-
|
|
167
|
-
// Mark root as processed for future calls
|
|
168
|
-
frozenRoots.add(state);
|
|
169
|
-
|
|
170
|
-
return state;
|
|
135
|
+
return Object.freeze(state);
|
|
171
136
|
}
|
|
172
137
|
|
|
173
138
|
/**
|
package/src/internals.ts
CHANGED
|
@@ -90,6 +90,7 @@ export interface RouterInternals<
|
|
|
90
90
|
readonly clearState: () => void;
|
|
91
91
|
readonly setState: (state: State) => void;
|
|
92
92
|
readonly routerExtensions: { keys: string[] }[];
|
|
93
|
+
readonly contextClaimRecords: Set<string>;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { completeTransition } from "./transition/completeTransition";
|
|
11
11
|
import { routeTransitionError } from "./transition/errorHandling";
|
|
12
12
|
import { executeGuardPipeline } from "./transition/guardPhase";
|
|
13
|
-
import { errorCodes, constants } from "../../constants";
|
|
13
|
+
import { EMPTY_PARAMS, errorCodes, constants } from "../../constants";
|
|
14
14
|
import { RouterError } from "../../RouterError";
|
|
15
15
|
import { getTransitionPath, nameToIDs } from "../../transitionPath";
|
|
16
16
|
|
|
@@ -310,9 +310,10 @@ export class NavigationNamespace {
|
|
|
310
310
|
|
|
311
311
|
const state: State = {
|
|
312
312
|
name: constants.UNKNOWN_ROUTE,
|
|
313
|
-
params:
|
|
313
|
+
params: EMPTY_PARAMS as Params,
|
|
314
314
|
path,
|
|
315
315
|
transition: transitionMeta,
|
|
316
|
+
context: {},
|
|
316
317
|
};
|
|
317
318
|
|
|
318
319
|
Object.freeze(state);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { errorCodes, constants } from "../../../constants";
|
|
2
|
-
import { freezeStateInPlace } from "../../../helpers";
|
|
3
2
|
import { RouterError } from "../../../RouterError";
|
|
4
3
|
|
|
5
4
|
import type { NavigationDependencies, NavigationContext } from "../types";
|
|
@@ -20,14 +19,19 @@ function buildTransitionMeta(
|
|
|
20
19
|
toActivate: string[],
|
|
21
20
|
intersection: string,
|
|
22
21
|
): TransitionMeta {
|
|
22
|
+
Object.freeze(toDeactivate);
|
|
23
|
+
Object.freeze(toActivate);
|
|
24
|
+
|
|
25
|
+
const segments = Object.freeze({
|
|
26
|
+
deactivated: toDeactivate,
|
|
27
|
+
activated: toActivate,
|
|
28
|
+
intersection,
|
|
29
|
+
});
|
|
30
|
+
|
|
23
31
|
const meta: MutableTransitionMeta = {
|
|
24
32
|
phase: "activating",
|
|
25
33
|
reason: "success",
|
|
26
|
-
segments
|
|
27
|
-
deactivated: toDeactivate,
|
|
28
|
-
activated: toActivate,
|
|
29
|
-
intersection,
|
|
30
|
-
},
|
|
34
|
+
segments,
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
if (fromState?.name !== undefined) {
|
|
@@ -42,7 +46,7 @@ function buildTransitionMeta(
|
|
|
42
46
|
meta.redirected = opts.redirected;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
return meta;
|
|
49
|
+
return Object.freeze(meta);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
function stripSignal({
|
|
@@ -88,7 +92,7 @@ export function completeTransition(
|
|
|
88
92
|
intersection,
|
|
89
93
|
);
|
|
90
94
|
|
|
91
|
-
const finalState =
|
|
95
|
+
const finalState = Object.freeze(toState);
|
|
92
96
|
|
|
93
97
|
deps.setState(finalState);
|
|
94
98
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
rebuildTreeInPlace,
|
|
8
8
|
resetStore,
|
|
9
9
|
} from "./routesStore";
|
|
10
|
-
import { constants } from "../../constants";
|
|
10
|
+
import { constants, DEFAULT_TRANSITION } from "../../constants";
|
|
11
11
|
import { getTransitionPath } from "../../transitionPath";
|
|
12
12
|
|
|
13
13
|
import type { RoutesStore } from "./routesStore";
|
|
@@ -107,7 +107,7 @@ export class RoutesNamespace<
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
if (toState.transition
|
|
110
|
+
if (toState.transition.reload) {
|
|
111
111
|
return true;
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -403,6 +403,8 @@ export class RoutesNamespace<
|
|
|
403
403
|
name,
|
|
404
404
|
params: effectiveParams,
|
|
405
405
|
path: "",
|
|
406
|
+
transition: DEFAULT_TRANSITION,
|
|
407
|
+
context: {},
|
|
406
408
|
};
|
|
407
409
|
|
|
408
410
|
return this.#deps.areStatesEqual(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// packages/core/src/namespaces/StateNamespace/StateNamespace.ts
|
|
2
2
|
|
|
3
3
|
import { areParamValuesEqual } from "./helpers";
|
|
4
|
-
import { EMPTY_PARAMS } from "../../constants";
|
|
4
|
+
import { DEFAULT_TRANSITION, EMPTY_PARAMS } from "../../constants";
|
|
5
5
|
import { freezeStateInPlace } from "../../helpers";
|
|
6
6
|
import { setStateMetaParams } from "../../stateMetaStore";
|
|
7
7
|
|
|
@@ -97,7 +97,15 @@ export class StateNamespace {
|
|
|
97
97
|
// =========================================================================
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Creates a
|
|
100
|
+
* Creates a state object for a route.
|
|
101
|
+
*
|
|
102
|
+
* `params` is frozen at creation so it is always immutable, even when
|
|
103
|
+
* `skipFreeze=true` is passed to defer the outer `Object.freeze(state)` call.
|
|
104
|
+
* This keeps params-freezing invariants independent of transition-pipeline
|
|
105
|
+
* mutation (e.g. `completeTransition` attaching `state.transition`).
|
|
106
|
+
*
|
|
107
|
+
* `context` is initialized as a fresh empty object — intentionally NOT frozen
|
|
108
|
+
* so plugins can publish data via `claim.write(state, value)` after creation.
|
|
101
109
|
*/
|
|
102
110
|
makeState<P extends Params = Params>(
|
|
103
111
|
name: string,
|
|
@@ -114,18 +122,23 @@ export class StateNamespace {
|
|
|
114
122
|
let mergedParams: P;
|
|
115
123
|
|
|
116
124
|
if (hasDefaultParams) {
|
|
117
|
-
mergedParams = {
|
|
125
|
+
mergedParams = Object.freeze({
|
|
126
|
+
...defaultParamsConfig[name],
|
|
127
|
+
...params,
|
|
128
|
+
}) as P;
|
|
118
129
|
} else if (!params || params === EMPTY_PARAMS) {
|
|
119
130
|
mergedParams = EMPTY_PARAMS as P;
|
|
120
131
|
} else {
|
|
121
|
-
mergedParams = { ...params };
|
|
132
|
+
mergedParams = Object.freeze({ ...params }) as P;
|
|
122
133
|
}
|
|
123
134
|
|
|
124
|
-
const state
|
|
135
|
+
const state = {
|
|
125
136
|
name,
|
|
126
137
|
params: mergedParams,
|
|
127
138
|
path: path ?? this.#deps.buildPath(name, params),
|
|
128
|
-
|
|
139
|
+
context: {},
|
|
140
|
+
...(!skipFreeze && { transition: DEFAULT_TRANSITION }),
|
|
141
|
+
} as State<P>;
|
|
129
142
|
|
|
130
143
|
if (meta) {
|
|
131
144
|
setStateMetaParams(state, meta as unknown as Params);
|