@real-router/core 0.80.0 → 0.82.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 +68 -55
- package/dist/cjs/Router-CCmcEi-S.js +2 -0
- package/dist/cjs/Router-CCmcEi-S.js.map +1 -0
- package/dist/cjs/Router.d.ts +7 -6
- package/dist/cjs/Router.d.ts.map +1 -1
- package/dist/cjs/Router.js +1 -1
- package/dist/cjs/Router.js.map +1 -1
- package/dist/cjs/api/getPluginApi.d.ts.map +1 -1
- package/dist/cjs/api/getPluginApi.js +1 -1
- package/dist/cjs/api/getPluginApi.js.map +1 -1
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -1
- package/dist/cjs/api/getRoutesApi.js +1 -1
- package/dist/cjs/api/getRoutesApi.js.map +1 -1
- package/dist/cjs/api.js +2 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/buildParamMeta-0m11OIJS.js +2 -0
- package/dist/cjs/buildParamMeta-0m11OIJS.js.map +1 -0
- package/dist/cjs/channels/defaults.js +2 -0
- package/dist/cjs/channels/defaults.js.map +1 -0
- package/dist/cjs/channels/guard.js +2 -0
- package/dist/cjs/channels/guard.js.map +1 -0
- package/dist/cjs/channels/modeGate.js +2 -0
- package/dist/cjs/channels/modeGate.js.map +1 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +1 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/createRouter.d.ts +1 -1
- package/dist/cjs/createRouter.d.ts.map +1 -1
- package/dist/cjs/createRouter.js.map +1 -1
- package/dist/cjs/engine/builder/buildTree.js +1 -1
- package/dist/cjs/engine/builder/buildTree.js.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +14 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -1
- package/dist/cjs/engine/path-matcher/types.d.ts +3 -0
- package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js.map +1 -1
- package/dist/cjs/engine/validation/routes.js +1 -1
- package/dist/cjs/engine/validation/routes.js.map +1 -1
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/helpers.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals.d.ts +30 -8
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/cjs/internals.js +1 -1
- package/dist/cjs/internals.js.map +1 -1
- package/dist/cjs/limits.js +2 -0
- package/dist/cjs/limits.js.map +1 -0
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +8 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +24 -7
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -1
- package/dist/cjs/pipeline/buildURL.js +2 -0
- package/dist/cjs/pipeline/buildURL.js.map +1 -0
- package/dist/cjs/pipeline/canonicalize.js +2 -0
- package/dist/cjs/pipeline/canonicalize.js.map +1 -0
- package/dist/cjs/pipeline/materialize.js +2 -0
- package/dist/cjs/pipeline/materialize.js.map +1 -0
- package/dist/cjs/pipeline/port.d.ts +147 -0
- package/dist/cjs/pipeline/port.d.ts.map +1 -0
- package/dist/cjs/routerFSM.js +1 -1
- package/dist/cjs/routerFSM.js.map +1 -1
- package/dist/cjs/transitionPath.js +1 -1
- package/dist/cjs/transitionPath.js.map +1 -1
- package/dist/cjs/types/RouterValidator.d.ts +14 -0
- package/dist/cjs/types/RouterValidator.d.ts.map +1 -1
- package/dist/cjs/types/api.d.ts +20 -11
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/base.d.ts +63 -4
- package/dist/cjs/types/base.d.ts.map +1 -1
- package/dist/cjs/types/constants.d.ts +1 -0
- package/dist/cjs/types/constants.d.ts.map +1 -1
- package/dist/cjs/types/router.d.ts +112 -23
- package/dist/cjs/types/router.d.ts.map +1 -1
- package/dist/cjs/types/tree-changed.d.ts +1 -1
- package/dist/cjs/types/tree-changed.d.ts.map +1 -1
- package/dist/cjs/types.d.ts +20 -11
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/event-emitter/EventEmitter.js.map +1 -0
- package/dist/cjs/utils/fsm/fsm.js.map +1 -0
- package/dist/cjs/utils/logger/RouterLogger.js.map +1 -0
- package/dist/cjs/utils/logger/constants.js.map +1 -0
- package/dist/cjs/validation.js +2 -1
- package/dist/cjs/validation.js.map +1 -1
- package/dist/cjs/wiring/wireNamespaces.js +1 -1
- package/dist/cjs/wiring/wireNamespaces.js.map +1 -1
- package/dist/esm/Router-DEpNuwBW.mjs +2 -0
- package/dist/esm/Router-DEpNuwBW.mjs.map +1 -0
- package/dist/esm/Router.d.mts +7 -6
- package/dist/esm/Router.d.mts.map +1 -1
- package/dist/esm/api/getPluginApi.d.mts.map +1 -1
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/buildParamMeta-D8td47fL.mjs +2 -0
- package/dist/esm/buildParamMeta-D8td47fL.mjs.map +1 -0
- package/dist/esm/constants.d.mts.map +1 -1
- package/dist/esm/createRouter.d.mts +1 -1
- package/dist/esm/createRouter.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +14 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/types.d.mts +3 -0
- package/dist/esm/engine/path-matcher/types.d.mts.map +1 -1
- package/dist/esm/index.d.mts +3 -3
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internals.d.mts +30 -8
- package/dist/esm/internals.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +8 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts +24 -7
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -1
- package/dist/esm/pipeline/port.d.mts +147 -0
- package/dist/esm/pipeline/port.d.mts.map +1 -0
- package/dist/esm/types/RouterValidator.d.mts +14 -0
- package/dist/esm/types/RouterValidator.d.mts.map +1 -1
- package/dist/esm/types/api.d.mts +20 -11
- package/dist/esm/types/api.d.mts.map +1 -1
- package/dist/esm/types/base.d.mts +63 -4
- package/dist/esm/types/base.d.mts.map +1 -1
- package/dist/esm/types/constants.d.mts +1 -0
- package/dist/esm/types/constants.d.mts.map +1 -1
- package/dist/esm/types/router.d.mts +112 -23
- package/dist/esm/types/router.d.mts.map +1 -1
- package/dist/esm/types/tree-changed.d.mts +1 -1
- package/dist/esm/types/tree-changed.d.mts.map +1 -1
- package/dist/esm/types.d.mts +20 -11
- package/dist/esm/types.d.mts.map +1 -1
- package/dist/esm/validation.mjs +1 -1
- package/dist/esm/validation.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/cjs/Router-DGUed2F4.js +0 -2
- package/dist/cjs/Router-DGUed2F4.js.map +0 -1
- package/dist/cjs/buildParamMeta-DniluAIV.js +0 -2
- package/dist/cjs/buildParamMeta-DniluAIV.js.map +0 -1
- package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +0 -1
- package/dist/cjs/foundation/fsm/fsm.js.map +0 -1
- package/dist/cjs/foundation/logger/RouterLogger.js.map +0 -1
- package/dist/cjs/foundation/logger/constants.js.map +0 -1
- package/dist/cjs/namespaces/StateNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/StateNamespace/helpers.js.map +0 -1
- package/dist/cjs/stateMetaStore.js +0 -2
- package/dist/cjs/stateMetaStore.js.map +0 -1
- package/dist/esm/Router-Bk6PfSE8.mjs +0 -2
- package/dist/esm/Router-Bk6PfSE8.mjs.map +0 -1
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs +0 -2
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +0 -1
- /package/dist/cjs/{foundation → utils}/event-emitter/EventEmitter.js +0 -0
- /package/dist/cjs/{foundation → utils}/fsm/fsm.js +0 -0
- /package/dist/cjs/{foundation → utils}/logger/RouterLogger.js +0 -0
- /package/dist/cjs/{foundation → utils}/logger/constants.js +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Params, SearchParams } from "../types/base.js";
|
|
2
|
+
//#region src/pipeline/port.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The pipeline's read-model over the routes layer — a narrow port, not a new
|
|
5
|
+
* layer. The module stays pure and mock-testable; the router implements the
|
|
6
|
+
* port at wiring time (`wiring/wireNamespaces.ts`).
|
|
7
|
+
*
|
|
8
|
+
* ⚠ **Both ends are deliberately interceptable, and all four phases closed
|
|
9
|
+
* without changing that.** The signatures describe the pipeline's contract, not
|
|
10
|
+
* where the implementation goes:
|
|
11
|
+
*
|
|
12
|
+
* - `resolveForward` is wired to the `forwardState` SEAM (`Router.ts:259-324`),
|
|
13
|
+
* i.e. the interceptable chain PLUS the centralized channel ASSERTION. It
|
|
14
|
+
* used to be a channel-SEPARATION wrapper (stage ②) that repaired a
|
|
15
|
+
* mis-channelled bag behind the producer's back; `ba0f6b18b` deleted the
|
|
16
|
+
* stage outright, and the seam now REFUSES such a bag instead
|
|
17
|
+
* (`assertChannelCorrect`, `src/channels/guard.ts`). Channel-correctness is
|
|
18
|
+
* the producer's contract, not something the port quietly restores. Calling
|
|
19
|
+
* the namespace primitive directly would switch off both the interceptors and
|
|
20
|
+
* that check.
|
|
21
|
+
* - `buildPath` is wired to `ctx.buildPath`, the interceptable, because the
|
|
22
|
+
* navigate path builds `state.path` through it (measured: one `navigate()`
|
|
23
|
+
* runs BOTH the `forwardState` and the `buildPath` interceptor). Reaching for
|
|
24
|
+
* the engine's `matcher.buildPath` here would silently stop running
|
|
25
|
+
* `persistent-params`' `buildPath` interceptor on the navigate path — a
|
|
26
|
+
* behaviour change, not a refactor. Phases 2 and 4 closed without un-wiring
|
|
27
|
+
* it: ⑤a stays on the interceptable.
|
|
28
|
+
*
|
|
29
|
+
* Accessors arrived with their consumers, as designed — `queryNames` with the
|
|
30
|
+
* channel guard, `admitsUndeclaredQuery` with the mode gate (#1575),
|
|
31
|
+
* `pathNames` and the two sinks with the diagnostics (#1579 / #1584). One
|
|
32
|
+
* member the design (RFC §4.5) listed never arrived at all: `encode`. The route
|
|
33
|
+
* codecs stayed with the entry points that own their direction (`buildPath`
|
|
34
|
+
* calls `config.encoders`, `matchPath` calls `config.decoders`), so the port
|
|
35
|
+
* never grew a stage for them — which is the case for adding a member only
|
|
36
|
+
* together with its caller, since one added early is dead weight nothing
|
|
37
|
+
* detects (knip has no issue type for unused members of an interface).
|
|
38
|
+
*/
|
|
39
|
+
interface RouteResolver {
|
|
40
|
+
/**
|
|
41
|
+
* Stage ① — resolve the `forwardTo` chain (layering the hops' defaults) and
|
|
42
|
+
* hand back channel-correct bags. Channel-correct by REFUSAL, not by repair:
|
|
43
|
+
* the seam asserts and throws (see the header). `search` flows THROUGH the
|
|
44
|
+
* interceptor zone, not past it: `search-schema` validates the query channel
|
|
45
|
+
* here on the URL→State direction.
|
|
46
|
+
*/
|
|
47
|
+
resolveForward: (name: string, params: Params, search?: SearchParams) => {
|
|
48
|
+
name: string;
|
|
49
|
+
params: Params;
|
|
50
|
+
search?: SearchParams | undefined;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Stage ③ input — the route's per-channel defaults. Split by field, never
|
|
54
|
+
* inferred: `defaultParams` owns the path channel, `defaultSearch` the query
|
|
55
|
+
* channel (RFC-4 M2 / #1548).
|
|
56
|
+
*
|
|
57
|
+
* Two accessors rather than one returning `{ params, search }`: `navigate` is
|
|
58
|
+
* the hot path, and the combined form allocated one throwaway object per
|
|
59
|
+
* navigation. That allocation is the deterministic reason; the bench arm it
|
|
60
|
+
* was measured on also carried a per-call `getInternals`, and the harness's
|
|
61
|
+
* own A/A spread is wider than the delta, so no percentage is attributable to
|
|
62
|
+
* this accessor alone.
|
|
63
|
+
*/
|
|
64
|
+
defaultParams: (name: string) => Params | undefined;
|
|
65
|
+
defaultSearch: (name: string) => SearchParams | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Stage ⑤a executor — builds the URL from already-merged channels. Kept in
|
|
68
|
+
* raw-channel form (not `Canonical`) so the port never has to know about the
|
|
69
|
+
* brand and stays mockable on its own; `buildURL` is the primitive that
|
|
70
|
+
* accepts nothing but a `Canonical`.
|
|
71
|
+
*/
|
|
72
|
+
buildPath: (name: string, params: Params, search: SearchParams) => string;
|
|
73
|
+
/**
|
|
74
|
+
* The route's declared `?query` names — the ONE registry that classifies and
|
|
75
|
+
* prints (#1556), so a key enters the query channel iff the build shows it.
|
|
76
|
+
* A name that also occupies a path slot (`/items/:id?id`) is absent here by
|
|
77
|
+
* construction: it is legitimately path-owned (#843 / #1549).
|
|
78
|
+
*/
|
|
79
|
+
queryNames: (name: string) => readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* The mode gate (#1575) — `true` exactly for `queryParamsMode: "loose"`, the
|
|
82
|
+
* one mode whose build prints undeclared query keys. Read as a boolean rather
|
|
83
|
+
* than leaking the mode itself into the pipeline: the pipeline's question is
|
|
84
|
+
* "may an undeclared key be canonical here?", not "which mode is this?".
|
|
85
|
+
*/
|
|
86
|
+
admitsUndeclaredQuery: () => boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The mode gate's opt-in diagnostic sink (#1575) — `undefined` unless
|
|
89
|
+
* `validation-plugin` is installed, so bare core drops silently and the
|
|
90
|
+
* pipeline pays nothing. Resolved per call, not captured: the plugin
|
|
91
|
+
* registers after wiring.
|
|
92
|
+
*
|
|
93
|
+
* ⚠ Like its sibling below, the absence has to be REAL: the router implements
|
|
94
|
+
* this as a GETTER returning `undefined` while `validator === null`, not as a
|
|
95
|
+
* closure that forwards into an optional-chained validator. A closure is
|
|
96
|
+
* always truthy, so the pipeline's `?.` read as taken and bare core still paid
|
|
97
|
+
* the `pathNames` existence lookup (#1584) once per dropped key with nothing
|
|
98
|
+
* to feed. The `| undefined` in the type is what lets the getter say so under
|
|
99
|
+
* `exactOptionalPropertyTypes`.
|
|
100
|
+
*
|
|
101
|
+
* Optional on the interface so a MOCK port (the property tests) stays a
|
|
102
|
+
* four-liner: the pipeline's contract is the drop, not the report.
|
|
103
|
+
*/
|
|
104
|
+
reportDroppedQueryKey?: ((routeName: string, key: string) => void) | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* The route's PATH slot names, or `undefined` when there is NO SUCH ROUTE —
|
|
107
|
+
* the other half of "is this key declared?". Needed only by the undeclared-key
|
|
108
|
+
* diagnostic (#1579), which asks whether a key is declared ANYWHERE;
|
|
109
|
+
* `queryNames` alone cannot answer that.
|
|
110
|
+
*
|
|
111
|
+
* ⚠ The `undefined` arm is load-bearing, not defensive (#1584). An empty array
|
|
112
|
+
* is what an EXISTING route with no path slots returns, so `[]` cannot say
|
|
113
|
+
* "no such route" — and the diagnostic, reading `[]` for both, reported every
|
|
114
|
+
* key in the caller's bag as "declared nowhere on route X" for a route X that
|
|
115
|
+
* has no declarations only because it has no existence. That blames the params
|
|
116
|
+
* for a typo in the ROUTE name, which is the most misleading direction
|
|
117
|
+
* available. The matcher already knows the difference (`getSegmentsByName`
|
|
118
|
+
* answers `undefined`); this member used to discard it.
|
|
119
|
+
*
|
|
120
|
+
* `queryNames` is deliberately NOT given the same arm: its three consumers —
|
|
121
|
+
* the diagnostic, the default merge and the mode gate — all want `[]` for a
|
|
122
|
+
* missing route, and only this one asks a question that presupposes existence.
|
|
123
|
+
*/
|
|
124
|
+
pathNames: (name: string) => readonly string[] | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Opt-in sink for a key the route declares NOWHERE (#1579 — the params half
|
|
127
|
+
* of #1553). Absent unless `validation-plugin` is installed, and the absence
|
|
128
|
+
* is what keeps the scan off the hot path: core checks one `undefined` and
|
|
129
|
+
* skips the walk entirely.
|
|
130
|
+
*
|
|
131
|
+
* ⚠ The absence has to be REAL — the router implements this member as a
|
|
132
|
+
* GETTER returning `undefined` while `validator === null`, not as a closure
|
|
133
|
+
* that forwards into an optional-chained validator. A closure is always
|
|
134
|
+
* truthy, so the gate would read as taken and bare core would walk the
|
|
135
|
+
* caller's bag on every commit; the `| undefined` in the type is what lets
|
|
136
|
+
* the getter say so under `exactOptionalPropertyTypes`.
|
|
137
|
+
*
|
|
138
|
+
* A diagnostic, never a gate — core keeps the key in `state.params` as
|
|
139
|
+
* app-level data (wiki `Route.md`). Dropping it was measured and rejected: it
|
|
140
|
+
* retires a shipped capability, and "declared nowhere" cannot tell a typo from
|
|
141
|
+
* `navigate("users", { id })` on a parent whose CHILD declares `:id`.
|
|
142
|
+
*/
|
|
143
|
+
reportUndeclaredParamKey?: ((routeName: string, key: string) => void) | undefined;
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
export { RouteResolver };
|
|
147
|
+
//# sourceMappingURL=port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port.d.ts","names":[],"sources":["../../../src/pipeline/port.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAwCiB;;;;;;;;EAQf,iBACE,cACA,QAAQ,QACR,SAAS;IAET;IACA,QAAQ;IAIR,SAAS;;;;;;;;;;;;;;EAeX,gBAAgB,iBAAiB;EACjC,gBAAgB,iBAAiB;;;;;;;EAQjC,YAAY,cAAc,QAAQ,QAAQ,QAAQ;;;;;;;EAQlD,aAAa;;;;;;;EAQb;;;;;;;;;;;;;;;;;;EAmBA,0BACI,mBAAmB;;;;;;;;;;;;;;;;;;;;EAqBvB,YAAY;;;;;;;;;;;;;;;;;;;EAoBZ,6BACI,mBAAmB"}
|
package/dist/cjs/routerFSM.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./
|
|
1
|
+
const e=require("./utils/fsm/fsm.js"),t={IDLE:`IDLE`,STARTING:`STARTING`,READY:`READY`,TRANSITION_STARTED:`TRANSITION_STARTED`,LEAVE_APPROVED:`LEAVE_APPROVED`,DISPOSED:`DISPOSED`},n={START:`START`,STARTED:`STARTED`,NAVIGATE:`NAVIGATE`,LEAVE_APPROVE:`LEAVE_APPROVE`,COMPLETE:`COMPLETE`,FAIL:`FAIL`,CANCEL:`CANCEL`,STOP:`STOP`,DISPOSE:`DISPOSE`},r={initial:t.IDLE,context:null,transitions:{[t.IDLE]:{[n.START]:t.STARTING,[n.DISPOSE]:t.DISPOSED},[t.STARTING]:{[n.STARTED]:t.READY,[n.FAIL]:t.IDLE,[n.STOP]:t.IDLE,[n.DISPOSE]:t.DISPOSED},[t.READY]:{[n.NAVIGATE]:t.TRANSITION_STARTED,[n.FAIL]:t.READY,[n.STOP]:t.IDLE,[n.DISPOSE]:t.DISPOSED},[t.TRANSITION_STARTED]:{[n.NAVIGATE]:t.TRANSITION_STARTED,[n.LEAVE_APPROVE]:t.LEAVE_APPROVED,[n.CANCEL]:t.READY,[n.FAIL]:t.READY,[n.DISPOSE]:t.DISPOSED},[t.LEAVE_APPROVED]:{[n.NAVIGATE]:t.TRANSITION_STARTED,[n.COMPLETE]:t.READY,[n.CANCEL]:t.READY,[n.FAIL]:t.READY,[n.DISPOSE]:t.DISPOSED},[t.DISPOSED]:{}}};function i(){return new e.FSM(r)}exports.createRouterFSM=i,exports.routerEvents=n,exports.routerStates=t;
|
|
2
2
|
//# sourceMappingURL=routerFSM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routerFSM.js","names":["FSM"],"sources":["../../src/routerFSM.ts"],"sourcesContent":["// packages/core/src/routerFSM.ts\n\nimport { FSM } from \"./
|
|
1
|
+
{"version":3,"file":"routerFSM.js","names":["FSM"],"sources":["../../src/routerFSM.ts"],"sourcesContent":["// packages/core/src/routerFSM.ts\n\nimport { FSM } from \"./utils/fsm\";\n\nimport type { NavigationOptions, State } from \"./types\";\nimport type { FSMConfig } from \"./utils/fsm\";\n\n/**\n * Router FSM states.\n *\n * - IDLE: Router not started or stopped\n * - STARTING: Router is initializing\n * - READY: Router is ready for navigation\n * - TRANSITION_STARTED: Navigation in progress (before deactivation guards)\n * - LEAVE_APPROVED: Deactivation guards passed, activation guards pending\n * - DISPOSED: Router has been disposed (R2+)\n */\nexport const routerStates = {\n IDLE: \"IDLE\",\n STARTING: \"STARTING\",\n READY: \"READY\",\n TRANSITION_STARTED: \"TRANSITION_STARTED\",\n LEAVE_APPROVED: \"LEAVE_APPROVED\",\n DISPOSED: \"DISPOSED\",\n} as const;\n\nexport type RouterState = (typeof routerStates)[keyof typeof routerStates];\n\n/**\n * Router FSM events.\n *\n * - START: Begin router initialization\n * - STARTED: Router initialization complete\n * - NAVIGATE: Begin navigation\n * - COMPLETE: Navigation completed successfully\n * - FAIL: Navigation or initialization failed\n * - CANCEL: Navigation cancelled\n * - STOP: Stop router\n * - DISPOSE: Dispose router (R2+)\n */\nexport const routerEvents = {\n START: \"START\",\n STARTED: \"STARTED\",\n NAVIGATE: \"NAVIGATE\",\n LEAVE_APPROVE: \"LEAVE_APPROVE\",\n COMPLETE: \"COMPLETE\",\n FAIL: \"FAIL\",\n CANCEL: \"CANCEL\",\n STOP: \"STOP\",\n DISPOSE: \"DISPOSE\",\n} as const;\n\nexport type RouterEvent = (typeof routerEvents)[keyof typeof routerEvents];\n\n/**\n * Per-event payloads for the router FSM (#1169 commit-gate). The three hot\n * navigation transitions carry their transition states so the FSM action\n * dispatched by `send()` emits the matching transition event — i.e. events are\n * literal consequences of FSM transitions (no `forceState` + manual emit). See\n * `EventBusNamespace.#setupFSMActions`.\n */\nexport interface RouterPayloads {\n NAVIGATE: { toState: State; fromState?: State | undefined };\n LEAVE_APPROVE: { toState: State; fromState?: State | undefined };\n COMPLETE: {\n toState: State;\n fromState?: State | undefined;\n opts?: NavigationOptions | undefined;\n };\n}\n\n/**\n * Router FSM configuration.\n *\n * Transitions:\n * - IDLE → STARTING (START), DISPOSED (DISPOSE)\n * - STARTING → READY (STARTED), IDLE (FAIL, STOP), DISPOSED (DISPOSE)\n * - READY → TRANSITION_STARTED (NAVIGATE), READY (FAIL, self-loop for early validation errors), IDLE (STOP), DISPOSED (DISPOSE)\n * - TRANSITION_STARTED → LEAVE_APPROVED (LEAVE_APPROVE), TRANSITION_STARTED (NAVIGATE, self-loop), READY (CANCEL, FAIL), DISPOSED (DISPOSE)\n * - LEAVE_APPROVED → READY (COMPLETE, CANCEL, FAIL), TRANSITION_STARTED (NAVIGATE), DISPOSED (DISPOSE)\n * - DISPOSED → (no transitions)\n *\n * DISPOSE is wired from every non-DISPOSED state so `router.dispose()` always\n * settles the FSM at DISPOSED. The facade orchestrates cleanup through IDLE\n * for healthy flows; the direct transitions guarantee the FSM is not left\n * stuck if cleanup is skipped (e.g. dispose mid-STARTING when the start\n * pipeline threw before STARTED/FAIL).\n */\nconst routerFSMConfig: FSMConfig<RouterState, RouterEvent, null> = {\n initial: routerStates.IDLE,\n context: null,\n transitions: {\n [routerStates.IDLE]: {\n [routerEvents.START]: routerStates.STARTING,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.STARTING]: {\n [routerEvents.STARTED]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.IDLE,\n [routerEvents.STOP]: routerStates.IDLE,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.READY]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.STOP]: routerStates.IDLE,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.TRANSITION_STARTED]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.LEAVE_APPROVE]: routerStates.LEAVE_APPROVED,\n [routerEvents.CANCEL]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.LEAVE_APPROVED]: {\n [routerEvents.NAVIGATE]: routerStates.TRANSITION_STARTED,\n [routerEvents.COMPLETE]: routerStates.READY,\n [routerEvents.CANCEL]: routerStates.READY,\n [routerEvents.FAIL]: routerStates.READY,\n [routerEvents.DISPOSE]: routerStates.DISPOSED,\n },\n [routerStates.DISPOSED]: {},\n },\n};\n\n/**\n * Factory function to create a router FSM instance.\n *\n * @returns FSM instance with initial state \"IDLE\"\n */\nexport function createRouterFSM(): FSM<\n RouterState,\n RouterEvent,\n null,\n RouterPayloads\n> {\n return new FSM<RouterState, RouterEvent, null, RouterPayloads>(\n routerFSMConfig,\n );\n}\n"],"mappings":"sCAiBa,EAAe,CAC1B,KAAM,OACN,SAAU,WACV,MAAO,QACP,mBAAoB,qBACpB,eAAgB,iBAChB,SAAU,UACZ,EAgBa,EAAe,CAC1B,MAAO,QACP,QAAS,UACT,SAAU,WACV,cAAe,gBACf,SAAU,WACV,KAAM,OACN,OAAQ,SACR,KAAM,OACN,QAAS,SACX,EAsCM,EAA6D,CACjE,QAAS,EAAa,KACtB,QAAS,KACT,YAAa,EACV,EAAa,MAAO,EAClB,EAAa,OAAQ,EAAa,UAClC,EAAa,SAAU,EAAa,QACvC,GACC,EAAa,UAAW,EACtB,EAAa,SAAU,EAAa,OACpC,EAAa,MAAO,EAAa,MACjC,EAAa,MAAO,EAAa,MACjC,EAAa,SAAU,EAAa,QACvC,GACC,EAAa,OAAQ,EACnB,EAAa,UAAW,EAAa,oBACrC,EAAa,MAAO,EAAa,OACjC,EAAa,MAAO,EAAa,MACjC,EAAa,SAAU,EAAa,QACvC,GACC,EAAa,oBAAqB,EAChC,EAAa,UAAW,EAAa,oBACrC,EAAa,eAAgB,EAAa,gBAC1C,EAAa,QAAS,EAAa,OACnC,EAAa,MAAO,EAAa,OACjC,EAAa,SAAU,EAAa,QACvC,GACC,EAAa,gBAAiB,EAC5B,EAAa,UAAW,EAAa,oBACrC,EAAa,UAAW,EAAa,OACrC,EAAa,QAAS,EAAa,OACnC,EAAa,MAAO,EAAa,OACjC,EAAa,SAAU,EAAa,QACvC,GACC,EAAa,UAAW,CAAC,CAC5B,CACF,EAOA,SAAgB,GAKd,CACA,OAAO,IAAIA,EAAAA,IACT,CACF,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=
|
|
1
|
+
const e=[];Object.freeze(e);function t(e){let t=e.split(`.`),n=t.length,r=[t[0]],i=t[0].length;for(let a=1;a<n-1;a++)i+=1+t[a].length,r.push(e.slice(0,i));return r.push(e),r}const n=new Set([`string`,`number`,`boolean`]);function r(e){return n.has(typeof e)}function i(e,t,n,i){let a=t[e];if(!a||typeof a!=`object`)return!0;for(let e of Object.keys(a)){let t=n.params[e],a=i.params[e];if(r(t)&&r(a)&&String(t)!==String(a))return!1}return!0}function a(e,t,n,r,a,o){for(let s=0;s<o;s++){let o=r[s];if(o!==a[s]||!i(o,e,t,n))return s}return o}const o=new Map;function s(e){let t=o.get(e);if(t)return t;let n=c(e);return Object.freeze(n),o.set(e,n),n}function c(e){if(!e)return[``];let n=e.indexOf(`.`);if(n===-1)return[e];let r=e.indexOf(`.`,n+1);if(r===-1)return[e.slice(0,n),e];let i=e.indexOf(`.`,r+1);return i===-1?[e.slice(0,n),e.slice(0,r),e]:e.indexOf(`.`,i+1)===-1?[e.slice(0,n),e.slice(0,r),e.slice(0,i),e]:t(e)}let l,u,d=null,f,p,m=null;function h(t,n,r){if(!n)return{intersection:``,toActivate:s(t.name),toDeactivate:e};let i=r(t.name),o=r(n.name);if(!i&&!o)return{intersection:``,toActivate:s(t.name),toDeactivate:s(n.name)};let c=s(t.name),l=s(n.name),u=Math.min(l.length,c.length),d=a(i??o,t,n,c,l,u),f;if(d>=l.length)f=e;else if(d===0&&l.length===1)f=l;else{f=[];for(let e=l.length-1;e>=d;e--)f.push(l[e])}let p=d===0?c:c.slice(d);return{intersection:d>0?l[d-1]:``,toDeactivate:f,toActivate:p}}function g(e,t,n){if(d!==null&&e===l&&t===u)return d;if(m!==null&&e===f&&t===p)return m;let r=h(e,t,n);return f=l,p=u,m=d,l=e,u=t,d=r,r}exports.getTransitionPath=g,exports.nameToIDs=s;
|
|
2
2
|
//# sourceMappingURL=transitionPath.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transitionPath.js","names":["getStateMetaParams"],"sources":["../../src/transitionPath.ts"],"sourcesContent":["// packages/core/src/transitionPath.ts\n\nimport { getStateMetaParams } from \"./stateMetaStore\";\n\nimport type { State } from \"./types\";\n\n/**\n * Parameters extracted from a route segment.\n * Maps parameter names to their string values.\n */\ntype PrimitiveParam = string | number | boolean;\n\n/**\n * Represents a transition path between two router states.\n * Contains information about which route segments need to be activated/deactivated.\n */\nexport interface TransitionPath {\n /** The common ancestor route segment where paths diverge */\n intersection: string;\n /** Route segments that need to be deactivated (in reverse order) */\n toDeactivate: string[];\n /** Route segments that need to be activated (in order) */\n toActivate: string[];\n}\n\n// Constants for better maintainability\nconst ROUTE_SEGMENT_SEPARATOR = \".\";\nconst EMPTY_INTERSECTION = \"\";\nconst DEFAULT_ROUTE_NAME = \"\";\nconst FROZEN_EMPTY_ARRAY: string[] = [];\n\nObject.freeze(FROZEN_EMPTY_ARRAY);\n\n/**\n * Handles conversion of route names with many segments (5+).\n * Internal helper for nameToIDs function.\n *\n * Uses optimized hybrid approach: split to get segments, then slice original\n * string to build cumulative paths. This approach is 65-81% faster than\n * string concatenation for typical cases (5-10 segments).\n *\n * @param name - Route name with 5 or more segments\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed\n * @internal\n */\nfunction nameToIDsGeneral(name: string): string[] {\n // We know there are at least 5 segments at this point (after fast paths)\n const segments = name.split(ROUTE_SEGMENT_SEPARATOR);\n const segmentCount = segments.length;\n\n // First segment is always just itself\n const ids: string[] = [segments[0]];\n\n // Calculate cumulative lengths and slice from original string\n // This avoids repeated string concatenation (O(k²) → O(k))\n let cumulativeLength = segments[0].length;\n\n for (let i = 1; i < segmentCount - 1; i++) {\n cumulativeLength += 1 + segments[i].length; // +1 for dot separator\n ids.push(name.slice(0, cumulativeLength));\n }\n\n // Last segment is always the full route name\n ids.push(name);\n\n return ids;\n}\n\nconst PRIMITIVE_TYPES: ReadonlySet<string> = new Set([\n \"string\",\n \"number\",\n \"boolean\",\n]);\n\nfunction isPrimitive(value: unknown): value is PrimitiveParam {\n return PRIMITIVE_TYPES.has(typeof value);\n}\n\n/**\n * Compares segment parameters between two states without creating intermediate objects.\n * Returns true if all primitive params for the given segment are equal in both states.\n */\nfunction segmentParamsEqual(\n name: string,\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n): boolean {\n const keys = toMetaParams[name];\n\n if (!keys || typeof keys !== \"object\") {\n return true;\n }\n\n for (const key of Object.keys(keys)) {\n const toVal = toState.params[key];\n const fromVal = fromState.params[key];\n\n if (\n isPrimitive(toVal) &&\n isPrimitive(fromVal) &&\n String(toVal) !== String(fromVal)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Finds the point where two state paths diverge based on segments and parameters.\n * Compares both segment names and their parameters to find the first difference.\n *\n * @param toMetaParams - Cached meta.params from toState (avoids per-segment WeakMap lookup)\n * @param toState - Target state\n * @param fromState - Source state\n * @param toStateIds - Segment IDs for target state\n * @param fromStateIds - Segment IDs for source state\n * @param maxI - Maximum index to check (minimum of both arrays)\n * @returns Index of first difference, or maxI if all checked segments match\n */\nfunction pointOfDifference(\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n toStateIds: string[],\n fromStateIds: string[],\n maxI: number,\n): number {\n for (let i = 0; i < maxI; i++) {\n const toSegment = toStateIds[i];\n const fromSegment = fromStateIds[i];\n\n // Different segment names - immediate difference\n if (toSegment !== fromSegment) {\n return i;\n }\n\n if (!segmentParamsEqual(toSegment, toMetaParams, toState, fromState)) {\n return i;\n }\n }\n\n return maxI;\n}\n\n/**\n * Converts a route name to an array of hierarchical segment identifiers.\n * Each segment ID includes all parent segments in the path.\n *\n * @param name - Route name in dot notation (e.g., 'users.profile.edit')\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed depth\n *\n * @example\n * // Simple route\n * nameToIDs('users');\n * // Returns: ['users']\n *\n * @example\n * // Nested route\n * nameToIDs('users.profile.edit');\n * // Returns: ['users', 'users.profile', 'users.profile.edit']\n *\n * @example\n * // Empty string (root route)\n * nameToIDs('');\n * // Returns: ['']\n *\n * @remarks\n * Input parameter is NOT validated in this function for performance reasons.\n * Validation significantly slows down nameToIDs execution.\n * The input should be validated by the function/method that calls nameToIDs.\n */\n// Module-global cache (shared across all router instances): bounded in practice by\n// the app's route-name vocabulary, which is stable across cloneRouter() requests, so\n// it does not grow per request. Intentionally NOT cleared on dispose() — it is not\n// per-router, so one router's teardown must not evict entries other routers rely on.\nconst nameToIDsCache = new Map<string, string[]>();\n\nexport function nameToIDs(name: string): string[] {\n const cached = nameToIDsCache.get(name);\n\n // Stryker disable next-line BlockStatement: equivalent — dropping the cache-hit early return recomputes the identical frozen id chain (the cache is a perf optimization, not a correctness gate).\n if (cached) {\n return cached;\n }\n\n const result = computeNameToIDs(name);\n\n Object.freeze(result);\n nameToIDsCache.set(name, result);\n\n return result;\n}\n\nfunction computeNameToIDs(name: string): string[] {\n if (!name) {\n return [DEFAULT_ROUTE_NAME];\n }\n\n const firstDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR);\n\n if (firstDot === -1) {\n return [name];\n }\n\n const secondDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, firstDot + 1);\n\n if (secondDot === -1) {\n return [name.slice(0, firstDot), name];\n }\n\n const thirdDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, secondDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 3-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (thirdDot === -1) {\n return [name.slice(0, firstDot), name.slice(0, secondDot), name];\n }\n\n // Stryker disable next-line ArithmeticOperator: equivalent — `thirdDot - 1` makes fourthDot non-(-1), routing 5+ segment names through nameToIDsGeneral, which yields the identical id chain.\n const fourthDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, thirdDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 4-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (fourthDot === -1) {\n return [\n name.slice(0, firstDot),\n name.slice(0, secondDot),\n name.slice(0, thirdDot),\n name,\n ];\n }\n\n return nameToIDsGeneral(name);\n}\n\n/**\n * Calculates the transition path between two router states.\n * Determines which route segments need to be deactivated and activated\n * to transition from one state to another.\n *\n * @param toState - Target state to transition to\n * @param fromState - Current state to transition from (optional)\n * @returns Transition path with intersection and segments to activate/deactivate\n *\n * @throws {TypeError} When toState is null or undefined\n * @throws {TypeError} When toState is not an object\n * @throws {TypeError} When toState.name is missing or not a string\n * @throws {TypeError} When toState.params is missing or not an object\n * @throws {TypeError} When toState.path is missing or not a string\n * @throws {TypeError} When toState.name contains invalid route format:\n * - Contains only whitespace (e.g., \" \")\n * - Has consecutive dots (e.g., \"users..profile\")\n * - Has leading/trailing dots (e.g., \".users\" or \"users.\")\n * - Segments don't match pattern [a-zA-Z_][a-zA-Z0-9_-]* (e.g., \"users.123\")\n * - Contains spaces or special characters (e.g., \"users profile\")\n * - Exceeds maximum length (8192 characters)\n * @throws {TypeError} When fromState is provided and has any of the validation errors listed above for toState\n *\n * @example\n * // ✅ Valid calls\n * getTransitionPath({ name: 'users.profile', params: {}, path: '/users/profile' });\n * getTransitionPath(toState, fromState);\n * getTransitionPath({ name: '', params: {}, path: '/' }); // root route\n *\n * @example\n * // ❌ Invalid calls that throw TypeError\n * getTransitionPath(null); // toState is null\n * getTransitionPath(undefined); // toState is undefined\n * getTransitionPath({}); // missing required fields\n * getTransitionPath({ name: 123, params: {}, path: '/' }); // name not a string\n * getTransitionPath({ name: 'home', path: '/' }); // missing params\n * getTransitionPath({ name: 'users..profile', params: {}, path: '/' }); // consecutive dots\n * getTransitionPath({ name: '.users', params: {}, path: '/' }); // leading dot\n * getTransitionPath({ name: 'users.', params: {}, path: '/' }); // trailing dot\n * getTransitionPath({ name: 'users profile', params: {}, path: '/' }); // contains space\n * getTransitionPath({ name: 'users.123', params: {}, path: '/' }); // segment starts with number\n * getTransitionPath(validToState, { name: 'invalid..route', params: {}, path: '/' }); // fromState invalid\n *\n * @example\n * // Full activation (no fromState)\n * getTransitionPath(makeState('users.profile'));\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['users', 'users.profile'],\n * // toDeactivate: []\n * // }\n *\n * @example\n * // Partial transition with common ancestor\n * getTransitionPath(\n * makeState('users.profile'),\n * makeState('users.list')\n * );\n * // Returns: {\n * // intersection: 'users',\n * // toActivate: ['users.profile'],\n * // toDeactivate: ['users.list']\n * // }\n *\n * @example\n * // Complete route change\n * getTransitionPath(\n * makeState('admin.dashboard'),\n * makeState('users.profile')\n * );\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['admin', 'admin.dashboard'],\n * // toDeactivate: ['users.profile', 'users']\n * // }\n */\n// Single-entry cache: shouldUpdateNode calls getTransitionPath N times per\n// navigation with the same state objects (once per subscribed node).\n// Cache by reference eliminates N-1 redundant computations.\n// Module-global (≤2 State refs); not cleared on dispose — negligible, not per-router.\nlet cached1To: State | undefined;\nlet cached1From: State | undefined;\nlet cached1Result: TransitionPath | null = null;\n\nlet cached2To: State | undefined;\nlet cached2From: State | undefined;\nlet cached2Result: TransitionPath | null = null;\n\nfunction computeTransitionPath(\n toState: State,\n fromState?: State,\n): TransitionPath {\n // ===== FAST PATH 1: Initial navigation (no fromState) =====\n // This is the best performing case in benchmarks (5M ops/sec)\n if (!fromState) {\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: FROZEN_EMPTY_ARRAY,\n };\n }\n\n // ===== FAST PATH 3: Missing meta requires full reload =====\n // Single WeakMap lookup per state, reused in pointOfDifference/segmentParamsEqual\n const toMetaParams = getStateMetaParams(toState);\n const fromMetaParams = getStateMetaParams(fromState);\n\n if (!toMetaParams && !fromMetaParams) {\n // FAST PATH 3 (both states meta-less). Consumers that land here read the\n // result order-INSENSITIVELY, so the from-chain is returned as-is\n // (root→leaf, no reverse needed):\n // • `shouldUpdateNode` reads `toDeactivate` by MEMBERSHIP (`.includes`).\n // • Externally-supplied meta-less states (e.g. a plugin passing a raw\n // `{name, params, path}` to `navigateToState`) land here. Since #1170,\n // `navigateToState` carries the source's WeakMap meta across its writable\n // shell, so start()/popstate states are NOT meta-less. A `replace()`\n // survivor stays meta-less but is benign: the next transition's `toState`\n // always carries meta (buildNavigateState), so this both-meta-less path\n // is not reached from it.\n // (`canNavigateTo` no longer reaches this path — since #970 it builds its\n // toState WITH meta, mirroring buildNavigateState.)\n // The navigate pipeline always carries meta (buildNavigateState) → STANDARD\n // PATH below, which trims the shared ancestor and reverses correctly.\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: nameToIDs(fromState.name),\n };\n }\n\n // ===== STANDARD PATH: Routes with parameters =====\n const toStateIds = nameToIDs(toState.name);\n const fromStateIds = nameToIDs(fromState.name);\n // Stryker disable next-line MethodExpression: equivalent — Math.max reads one index past the shorter id array; that slot is undefined, so the `toSegment !== fromSegment` check in pointOfDifference returns the same divergence index Math.min would stop at.\n const maxI = Math.min(fromStateIds.length, toStateIds.length);\n\n const i = pointOfDifference(\n (toMetaParams ?? fromMetaParams) as Record<string, unknown>,\n toState,\n fromState,\n toStateIds,\n fromStateIds,\n maxI,\n );\n\n // Optimization: Build deactivation list in reverse order directly\n // instead of slice(i).toReversed() which creates 2 arrays\n let toDeactivate: string[];\n\n if (i >= fromStateIds.length) {\n toDeactivate = FROZEN_EMPTY_ARRAY;\n } else if (i === 0 && fromStateIds.length === 1) {\n // Single-segment route: reversed = original, reuse cached frozen array\n toDeactivate = fromStateIds;\n } else {\n toDeactivate = [];\n\n for (let j = fromStateIds.length - 1; j >= i; j--) {\n toDeactivate.push(fromStateIds[j]);\n }\n }\n\n // Build activation list — reuse cached frozen array when using full list\n const toActivate = i === 0 ? toStateIds : toStateIds.slice(i);\n\n // Determine intersection point (common ancestor)\n const intersection = i > 0 ? fromStateIds[i - 1] : EMPTY_INTERSECTION;\n\n return {\n intersection,\n toDeactivate,\n toActivate,\n };\n}\n\nexport function getTransitionPath(\n toState: State,\n fromState?: State,\n): TransitionPath {\n // Stryker disable BlockStatement: equivalent — both cache short-circuits below; emptying either early-return recomputes the identical TransitionPath (computeTransitionPath is deterministic for the same to/from states) and re-caches it. Restored right after.\n if (\n cached1Result !== null &&\n toState === cached1To &&\n fromState === cached1From\n ) {\n return cached1Result;\n }\n\n if (\n cached2Result !== null &&\n toState === cached2To &&\n fromState === cached2From\n ) {\n return cached2Result;\n }\n // Stryker restore BlockStatement\n\n const result = computeTransitionPath(toState, fromState);\n\n cached2To = cached1To;\n cached2From = cached1From;\n cached2Result = cached1Result;\n\n cached1To = toState;\n cached1From = fromState;\n cached1Result = result;\n\n return result;\n}\n"],"mappings":"uCA6BM,EAA+B,CAAC,EAEtC,OAAO,OAAO,CAAkB,EAehC,SAAS,EAAiB,EAAwB,CAEhD,IAAM,EAAW,EAAK,MAAM,GAAuB,EAC7C,EAAe,EAAS,OAGxB,EAAgB,CAAC,EAAS,EAAE,EAI9B,EAAmB,EAAS,EAAE,CAAC,OAEnC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAe,EAAG,IACpC,GAAoB,EAAI,EAAS,EAAE,CAAC,OACpC,EAAI,KAAK,EAAK,MAAM,EAAG,CAAgB,CAAC,EAM1C,OAFA,EAAI,KAAK,CAAI,EAEN,CACT,CAEA,MAAM,EAAuC,IAAI,IAAI,CACnD,SACA,SACA,SACF,CAAC,EAED,SAAS,EAAY,EAAyC,CAC5D,OAAO,EAAgB,IAAI,OAAO,CAAK,CACzC,CAMA,SAAS,EACP,EACA,EACA,EACA,EACS,CACT,IAAM,EAAO,EAAa,GAE1B,GAAI,CAAC,GAAQ,OAAO,GAAS,SAC3B,MAAO,GAGT,IAAK,IAAM,KAAO,OAAO,KAAK,CAAI,EAAG,CACnC,IAAM,EAAQ,EAAQ,OAAO,GACvB,EAAU,EAAU,OAAO,GAEjC,GACE,EAAY,CAAK,GACjB,EAAY,CAAO,GACnB,OAAO,CAAK,IAAM,OAAO,CAAO,EAEhC,MAAO,EAEX,CAEA,MAAO,EACT,CAcA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACQ,CACR,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,IAAK,CAC7B,IAAM,EAAY,EAAW,GAQ7B,GAJI,IAHgB,EAAa,IAO7B,CAAC,EAAmB,EAAW,EAAc,EAAS,CAAS,EACjE,OAAO,CAEX,CAEA,OAAO,CACT,CAkCA,MAAM,EAAiB,IAAI,IAE3B,SAAgB,EAAU,EAAwB,CAChD,IAAM,EAAS,EAAe,IAAI,CAAI,EAGtC,GAAI,EACF,OAAO,EAGT,IAAM,EAAS,EAAiB,CAAI,EAKpC,OAHA,OAAO,OAAO,CAAM,EACpB,EAAe,IAAI,EAAM,CAAM,EAExB,CACT,CAEA,SAAS,EAAiB,EAAwB,CAChD,GAAI,CAAC,EACH,MAAO,CAAC,EAAkB,EAG5B,IAAM,EAAW,EAAK,QAAQ,GAAuB,EAErD,GAAI,IAAa,GACf,MAAO,CAAC,CAAI,EAGd,IAAM,EAAY,EAAK,QAAQ,IAAyB,EAAW,CAAC,EAEpE,GAAI,IAAc,GAChB,MAAO,CAAC,EAAK,MAAM,EAAG,CAAQ,EAAG,CAAI,EAGvC,IAAM,EAAW,EAAK,QAAQ,IAAyB,EAAY,CAAC,EAoBpE,OAjBI,IAAa,GACR,CAAC,EAAK,MAAM,EAAG,CAAQ,EAAG,EAAK,MAAM,EAAG,CAAS,EAAG,CAAI,EAI/C,EAAK,QAAQ,IAAyB,EAAW,CAGvD,IAAM,GACT,CACL,EAAK,MAAM,EAAG,CAAQ,EACtB,EAAK,MAAM,EAAG,CAAS,EACvB,EAAK,MAAM,EAAG,CAAQ,EACtB,CACF,EAGK,EAAiB,CAAI,CAC9B,CAkFA,IAAI,EACA,EACA,EAAuC,KAEvC,EACA,EACA,EAAuC,KAE3C,SAAS,EACP,EACA,EACgB,CAGhB,GAAI,CAAC,EACH,MAAO,CACL,aAAc,GACd,WAAY,EAAU,EAAQ,IAAI,EAClC,aAAc,CAChB,EAKF,IAAM,EAAeA,EAAAA,mBAAmB,CAAO,EACzC,EAAiBA,EAAAA,mBAAmB,CAAS,EAEnD,GAAI,CAAC,GAAgB,CAAC,EAgBpB,MAAO,CACL,aAAc,GACd,WAAY,EAAU,EAAQ,IAAI,EAClC,aAAc,EAAU,EAAU,IAAI,CACxC,EAIF,IAAM,EAAa,EAAU,EAAQ,IAAI,EACnC,EAAe,EAAU,EAAU,IAAI,EAEvC,EAAO,KAAK,IAAI,EAAa,OAAQ,EAAW,MAAM,EAEtD,EAAI,EACP,GAAgB,EACjB,EACA,EACA,EACA,EACA,CACF,EAII,EAEJ,GAAI,GAAK,EAAa,OACpB,EAAe,OACV,GAAI,IAAM,GAAK,EAAa,SAAW,EAE5C,EAAe,MACV,CACL,EAAe,CAAC,EAEhB,IAAK,IAAI,EAAI,EAAa,OAAS,EAAG,GAAK,EAAG,IAC5C,EAAa,KAAK,EAAa,EAAE,CAErC,CAGA,IAAM,EAAa,IAAM,EAAI,EAAa,EAAW,MAAM,CAAC,EAK5D,MAAO,CACL,aAHmB,EAAI,EAAI,EAAa,EAAI,GAAK,GAIjD,eACA,YACF,CACF,CAEA,SAAgB,EACd,EACA,EACgB,CAEhB,GACE,IAAkB,MAClB,IAAY,GACZ,IAAc,EAEd,OAAO,EAGT,GACE,IAAkB,MAClB,IAAY,GACZ,IAAc,EAEd,OAAO,EAIT,IAAM,EAAS,EAAsB,EAAS,CAAS,EAUvD,MARA,GAAY,EACZ,EAAc,EACd,EAAgB,EAEhB,EAAY,EACZ,EAAc,EACd,EAAgB,EAET,CACT"}
|
|
1
|
+
{"version":3,"file":"transitionPath.js","names":[],"sources":["../../src/transitionPath.ts"],"sourcesContent":["// packages/core/src/transitionPath.ts\n\nimport type { State } from \"./types\";\n\n/**\n * Per-segment param-source map for a route name — `{ segment: { param: \"url\" |\n * \"query\" } }`. Resolved from the live matcher via `RoutesNamespace.getMetaForState`\n * (RFC-4 M2 / #1548: this replaced the removed `stateMetaStore` WeakMap — ownership\n * is now read by `state.name`, not carried per-State object). `undefined` when the\n * name is not in the tree (a CRUD-removed route → FAST PATH 3 fallback).\n */\nexport type RouteMetaLookup = (\n name: string,\n) => Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n/**\n * Parameters extracted from a route segment.\n * Maps parameter names to their string values.\n */\ntype PrimitiveParam = string | number | boolean;\n\n/**\n * Represents a transition path between two router states.\n * Contains information about which route segments need to be activated/deactivated.\n */\nexport interface TransitionPath {\n /** The common ancestor route segment where paths diverge */\n intersection: string;\n /** Route segments that need to be deactivated (in reverse order) */\n toDeactivate: string[];\n /** Route segments that need to be activated (in order) */\n toActivate: string[];\n}\n\n// Constants for better maintainability\nconst ROUTE_SEGMENT_SEPARATOR = \".\";\nconst EMPTY_INTERSECTION = \"\";\nconst DEFAULT_ROUTE_NAME = \"\";\nconst FROZEN_EMPTY_ARRAY: string[] = [];\n\nObject.freeze(FROZEN_EMPTY_ARRAY);\n\n/**\n * Handles conversion of route names with many segments (5+).\n * Internal helper for nameToIDs function.\n *\n * Uses optimized hybrid approach: split to get segments, then slice original\n * string to build cumulative paths. This approach is 65-81% faster than\n * string concatenation for typical cases (5-10 segments).\n *\n * @param name - Route name with 5 or more segments\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed\n * @internal\n */\nfunction nameToIDsGeneral(name: string): string[] {\n // We know there are at least 5 segments at this point (after fast paths)\n const segments = name.split(ROUTE_SEGMENT_SEPARATOR);\n const segmentCount = segments.length;\n\n // First segment is always just itself\n const ids: string[] = [segments[0]];\n\n // Calculate cumulative lengths and slice from original string\n // This avoids repeated string concatenation (O(k²) → O(k))\n let cumulativeLength = segments[0].length;\n\n for (let i = 1; i < segmentCount - 1; i++) {\n cumulativeLength += 1 + segments[i].length; // +1 for dot separator\n ids.push(name.slice(0, cumulativeLength));\n }\n\n // Last segment is always the full route name\n ids.push(name);\n\n return ids;\n}\n\nconst PRIMITIVE_TYPES: ReadonlySet<string> = new Set([\n \"string\",\n \"number\",\n \"boolean\",\n]);\n\nfunction isPrimitive(value: unknown): value is PrimitiveParam {\n return PRIMITIVE_TYPES.has(typeof value);\n}\n\n/**\n * Compares segment parameters between two states without creating intermediate objects.\n * Returns true if all primitive params for the given segment are equal in both states.\n */\nfunction segmentParamsEqual(\n name: string,\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n): boolean {\n const keys = toMetaParams[name];\n\n if (!keys || typeof keys !== \"object\") {\n return true;\n }\n\n for (const key of Object.keys(keys)) {\n const toVal = toState.params[key];\n const fromVal = fromState.params[key];\n\n if (\n isPrimitive(toVal) &&\n isPrimitive(fromVal) &&\n String(toVal) !== String(fromVal)\n ) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Finds the point where two state paths diverge based on segments and parameters.\n * Compares both segment names and their parameters to find the first difference.\n *\n * @param toMetaParams - Cached meta.params from toState (avoids per-segment WeakMap lookup)\n * @param toState - Target state\n * @param fromState - Source state\n * @param toStateIds - Segment IDs for target state\n * @param fromStateIds - Segment IDs for source state\n * @param maxI - Maximum index to check (minimum of both arrays)\n * @returns Index of first difference, or maxI if all checked segments match\n */\nfunction pointOfDifference(\n toMetaParams: Record<string, unknown>,\n toState: State,\n fromState: State,\n toStateIds: string[],\n fromStateIds: string[],\n maxI: number,\n): number {\n for (let i = 0; i < maxI; i++) {\n const toSegment = toStateIds[i];\n const fromSegment = fromStateIds[i];\n\n // Different segment names - immediate difference\n if (toSegment !== fromSegment) {\n return i;\n }\n\n if (!segmentParamsEqual(toSegment, toMetaParams, toState, fromState)) {\n return i;\n }\n }\n\n return maxI;\n}\n\n/**\n * Converts a route name to an array of hierarchical segment identifiers.\n * Each segment ID includes all parent segments in the path.\n *\n * @param name - Route name in dot notation (e.g., 'users.profile.edit')\n * @returns Array of cumulative segment IDs\n * @throws {Error} If route depth exceeds maximum allowed depth\n *\n * @example\n * // Simple route\n * nameToIDs('users');\n * // Returns: ['users']\n *\n * @example\n * // Nested route\n * nameToIDs('users.profile.edit');\n * // Returns: ['users', 'users.profile', 'users.profile.edit']\n *\n * @example\n * // Empty string (root route)\n * nameToIDs('');\n * // Returns: ['']\n *\n * @remarks\n * Input parameter is NOT validated in this function for performance reasons.\n * Validation significantly slows down nameToIDs execution.\n * The input should be validated by the function/method that calls nameToIDs.\n */\n// Module-global cache (shared across all router instances): bounded in practice by\n// the app's route-name vocabulary, which is stable across cloneRouter() requests, so\n// it does not grow per request. Intentionally NOT cleared on dispose() — it is not\n// per-router, so one router's teardown must not evict entries other routers rely on.\nconst nameToIDsCache = new Map<string, string[]>();\n\nexport function nameToIDs(name: string): string[] {\n const cached = nameToIDsCache.get(name);\n\n // Stryker disable next-line BlockStatement: equivalent — dropping the cache-hit early return recomputes the identical frozen id chain (the cache is a perf optimization, not a correctness gate).\n if (cached) {\n return cached;\n }\n\n const result = computeNameToIDs(name);\n\n Object.freeze(result);\n nameToIDsCache.set(name, result);\n\n return result;\n}\n\nfunction computeNameToIDs(name: string): string[] {\n if (!name) {\n return [DEFAULT_ROUTE_NAME];\n }\n\n const firstDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR);\n\n if (firstDot === -1) {\n return [name];\n }\n\n const secondDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, firstDot + 1);\n\n if (secondDot === -1) {\n return [name.slice(0, firstDot), name];\n }\n\n const thirdDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, secondDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 3-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (thirdDot === -1) {\n return [name.slice(0, firstDot), name.slice(0, secondDot), name];\n }\n\n // Stryker disable next-line ArithmeticOperator: equivalent — `thirdDot - 1` makes fourthDot non-(-1), routing 5+ segment names through nameToIDsGeneral, which yields the identical id chain.\n const fourthDot = name.indexOf(ROUTE_SEGMENT_SEPARATOR, thirdDot + 1);\n\n // Stryker disable next-line UnaryOperator,BlockStatement: equivalent — inverting/emptying the 4-segment fast path routes the name through nameToIDsGeneral (below), which yields the identical id chain (same rationale as the L242 ArithmeticOperator disable). The ConditionalExpression/EqualityOperator siblings stay live (→true and !== are killed).\n if (fourthDot === -1) {\n return [\n name.slice(0, firstDot),\n name.slice(0, secondDot),\n name.slice(0, thirdDot),\n name,\n ];\n }\n\n return nameToIDsGeneral(name);\n}\n\n/**\n * Calculates the transition path between two router states.\n * Determines which route segments need to be deactivated and activated\n * to transition from one state to another.\n *\n * @param toState - Target state to transition to\n * @param fromState - Current state to transition from (optional)\n * @returns Transition path with intersection and segments to activate/deactivate\n *\n * @throws {TypeError} When toState is null or undefined\n * @throws {TypeError} When toState is not an object\n * @throws {TypeError} When toState.name is missing or not a string\n * @throws {TypeError} When toState.params is missing or not an object\n * @throws {TypeError} When toState.path is missing or not a string\n * @throws {TypeError} When toState.name contains invalid route format:\n * - Contains only whitespace (e.g., \" \")\n * - Has consecutive dots (e.g., \"users..profile\")\n * - Has leading/trailing dots (e.g., \".users\" or \"users.\")\n * - Segments don't match pattern [a-zA-Z_][a-zA-Z0-9_-]* (e.g., \"users.123\")\n * - Contains spaces or special characters (e.g., \"users profile\")\n * - Exceeds maximum length (8192 characters)\n * @throws {TypeError} When fromState is provided and has any of the validation errors listed above for toState\n *\n * @example\n * // ✅ Valid calls\n * getTransitionPath({ name: 'users.profile', params: {}, path: '/users/profile' });\n * getTransitionPath(toState, fromState);\n * getTransitionPath({ name: '', params: {}, path: '/' }); // root route\n *\n * @example\n * // ❌ Invalid calls that throw TypeError\n * getTransitionPath(null); // toState is null\n * getTransitionPath(undefined); // toState is undefined\n * getTransitionPath({}); // missing required fields\n * getTransitionPath({ name: 123, params: {}, path: '/' }); // name not a string\n * getTransitionPath({ name: 'home', path: '/' }); // missing params\n * getTransitionPath({ name: 'users..profile', params: {}, path: '/' }); // consecutive dots\n * getTransitionPath({ name: '.users', params: {}, path: '/' }); // leading dot\n * getTransitionPath({ name: 'users.', params: {}, path: '/' }); // trailing dot\n * getTransitionPath({ name: 'users profile', params: {}, path: '/' }); // contains space\n * getTransitionPath({ name: 'users.123', params: {}, path: '/' }); // segment starts with number\n * getTransitionPath(validToState, { name: 'invalid..route', params: {}, path: '/' }); // fromState invalid\n *\n * @example\n * // Full activation (no fromState)\n * getTransitionPath(makeState('users.profile'));\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['users', 'users.profile'],\n * // toDeactivate: []\n * // }\n *\n * @example\n * // Partial transition with common ancestor\n * getTransitionPath(\n * makeState('users.profile'),\n * makeState('users.list')\n * );\n * // Returns: {\n * // intersection: 'users',\n * // toActivate: ['users.profile'],\n * // toDeactivate: ['users.list']\n * // }\n *\n * @example\n * // Complete route change\n * getTransitionPath(\n * makeState('admin.dashboard'),\n * makeState('users.profile')\n * );\n * // Returns: {\n * // intersection: '',\n * // toActivate: ['admin', 'admin.dashboard'],\n * // toDeactivate: ['users.profile', 'users']\n * // }\n */\n// Single-entry cache: shouldUpdateNode calls getTransitionPath N times per\n// navigation with the same state objects (once per subscribed node).\n// Cache by reference eliminates N-1 redundant computations.\n// Module-global (≤2 State refs); not cleared on dispose — negligible, not per-router.\nlet cached1To: State | undefined;\nlet cached1From: State | undefined;\nlet cached1Result: TransitionPath | null = null;\n\nlet cached2To: State | undefined;\nlet cached2From: State | undefined;\nlet cached2Result: TransitionPath | null = null;\n\nfunction computeTransitionPath(\n toState: State,\n fromState: State | undefined,\n getMeta: RouteMetaLookup,\n): TransitionPath {\n // ===== FAST PATH 1: Initial navigation (no fromState) =====\n // This is the best performing case in benchmarks (5M ops/sec)\n if (!fromState) {\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: FROZEN_EMPTY_ARRAY,\n };\n }\n\n // ===== FAST PATH 3: Route name not in tree → full reload =====\n // Ownership is read from the live matcher by `state.name` (RFC-4 M2 / #1548 —\n // the per-State `stateMetaStore` WeakMap was removed). `getMeta` returns\n // `undefined` only when the name is not in the tree — a CRUD-removed route.\n const toMetaParams = getMeta(toState.name);\n const fromMetaParams = getMeta(fromState.name);\n\n if (!toMetaParams && !fromMetaParams) {\n // FAST PATH 3 (both names gone from the tree — e.g. `replace()`d away, or a\n // survivor state whose route was removed). Consumers read the result\n // order-INSENSITIVELY, so the from-chain is returned as-is (root→leaf, no\n // reverse needed): `shouldUpdateNode` reads `toDeactivate` by MEMBERSHIP\n // (`.includes`). Any state whose name IS still in the tree — every navigate\n // pipeline state, popstate/start, and `canNavigateTo`'s built toState — takes\n // the STANDARD PATH below, which trims the shared ancestor and reverses\n // correctly. (Pre-#1548 this branch keyed on a missing per-State WeakMap\n // entry; it now keys on tree membership by name.)\n return {\n intersection: EMPTY_INTERSECTION,\n toActivate: nameToIDs(toState.name),\n toDeactivate: nameToIDs(fromState.name),\n };\n }\n\n // ===== STANDARD PATH: Routes with parameters =====\n const toStateIds = nameToIDs(toState.name);\n const fromStateIds = nameToIDs(fromState.name);\n // Stryker disable next-line MethodExpression: equivalent — Math.max reads one index past the shorter id array; that slot is undefined, so the `toSegment !== fromSegment` check in pointOfDifference returns the same divergence index Math.min would stop at.\n const maxI = Math.min(fromStateIds.length, toStateIds.length);\n\n const i = pointOfDifference(\n (toMetaParams ?? fromMetaParams) as Record<string, unknown>,\n toState,\n fromState,\n toStateIds,\n fromStateIds,\n maxI,\n );\n\n // Optimization: Build deactivation list in reverse order directly\n // instead of slice(i).toReversed() which creates 2 arrays\n let toDeactivate: string[];\n\n if (i >= fromStateIds.length) {\n toDeactivate = FROZEN_EMPTY_ARRAY;\n } else if (i === 0 && fromStateIds.length === 1) {\n // Single-segment route: reversed = original, reuse cached frozen array\n toDeactivate = fromStateIds;\n } else {\n toDeactivate = [];\n\n for (let j = fromStateIds.length - 1; j >= i; j--) {\n toDeactivate.push(fromStateIds[j]);\n }\n }\n\n // Build activation list — reuse cached frozen array when using full list\n const toActivate = i === 0 ? toStateIds : toStateIds.slice(i);\n\n // Determine intersection point (common ancestor)\n const intersection = i > 0 ? fromStateIds[i - 1] : EMPTY_INTERSECTION;\n\n return {\n intersection,\n toDeactivate,\n toActivate,\n };\n}\n\nexport function getTransitionPath(\n toState: State,\n fromState: State | undefined,\n getMeta: RouteMetaLookup,\n): TransitionPath {\n // Cache keys on (toState, fromState) identity only — `getMeta` is deterministic\n // per route name and per-router (states are unique per router), so the same\n // state pair always resolves the same meta, and the cache stays correct\n // without keying on the callback (RFC-4 M2 / #1548).\n // Stryker disable BlockStatement: equivalent — both cache short-circuits below; emptying either early-return recomputes the identical TransitionPath (computeTransitionPath is deterministic for the same to/from states) and re-caches it. Restored right after.\n if (\n cached1Result !== null &&\n toState === cached1To &&\n fromState === cached1From\n ) {\n return cached1Result;\n }\n\n if (\n cached2Result !== null &&\n toState === cached2To &&\n fromState === cached2From\n ) {\n return cached2Result;\n }\n // Stryker restore BlockStatement\n\n const result = computeTransitionPath(toState, fromState, getMeta);\n\n cached2To = cached1To;\n cached2From = cached1From;\n cached2Result = cached1Result;\n\n cached1To = toState;\n cached1From = fromState;\n cached1Result = result;\n\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -93,6 +93,20 @@ interface RouterValidator {
|
|
|
93
93
|
state: {
|
|
94
94
|
validateMakeStateArgs: (name: unknown, params: unknown, path: unknown) => void;
|
|
95
95
|
validateAreStatesEqualArgs: (s1: unknown, s2: unknown, ignoreQP: unknown) => void;
|
|
96
|
+
/**
|
|
97
|
+
* The mode gate's opt-in diagnostic (#1575). Core DROPS a query key the
|
|
98
|
+
* active `queryParamsMode` will not print — silently, by the same
|
|
99
|
+
* always-on-fixes / opt-in-diagnoses split the channel guard follows. This
|
|
100
|
+
* hook is what makes the drop visible in development.
|
|
101
|
+
*
|
|
102
|
+
* Called once per dropped key, from the gate itself, so the report cannot
|
|
103
|
+
* disagree with what was actually dropped. A `defaultSearch` declared for
|
|
104
|
+
* such a key surfaces through the same call — it is dead config in these
|
|
105
|
+
* modes, and that is the edge worth naming out loud.
|
|
106
|
+
*/
|
|
107
|
+
reportDroppedQueryKey: (routeName: string, key: string) => void;
|
|
108
|
+
/** Opt-in diagnostic for a key the route declares NOWHERE (#1579). */
|
|
109
|
+
reportUndeclaredParamKey: (routeName: string, key: string) => void;
|
|
96
110
|
};
|
|
97
111
|
/**
|
|
98
112
|
* Event bus validation methods
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouterValidator.d.ts","names":[],"sources":["../../../src/types/RouterValidator.ts"],"mappings":";;;;;;;;;;UAUiB;;;;EAIf;IACE,wBAAwB;IACxB,wBAAwB;IACxB,4BACE,eACA,iBACA,iBACA;IAEF,+BAA+B;IAC/B,2BACE,eACA,iBACA;IAEF,uBAAuB;IACvB,iBACE,mBACA,eACA;IAEF,0BAA0B;IAC1B,+BAA+B,eAAe;IAC9C,mCAAmC,cAAc;IACjD,sBACE,cACA,kBACA;IAEF,uBAAuB,iBAAiB;IACxC,oBAAoB,eAAe;IACnC,uBAAuB,eAAe;IACtC,8BAA8B,mBAAmB;IACjD,0BAA0B;IAC1B,sBAAsB;IACtB,wBAAwB;;;;;EAM1B;IACE,kBAAkB,kBAAkB;IACpC,+BAA+B,oBAAoB;;;;;EAMrD;IACE,yBAAyB,eAAe;IACxC,4BACE,eACA,gBACA;IAEF,6BAA6B,eAAe;IAC5C,2BAA2B,cAAc;IACzC,0BAA0B,gBAAgB;IAC1C,oBAAoB;IACpB,gBAAgB,cAAc;IAC9B,qBAAqB,gBAAgB;IACrC,wBAAwB;;;;;EAM1B;IACE,sBAAsB,eAAe;IACrC,6BACE,kBACA;IAEF,qBAAqB;IACrB,0BAA0B;IAC1B,sBAAsB;IACtB,uBAAuB;IACvB,uBAAuB;IACvB,6BAA6B,iBAAiB;;;;;EAMhD;IACE,kBAAkB,kBAAkB;IACpC,uBAAuB,eAAe;IACtC,0BAA0B,eAAe;IACzC,gBAAgB,cAAc,cAAc;IAC5C,qBAAqB,cAAc;;;;;EAMrC;IACE,uBAAuB;IACvB,gCAAgC;IAChC,8BAA8B;IAC9B,4BAA4B,kBAAkB;IAC9C,iBAAiB,iBAAiB;IAClC,oBAAoB;;;;;EAMtB;IACE,wBACE,eACA,iBACA;IAEF,6BACE,aACA,aACA;;;;;
|
|
1
|
+
{"version":3,"file":"RouterValidator.d.ts","names":[],"sources":["../../../src/types/RouterValidator.ts"],"mappings":";;;;;;;;;;UAUiB;;;;EAIf;IACE,wBAAwB;IACxB,wBAAwB;IACxB,4BACE,eACA,iBACA,iBACA;IAEF,+BAA+B;IAC/B,2BACE,eACA,iBACA;IAEF,uBAAuB;IACvB,iBACE,mBACA,eACA;IAEF,0BAA0B;IAC1B,+BAA+B,eAAe;IAC9C,mCAAmC,cAAc;IACjD,sBACE,cACA,kBACA;IAEF,uBAAuB,iBAAiB;IACxC,oBAAoB,eAAe;IACnC,uBAAuB,eAAe;IACtC,8BAA8B,mBAAmB;IACjD,0BAA0B;IAC1B,sBAAsB;IACtB,wBAAwB;;;;;EAM1B;IACE,kBAAkB,kBAAkB;IACpC,+BAA+B,oBAAoB;;;;;EAMrD;IACE,yBAAyB,eAAe;IACxC,4BACE,eACA,gBACA;IAEF,6BAA6B,eAAe;IAC5C,2BAA2B,cAAc;IACzC,0BAA0B,gBAAgB;IAC1C,oBAAoB;IACpB,gBAAgB,cAAc;IAC9B,qBAAqB,gBAAgB;IACrC,wBAAwB;;;;;EAM1B;IACE,sBAAsB,eAAe;IACrC,6BACE,kBACA;IAEF,qBAAqB;IACrB,0BAA0B;IAC1B,sBAAsB;IACtB,uBAAuB;IACvB,uBAAuB;IACvB,6BAA6B,iBAAiB;;;;;EAMhD;IACE,kBAAkB,kBAAkB;IACpC,uBAAuB,eAAe;IACtC,0BAA0B,eAAe;IACzC,gBAAgB,cAAc,cAAc;IAC5C,qBAAqB,cAAc;;;;;EAMrC;IACE,uBAAuB;IACvB,gCAAgC;IAChC,8BAA8B;IAC9B,4BAA4B,kBAAkB;IAC9C,iBAAiB,iBAAiB;IAClC,oBAAoB;;;;;EAMtB;IACE,wBACE,eACA,iBACA;IAEF,6BACE,aACA,aACA;;;;;;;;;;;;IAaF,wBAAwB,mBAAmB;;IAG3C,2BAA2B,mBAAmB;;;;;EAMhD;IACE,uBAAuB,eAAe;IACtC,0BACE,eACA,mBACA"}
|
package/dist/cjs/types/api.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { DefaultDependencies, GuardFnFactory, Options, Plugin, Route, RouteConfigUpdate } from "./router.js";
|
|
1
|
+
import { Params, SearchParams, SimpleState, State, Unsubscribe } from "./base.js";
|
|
2
|
+
import { AnyOptions, DefaultDependencies, GuardFnFactory, Plugin, Route, RouteConfigUpdate } from "./router.js";
|
|
4
3
|
import { EventMethodMap, EventName } from "./constants.js";
|
|
5
4
|
import { TreeChangedEvent } from "./tree-changed.js";
|
|
6
5
|
import { NavigationOptions, StateContext } from "../types.js";
|
|
@@ -11,14 +10,14 @@ import { NavigationOptions, StateContext } from "../types.js";
|
|
|
11
10
|
*
|
|
12
11
|
* To add a new interceptable method:
|
|
13
12
|
* 1. Add its signature here
|
|
14
|
-
* 2. Wrap it with `createInterceptable()` / `
|
|
13
|
+
* 2. Wrap it with `createInterceptable()` / `createTernaryInterceptable()` in
|
|
15
14
|
* the `registerInternals` block of the Router constructor
|
|
16
15
|
* (`packages/core/src/Router.ts`)
|
|
17
16
|
*/
|
|
18
17
|
interface InterceptableMethodMap {
|
|
19
18
|
start: (path?: string) => Promise<State>;
|
|
20
|
-
buildPath: (route: string, params?: Params) => string;
|
|
21
|
-
forwardState: (routeName: string, routeParams: Params) => SimpleState;
|
|
19
|
+
buildPath: (route: string, params?: Params, search?: SearchParams) => string;
|
|
20
|
+
forwardState: (routeName: string, routeParams: Params, routeSearch?: SearchParams) => SimpleState;
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
23
|
* Type-safe interceptor callback.
|
|
@@ -69,9 +68,8 @@ interface ContextNamespaceClaim<T = unknown> {
|
|
|
69
68
|
* Hides plugin-internal methods from public autocomplete.
|
|
70
69
|
*/
|
|
71
70
|
interface PluginApi {
|
|
72
|
-
makeState: <P extends Params = Params>(name: string, params?: P,
|
|
73
|
-
|
|
74
|
-
forwardState: <P extends Params = Params>(routeName: string, routeParams: P) => SimpleState<P>;
|
|
71
|
+
makeState: <P extends Params = Params, S extends SearchParams = SearchParams>(name: string, params?: P, search?: S, path?: string) => State<P, S>;
|
|
72
|
+
forwardState: <P extends Params = Params, S extends SearchParams = SearchParams>(routeName: string, routeParams: P, routeSearch?: S) => SimpleState<P, S>;
|
|
75
73
|
matchPath: <P extends Params = Params>(path: string) => State<P> | undefined;
|
|
76
74
|
/**
|
|
77
75
|
* Navigate to a fully-built `State`, skipping the redundant
|
|
@@ -97,8 +95,19 @@ interface PluginApi {
|
|
|
97
95
|
setRootPath: (rootPath: string) => void;
|
|
98
96
|
getRootPath: () => string;
|
|
99
97
|
addEventListener: <E extends EventName>(eventName: E, cb: Plugin[EventMethodMap[E]]) => Unsubscribe;
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Builds the State `navigate` would commit, without committing it: resolves
|
|
100
|
+
* `forwardTo`, checks existence (`undefined` = unknown route) and merges the
|
|
101
|
+
* route defaults.
|
|
102
|
+
*
|
|
103
|
+
* `search` is the query channel (#1571). It was the ONE pipeline entry point
|
|
104
|
+
* without the slot — `navigate` / `buildPath` / `canNavigateTo` /
|
|
105
|
+
* `isActiveRoute` / `makeState` all take one — so a query intent could only
|
|
106
|
+
* be spelled by riding declared keys in the `params` bag. An explicit value
|
|
107
|
+
* beats such a twin, matching the other five.
|
|
108
|
+
*/
|
|
109
|
+
buildNavigationState: (name: string, params?: Params, search?: SearchParams) => State | undefined;
|
|
110
|
+
getOptions: () => AnyOptions;
|
|
102
111
|
getTree: () => unknown;
|
|
103
112
|
addInterceptor: <M extends keyof InterceptableMethodMap>(method: M, fn: InterceptorFn<M>) => Unsubscribe;
|
|
104
113
|
extendRouter: (extensions: Record<string, unknown>) => Unsubscribe;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","names":[],"sources":["../../../src/types/api.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","names":[],"sources":["../../../src/types/api.ts"],"mappings":";;;;;;;;;;;;;;;;UAoCiB;EACf,QAAQ,kBAAkB,QAAQ;EAClC,YAAY,eAAe,SAAS,QAAQ,SAAS;EACrD,eACE,mBACA,aAAa,QACb,cAAc,iBACX;;;;;;KAOK,cAAc,gBAAgB,2BACxC,MAAM,uBAAuB,OAC1B,MAAM,WAAW,uBAAuB,QACxC,WAAW,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAqCtB,sBAAsB;EACrC,QAAQ,OAAO,OAAO,OAAO;EAC7B;;;;;;UAOe;EACf,YAAY,UAAU,SAAS,QAAQ,UAAU,eAAe,cAC9D,cACA,SAAS,GACT,SAAS,GACT,kBACG,MAAM,GAAG;EAEd,eACE,UAAU,SAAS,QACnB,UAAU,eAAe,cAEzB,mBACA,aAAa,GACb,cAAc,MACX,YAAY,GAAG;EAEpB,YAAY,UAAU,SAAS,QAAQ,iBAAiB,MAAM;;;;;;;;;;;;;;;;;;;;;EAsB9D,kBACE,OAAO,OACP,UAAU,sBACP,QAAQ;EAEb,cAAc;EACd;EAEA,mBAAmB,UAAU,WAC3B,WAAW,GACX,IAAI,OAAO,eAAe,QACvB;;;;;;;;;;;;EAaL,uBACE,cACA,SAAS,QACT,SAAS,iBACN;EAEL,kBAAkB;EAElB;EAEA,iBAAiB,gBAAgB,wBAC/B,QAAQ,GACR,IAAI,cAAc,OACf;EAEL,eAAe,YAAY,4BAA4B;;;;;;;;;;;;;EAcvD,sBAAsB,OAAO;EAE7B;KAEG,gBAAgB,uBACf,WAAW,IACV,sBAAsB,aAAa;KACrC,oBAAoB;;EAGvB,iBAAiB,iBAAiB;;;;;UAMnB,UACf,qBAAqB,sBAAsB;EAE3C,MACE,QAAQ,MAAM,kBAAkB,MAAM,eACtC;IAAY;;EAGd,SAAS;;;;;;;;;;;EAYT,SAAS,cAAc,SAAS,kBAAkB;EAElD;EAEA,UAAU,QAAQ,MAAM,kBAAkB,MAAM;EAEhD,MAAM;EAEN,MAAM,iBAAiB,MAAM;;;;;;;;;;;;;EAc7B,mBACE,UAAU,OAAO,iBAAiB,2BAC/B;;;;;UAMU,gBACf,qBAAqB,sBAAsB;EAE3C,MAAM,gBAAgB,cAAc,KAAK,MAAM,aAAa;EAC5D,cAAc,QAAQ;EACtB,MAAM,gBAAgB,uBACpB,MAAM,GACN,OAAO,aAAa;EAEtB,SAAS,MAAM;EACf,SAAS,YAAY;EACrB;EACA,MAAM,YAAY;;;;;UAMH,aACf,qBAAqB,sBAAsB;EAE3C,mBACE,cACA,oBAAoB,eAAe;EAErC,qBACE,cACA,sBAAsB,eAAe;EAEvC,sBAAsB;EACtB,wBAAwB"}
|
package/dist/cjs/types/base.d.ts
CHANGED
|
@@ -10,9 +10,15 @@ import { StateContext } from "../types.js";
|
|
|
10
10
|
* has no additional effect after the first call.
|
|
11
11
|
*/
|
|
12
12
|
type Unsubscribe = () => void;
|
|
13
|
-
interface SimpleState<P extends Params = Params> {
|
|
13
|
+
interface SimpleState<P extends Params = Params, S extends SearchParams = SearchParams> {
|
|
14
14
|
name: string;
|
|
15
15
|
params: P;
|
|
16
|
+
/**
|
|
17
|
+
* Query channel — see {@link SearchParams}. Split out of the former single
|
|
18
|
+
* `params` bag (RFC-4 M2 / #1548). Always present: a frozen empty object when
|
|
19
|
+
* there are no query params.
|
|
20
|
+
*/
|
|
21
|
+
search: S;
|
|
16
22
|
}
|
|
17
23
|
type TransitionPhase = "deactivating" | "activating";
|
|
18
24
|
type TransitionReason = "success" | "blocked" | "cancelled" | "error";
|
|
@@ -30,9 +36,15 @@ interface TransitionMeta {
|
|
|
30
36
|
intersection: string;
|
|
31
37
|
};
|
|
32
38
|
}
|
|
33
|
-
interface State<P extends Params = Params> {
|
|
39
|
+
interface State<P extends Params = Params, S extends SearchParams = SearchParams> {
|
|
34
40
|
name: string;
|
|
35
41
|
params: P;
|
|
42
|
+
/**
|
|
43
|
+
* Query channel — see {@link SearchParams}. Holds ONLY query-string params;
|
|
44
|
+
* path params live in {@link State.params} (RFC-4 M2 / #1548). Always present:
|
|
45
|
+
* a frozen empty object when there are no query params.
|
|
46
|
+
*/
|
|
47
|
+
search: S;
|
|
36
48
|
path: string;
|
|
37
49
|
transition: TransitionMeta;
|
|
38
50
|
/**
|
|
@@ -57,8 +69,36 @@ interface State<P extends Params = Params> {
|
|
|
57
69
|
*/
|
|
58
70
|
context: StateContext & Record<string, unknown>;
|
|
59
71
|
}
|
|
60
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Descriptor form of a navigation target (RFC-4 M2 / #1548). The two-channel
|
|
74
|
+
* counterpart to the positional `navigate(name, params, search, opts)` form:
|
|
75
|
+
* `params` is the path channel, `search` the query channel. Passed as the first
|
|
76
|
+
* argument to `router.navigate(target, opts?)` / `router.buildPath(target)` /
|
|
77
|
+
* `router.isActiveRoute(target)` and as the `to` prop of framework `<Link>`s,
|
|
78
|
+
* so a target can be threaded as one value instead of positional arguments.
|
|
79
|
+
*/
|
|
80
|
+
interface NavigationTarget<P extends Params = Params, S extends SearchParams = SearchParams> {
|
|
81
|
+
name: string;
|
|
82
|
+
params?: P;
|
|
83
|
+
search?: S;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The two navigation channels a per-route codec operates on (RFC-4 M2 / #1548):
|
|
87
|
+
* `params` is the path channel, `search` the query channel. Used as **both** the
|
|
88
|
+
* input and the output of {@link Route.encodeParams} / {@link Route.decodeParams}
|
|
89
|
+
* — a codec transforms whichever channel(s) it owns and returns the other channel
|
|
90
|
+
* unchanged (it must be passed through explicitly).
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* v1 ran the whole (path + query) bag through both callbacks, so a query value
|
|
94
|
+
* reached `decodeParams`. This two-channel shape preserves that reach: the query
|
|
95
|
+
* arrives in `search` rather than merged into `params`. Value semantics are
|
|
96
|
+
* unchanged — only the storage location differs (M2 keeps query parsing/printing
|
|
97
|
+
* exactly as v1).
|
|
98
|
+
*/
|
|
99
|
+
interface ParamsSearch<P extends Params = Params, S extends SearchParams = SearchParams> {
|
|
61
100
|
params: P;
|
|
101
|
+
search: S;
|
|
62
102
|
}
|
|
63
103
|
/**
|
|
64
104
|
* Parsed shape produced by `serializeRouterState()` (`@real-router/ssr-utils`,
|
|
@@ -90,6 +130,25 @@ interface RouterError extends Error {
|
|
|
90
130
|
interface Params {
|
|
91
131
|
[key: string]: string | string[] | number | number[] | boolean | boolean[] | Params | Params[] | Record<string, string | number | boolean> | null | undefined;
|
|
92
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Query-parameter primitive values. Mirrors the engine's `QueryParamPrimitive`
|
|
135
|
+
* (`engine/search-params/types.ts`) — the value form is identical to v1's query
|
|
136
|
+
* half (the union produced across `queryParamsMode` formats).
|
|
137
|
+
*/
|
|
138
|
+
type SearchParamPrimitive = string | number | boolean | null;
|
|
139
|
+
/**
|
|
140
|
+
* A single query-parameter value — a primitive or an array of primitives.
|
|
141
|
+
*/
|
|
142
|
+
type SearchParamValue = SearchParamPrimitive | SearchParamPrimitive[];
|
|
143
|
+
/**
|
|
144
|
+
* Query-parameter bag carried by {@link State.search} — the query channel,
|
|
145
|
+
* split out of the former single `params` bag in M2 (RFC-4 M2 / #1548). Only
|
|
146
|
+
* the storage location changes vs v1; value semantics (modes, `queryParamsMode`,
|
|
147
|
+
* ternary arrays) are unchanged. Structurally matches the engine's internal
|
|
148
|
+
* `SearchParams` (`engine/search-params/types.ts`), so match results flow into
|
|
149
|
+
* `State.search` without conversion.
|
|
150
|
+
*/
|
|
151
|
+
type SearchParams = Record<string, SearchParamValue | undefined>;
|
|
93
152
|
//#endregion
|
|
94
|
-
export { Params, RouterError, SerializedRouterState, SimpleState, State,
|
|
153
|
+
export { NavigationTarget, Params, ParamsSearch, RouterError, SearchParamPrimitive, SearchParamValue, SearchParams, SerializedRouterState, SimpleState, State, TransitionMeta, TransitionPhase, TransitionReason, Unsubscribe };
|
|
95
154
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","names":[],"sources":["../../../src/types/base.ts"],"mappings":";;;;;;;;;;;KAmBY;UAEK,
|
|
1
|
+
{"version":3,"file":"base.d.ts","names":[],"sources":["../../../src/types/base.ts"],"mappings":";;;;;;;;;;;KAmBY;UAEK,YACf,UAAU,SAAS,QACnB,UAAU,eAAe;EAEzB;EACA,QAAQ;;;;;;EAMR,QAAQ;;KAGE;KAEA;UAEK;EACf,OAAO;EACP,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;IACE;IACA;IACA;;;UAIa,MACf,UAAU,SAAS,QACnB,UAAU,eAAe;EAEzB;EACA,QAAQ;;;;;;EAMR,QAAQ;EACR;EACA,YAAY;;;;;;;;;;;;;;;;;;;;;EAqBZ,SAAS,eAAe;;;;;;;;;;UAWT,iBACf,UAAU,SAAS,QACnB,UAAU,eAAe;EAEzB;EACA,SAAS;EACT,SAAS;;;;;;;;;;;;;;;;UAiBM,aACf,UAAU,SAAS,QACnB,UAAU,eAAe;EAEzB,QAAQ;EACR,QAAQ;;;;;;;;;;KAWE,sBAAsB,UAAU,SAAS,UAAU,KAC7D,MAAM;;;;;;;UAUS,oBAAoB;GAClC;WACQ;WACA;WACA;EACT,UAAU;EACV,mBAAmB,KAAK;EACxB,sBAAsB,QAAQ;EAC9B,WAAW;EACX,WAAW;EACX,cAAc;;UAGC;GACd,4EAOG,SACA,WACA;;;;;;;KAUM;;;;KAKA,mBAAmB,uBAAuB;;;;;;;;;KAU1C,eAAe,eAAe"}
|
|
@@ -66,6 +66,7 @@ interface ErrorCodeToValueMap {
|
|
|
66
66
|
CONTEXT_NAMESPACE_ALREADY_CLAIMED: "CONTEXT_NAMESPACE_ALREADY_CLAIMED";
|
|
67
67
|
REENTRANT_NAVIGATION: "REENTRANT_NAVIGATION";
|
|
68
68
|
REENTRANT_TREE_MUTATION: "REENTRANT_TREE_MUTATION";
|
|
69
|
+
WRONG_CHANNEL: "WRONG_CHANNEL";
|
|
69
70
|
}
|
|
70
71
|
//#endregion
|
|
71
72
|
export { ErrorCodeKeys, ErrorCodeToValueMap, ErrorCodeValues, EventMethodMap, EventName, EventToNameMap, EventToPluginMap, EventsKeys, PluginMethod };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","names":[],"sources":["../../../src/types/constants.ts"],"mappings":";;;;KAGY;;;;KAYA;;;;KAYA;;;;KAYA;;;;KAiBA;;;;UAmBK;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;;;;UAMM;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU,oBACT,KAAK,cAAc,eAAe,KAAK,iBAAiB;;;;UAM1C;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","names":[],"sources":["../../../src/types/constants.ts"],"mappings":";;;;KAGY;;;;KAYA;;;;KAYA;;;;KAYA;;;;KAiBA;;;;UAmBK;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;;;;UAMM;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU,oBACT,KAAK,cAAc,eAAe,KAAK,iBAAiB;;;;UAM1C;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA"}
|