@xmachines/play-router 1.0.0-beta.8 → 1.0.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/LICENSE +21 -0
- package/README.md +259 -335
- package/dist/base-route-map.d.ts +136 -0
- package/dist/base-route-map.d.ts.map +1 -0
- package/dist/base-route-map.js +182 -0
- package/dist/base-route-map.js.map +1 -0
- package/dist/build-tree.d.ts +7 -0
- package/dist/build-tree.d.ts.map +1 -1
- package/dist/build-tree.js +21 -6
- package/dist/build-tree.js.map +1 -1
- package/dist/create-route-map-from-tree.d.ts +33 -0
- package/dist/create-route-map-from-tree.d.ts.map +1 -0
- package/dist/create-route-map-from-tree.js +43 -0
- package/dist/create-route-map-from-tree.js.map +1 -0
- package/dist/create-route-map.d.ts +29 -33
- package/dist/create-route-map.d.ts.map +1 -1
- package/dist/create-route-map.js +25 -64
- package/dist/create-route-map.js.map +1 -1
- package/dist/errors.d.ts +152 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +186 -0
- package/dist/errors.js.map +1 -0
- package/dist/extract-routes.d.ts +5 -31
- package/dist/extract-routes.d.ts.map +1 -1
- package/dist/extract-routes.js +73 -51
- package/dist/extract-routes.js.map +1 -1
- package/dist/find-route.d.ts +44 -0
- package/dist/find-route.d.ts.map +1 -0
- package/dist/find-route.js +103 -0
- package/dist/find-route.js.map +1 -0
- package/dist/index.d.ts +13 -51
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -133
- package/dist/index.js.map +1 -1
- package/dist/machine-to-graph.d.ts +17 -0
- package/dist/machine-to-graph.d.ts.map +1 -0
- package/dist/machine-to-graph.js +115 -0
- package/dist/machine-to-graph.js.map +1 -0
- package/dist/query.d.ts +49 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +98 -3
- package/dist/query.js.map +1 -1
- package/dist/router-bridge-base.d.ts +196 -32
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +319 -85
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.d.ts +95 -0
- package/dist/router-sync.d.ts.map +1 -0
- package/dist/router-sync.js +161 -0
- package/dist/router-sync.js.map +1 -0
- package/dist/types.d.ts +163 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/url-pattern-utils.d.ts +117 -0
- package/dist/url-pattern-utils.d.ts.map +1 -0
- package/dist/url-pattern-utils.js +176 -0
- package/dist/url-pattern-utils.js.map +1 -0
- package/dist/validate-routes.d.ts +32 -14
- package/dist/validate-routes.d.ts.map +1 -1
- package/dist/validate-routes.js +28 -21
- package/dist/validate-routes.js.map +1 -1
- package/package.json +42 -14
- package/dist/connect-router.d.ts +0 -56
- package/dist/connect-router.d.ts.map +0 -1
- package/dist/connect-router.js +0 -119
- package/dist/connect-router.js.map +0 -1
- package/dist/crawl-machine.d.ts +0 -74
- package/dist/crawl-machine.d.ts.map +0 -1
- package/dist/crawl-machine.js +0 -95
- package/dist/crawl-machine.js.map +0 -1
- package/dist/create-browser-history.d.ts +0 -68
- package/dist/create-browser-history.d.ts.map +0 -1
- package/dist/create-browser-history.js +0 -94
- package/dist/create-browser-history.js.map +0 -1
- package/dist/create-router.d.ts +0 -73
- package/dist/create-router.d.ts.map +0 -1
- package/dist/create-router.js +0 -63
- package/dist/create-router.js.map +0 -1
- package/dist/extract-route.d.ts +0 -25
- package/dist/extract-route.d.ts.map +0 -1
- package/dist/extract-route.js +0 -63
- package/dist/extract-route.js.map +0 -1
package/dist/query.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import { getSuccessors, hasNode, hasPath } from "@statelyai/graph";
|
|
2
|
+
/**
|
|
3
|
+
* Get the routable successor nodes of a state — states directly reachable via
|
|
4
|
+
* transition edges that have a defined `meta.route`.
|
|
5
|
+
*
|
|
6
|
+
* Shared traversal core for `getTransitionReachableStateIds` and
|
|
7
|
+
* `getTransitionReachableRoutes`; returns full nodes so callers can project
|
|
8
|
+
* whichever field they need (stateId vs raw route string).
|
|
9
|
+
*/
|
|
10
|
+
const getRoutableSuccessors = (graph, stateId) =>
|
|
11
|
+
// A state ID with no graph node (e.g. a non-routable or removed state) is a
|
|
12
|
+
// normal "nothing reachable" outcome, checked explicitly rather than by
|
|
13
|
+
// pattern-matching thrown error messages.
|
|
14
|
+
hasNode(graph, stateId)
|
|
15
|
+
? getSuccessors(graph, stateId).filter((n) => n.data.route !== undefined)
|
|
16
|
+
: [];
|
|
1
17
|
/**
|
|
2
18
|
* Get all routes navigable from given state
|
|
3
19
|
*
|
|
@@ -19,9 +35,34 @@ export const getNavigableRoutes = (tree, stateId) => {
|
|
|
19
35
|
const node = tree.byStateId.get(stateId);
|
|
20
36
|
if (!node)
|
|
21
37
|
return [];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
const results = [...node.children]; // Direct child routes (existing behavior)
|
|
39
|
+
// If graph is available, also include routes reachable via transitions.
|
|
40
|
+
// Look nodes up by state ID: graph nodes carry the RAW meta.route string
|
|
41
|
+
// (possibly relative, e.g. "detail"), while tree.byPath is keyed by resolved
|
|
42
|
+
// fullPath ("/section/detail") — a path-based lookup would silently drop
|
|
43
|
+
// every relative route. State IDs are unambiguous in both structures.
|
|
44
|
+
if (tree.graph) {
|
|
45
|
+
const reachableStateIds = getTransitionReachableStateIds(tree.graph, stateId);
|
|
46
|
+
const existingPaths = new Set(results.map((r) => r.fullPath));
|
|
47
|
+
for (const reachableStateId of reachableStateIds) {
|
|
48
|
+
const routeNode = tree.byStateId.get(reachableStateId);
|
|
49
|
+
if (routeNode && !existingPaths.has(routeNode.fullPath)) {
|
|
50
|
+
results.push(routeNode);
|
|
51
|
+
existingPaths.add(routeNode.fullPath);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Get state IDs of routable states reachable via transitions from a state.
|
|
59
|
+
*
|
|
60
|
+
* Same traversal as `getTransitionReachableRoutes`, but returns state IDs
|
|
61
|
+
* instead of raw `meta.route` strings so callers can resolve nodes through
|
|
62
|
+
* `tree.byStateId` without raw-vs-resolved path ambiguity.
|
|
63
|
+
*/
|
|
64
|
+
const getTransitionReachableStateIds = (graph, stateId) => {
|
|
65
|
+
return getRoutableSuccessors(graph, stateId).map((n) => n.data.stateId);
|
|
25
66
|
};
|
|
26
67
|
/**
|
|
27
68
|
* Get all routable routes from tree as flat array
|
|
@@ -66,4 +107,58 @@ export const getRoutableRoutes = (tree) => {
|
|
|
66
107
|
export const routeExists = (tree, path) => {
|
|
67
108
|
return tree.byPath.has(path);
|
|
68
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* Get routes reachable via transitions from current state
|
|
112
|
+
*
|
|
113
|
+
* Uses the @statelyai/graph successor algorithm to find all states
|
|
114
|
+
* directly reachable via transition edges from the given state,
|
|
115
|
+
* then filters to those with defined routes.
|
|
116
|
+
*
|
|
117
|
+
* Returned values are the RAW `meta.route` strings from the machine — relative
|
|
118
|
+
* routes (e.g. `"detail"`) are NOT resolved to full paths and cannot be used as
|
|
119
|
+
* `tree.byPath` keys. For resolved `RouteNode`s use `getNavigableRoutes`, which
|
|
120
|
+
* resolves reachable states through `tree.byStateId`.
|
|
121
|
+
*
|
|
122
|
+
* @param graph - Machine graph from RouteTree.graph
|
|
123
|
+
* @param stateId - Current state ID (e.g., "test.home")
|
|
124
|
+
* @returns Array of raw route strings reachable via transitions
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const tree = extractMachineRoutes(machine);
|
|
129
|
+
* if (tree.graph) {
|
|
130
|
+
* const reachable = getTransitionReachableRoutes(tree.graph, 'auth.loggedIn');
|
|
131
|
+
* // ['/dashboard', '/settings'] — routes reachable via transitions
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export const getTransitionReachableRoutes = (graph, stateId) => {
|
|
136
|
+
return getRoutableSuccessors(graph, stateId).map((n) => n.data.route);
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Check if a route is reachable from current state via transitions
|
|
140
|
+
*
|
|
141
|
+
* Uses @statelyai/graph path-finding to determine if there exists
|
|
142
|
+
* a chain of transition edges from the source state to the target state.
|
|
143
|
+
*
|
|
144
|
+
* @param graph - Machine graph from RouteTree.graph
|
|
145
|
+
* @param fromStateId - Source state ID
|
|
146
|
+
* @param toStateId - Target state ID
|
|
147
|
+
* @returns true if a transition path exists, false otherwise
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const tree = extractMachineRoutes(machine);
|
|
152
|
+
* if (tree.graph) {
|
|
153
|
+
* const canReach = isRouteReachable(tree.graph, 'auth.login', 'auth.dashboard');
|
|
154
|
+
* // true if login → dashboard transition path exists
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export const isRouteReachable = (graph, fromStateId, toStateId) => {
|
|
159
|
+
// Missing endpoints are a normal "not reachable" outcome.
|
|
160
|
+
return hasNode(graph, fromStateId) && hasNode(graph, toStateId)
|
|
161
|
+
? hasPath(graph, fromStateId, toStateId)
|
|
162
|
+
: false;
|
|
163
|
+
};
|
|
69
164
|
//# sourceMappingURL=query.js.map
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Captures the 90% identical logic from all existing router bridges:
|
|
5
5
|
* - TC39 Signal watcher for actor → router direction
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
6
|
+
* - `lastSyncedPath` for echo suppression in the actor→router direction
|
|
7
|
+
* - `isProcessingNavigation` flag for guard-redirect loop prevention in `syncActorFromRouter` only
|
|
8
|
+
* - `syncRouterFromActor` / `syncActorFromRouter` with URL parameter extraction
|
|
8
9
|
* - connect / disconnect lifecycle matching the RouterBridge protocol
|
|
9
10
|
*
|
|
10
11
|
* Subclasses implement only the 3 framework-specific abstract methods:
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
* export class MyRouterBridge extends RouterBridgeBase {
|
|
18
19
|
* private unsubscribe: (() => void) | null = null;
|
|
19
20
|
*
|
|
20
|
-
* constructor(private router: MyRouter, actor:
|
|
21
|
+
* constructor(private router: MyRouter, actor: RoutableActor, routeMap: { getStateIdByPath(path: string): string | undefined; getPathByStateId(id: string): string | undefined; }) {
|
|
21
22
|
* super(actor, routeMap);
|
|
22
23
|
* }
|
|
23
24
|
*
|
|
@@ -38,12 +39,29 @@
|
|
|
38
39
|
* }
|
|
39
40
|
* ```
|
|
40
41
|
*
|
|
41
|
-
* @see
|
|
42
|
+
* @see [Play RFC](../../docs/rfc/play.md) - Invariant INV-04
|
|
42
43
|
*/
|
|
43
44
|
import { Signal } from "@xmachines/play-signals";
|
|
44
|
-
import type {
|
|
45
|
-
import type { AnyActorLogic } from "xstate";
|
|
45
|
+
import type { RoutableActor } from "./types.js";
|
|
46
46
|
import type { RouterBridge } from "./types.js";
|
|
47
|
+
/**
|
|
48
|
+
* Narrow interface for the TC39 Signal watcher used by `RouterBridgeBase` to
|
|
49
|
+
* monitor `actor.currentRoute` changes.
|
|
50
|
+
*
|
|
51
|
+
* This interface hides the full `Signal.subtle.Watcher` surface and exposes only
|
|
52
|
+
* the two operations that `RouterBridgeBase` actually needs:
|
|
53
|
+
* - `watch(signal)` — arm the watcher on a specific signal
|
|
54
|
+
* - `unwatch()` — stop watching and release resources
|
|
55
|
+
*
|
|
56
|
+
* Framework adapter subclasses never interact with this handle directly; it is
|
|
57
|
+
* created and managed internally by `RouterBridgeBase`.
|
|
58
|
+
*/
|
|
59
|
+
export interface RouteWatcherHandle {
|
|
60
|
+
/** Arm the watcher to observe the given signal. */
|
|
61
|
+
watch(signal: Signal.Computed<string | null>): void;
|
|
62
|
+
/** Stop observing and release the watcher. */
|
|
63
|
+
unwatch(): void;
|
|
64
|
+
}
|
|
47
65
|
/**
|
|
48
66
|
* Abstract base class for all `@xmachines` router adapter bridges.
|
|
49
67
|
*
|
|
@@ -52,31 +70,52 @@ import type { RouterBridge } from "./types.js";
|
|
|
52
70
|
* between frameworks.
|
|
53
71
|
*/
|
|
54
72
|
export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
55
|
-
protected readonly actor:
|
|
73
|
+
protected readonly actor: RoutableActor;
|
|
56
74
|
protected readonly routeMap: {
|
|
57
|
-
getStateIdByPath(path: string): string | undefined;
|
|
58
|
-
getPathByStateId(id: string): string | undefined;
|
|
75
|
+
getStateIdByPath(path: string): string | null | undefined;
|
|
76
|
+
getPathByStateId(id: string): string | null | undefined;
|
|
59
77
|
};
|
|
60
78
|
protected isConnected: boolean;
|
|
61
79
|
protected hasConnectedOnce: boolean;
|
|
62
|
-
protected lastSyncedPath: string;
|
|
63
|
-
protected isProcessingNavigation: boolean;
|
|
64
|
-
protected routeWatcher: Signal.subtle.Watcher | null;
|
|
80
|
+
protected lastSyncedPath: string | null;
|
|
65
81
|
/**
|
|
66
|
-
*
|
|
82
|
+
* Guards `syncActorFromRouter` against re-entrant calls triggered by the
|
|
83
|
+
* actor's own guard redirects (router→actor send → signal fires → actor→router
|
|
84
|
+
* push → another syncActorFromRouter before the first one returns).
|
|
67
85
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
86
|
+
* NOT used for actor→router echo suppression — that is handled exclusively by
|
|
87
|
+
* `lastSyncedPath`, which is updated before `navigateRouter()` is called so
|
|
88
|
+
* any router callback for the same path short-circuits at the
|
|
89
|
+
* `sanitized === lastSyncedPath` check in `syncActorFromRouter`.
|
|
70
90
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
protected isProcessingNavigation: boolean;
|
|
92
|
+
protected routeWatcher: RouteWatcherHandle | null;
|
|
93
|
+
/**
|
|
94
|
+
* @param actor - A `RoutableActor` exposing `currentRoute`, `initialRoute`, and `send`.
|
|
95
|
+
* @param routeMap - Bidirectional route map for `stateId ↔ path` resolution.
|
|
96
|
+
* Provide `getStateIdByPath` and `getPathByStateId`. Framework adapters
|
|
97
|
+
* typically wrap the result of `createRouteMap(machine)` or an equivalent.
|
|
98
|
+
* `getPathByStateId` may be keyed on either the `"#stateId"` or the bare
|
|
99
|
+
* `"stateId"` form — both forms are tried automatically by the bridge, so
|
|
100
|
+
* custom implementations (e.g. plain test objects) need only handle one.
|
|
101
|
+
*/
|
|
102
|
+
constructor(actor: RoutableActor, routeMap: {
|
|
103
|
+
getStateIdByPath(path: string): string | null | undefined;
|
|
104
|
+
getPathByStateId(id: string): string | null | undefined;
|
|
74
105
|
});
|
|
75
106
|
/**
|
|
76
107
|
* Connect the router bridge to the Actor.
|
|
77
108
|
*
|
|
78
109
|
* Sets up the TC39 Signal watcher for actor → router direction and
|
|
79
110
|
* starts watching router changes (framework-specific).
|
|
111
|
+
*
|
|
112
|
+
* Ordering here is part of the bridge contract:
|
|
113
|
+
* - `lastSyncedPath` is seeded in the constructor from `actor.currentRoute`
|
|
114
|
+
* - the actor watcher is installed before adapter router subscriptions
|
|
115
|
+
* - initial sync then resolves deep-link vs restore using `actor.initialRoute`
|
|
116
|
+
*
|
|
117
|
+
* Adapters that need custom initial-sync behavior should override
|
|
118
|
+
* `getInitialRouterPath()` rather than reordering `connect()` steps.
|
|
80
119
|
*/
|
|
81
120
|
connect(): void;
|
|
82
121
|
/**
|
|
@@ -88,25 +127,102 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
88
127
|
/**
|
|
89
128
|
* Sync router location when actor route signal changes.
|
|
90
129
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
130
|
+
* Resolves the actor route to a concrete URL path and calls navigateRouter()
|
|
131
|
+
* for framework-specific navigation. When the route cannot be resolved
|
|
132
|
+
* (unknown stateId, parameterized pattern with no concrete values) the push
|
|
133
|
+
* is skipped entirely — `navigateRouter` is never called with a raw stateId
|
|
134
|
+
* or pattern.
|
|
135
|
+
*
|
|
136
|
+
* Echo suppression — preventing the router's own callback from re-driving the
|
|
137
|
+
* actor — is handled entirely by `lastSyncedPath`: it is set to the resolved
|
|
138
|
+
* path before `navigateRouter()` is called, so any `syncActorFromRouter`
|
|
139
|
+
* invocation for the same path short-circuits at the
|
|
140
|
+
* `sanitized === lastSyncedPath` check and sends no event regardless of
|
|
141
|
+
* whether the callback fires synchronously or asynchronously.
|
|
142
|
+
*
|
|
143
|
+
* `isProcessingNavigation` is NOT set here — it is only used inside
|
|
144
|
+
* `syncActorFromRouter` to guard against re-entrant guard-redirect loops.
|
|
93
145
|
*/
|
|
94
146
|
protected syncRouterFromActor(route: string | null | unknown): void;
|
|
147
|
+
/**
|
|
148
|
+
* Resolve an actor route to its concrete URL path and push it to the router.
|
|
149
|
+
*
|
|
150
|
+
* lastSyncedPath must store the concrete path so that the router callback
|
|
151
|
+
* (which fires with the concrete path) matches and short-circuits in
|
|
152
|
+
* syncActorFromRouter. Storing the raw stateId (e.g. "#home") would cause a
|
|
153
|
+
* mismatch against the sanitized path ("/") in the watcher.
|
|
154
|
+
*
|
|
155
|
+
* When resolveNavigationPath returns null (parameterized/wildcard pattern,
|
|
156
|
+
* unknown id) the push is skipped entirely — pushing the raw value would write
|
|
157
|
+
* stateIds, `:param` patterns, or a literal `*` into the browser URL.
|
|
158
|
+
* lastSyncedPath is still set to the
|
|
159
|
+
* raw route so the dedup guard fires correctly on the next identical signal value.
|
|
160
|
+
*/
|
|
161
|
+
private pushResolvedRoute;
|
|
95
162
|
/**
|
|
96
163
|
* Sync actor state when router location changes.
|
|
97
164
|
*
|
|
98
|
-
*
|
|
99
|
-
* Prevents circular updates via
|
|
165
|
+
* **Known path:** sends a `play.route` event to the actor with the matched
|
|
166
|
+
* stateId, params, and query. Prevents circular updates via the
|
|
167
|
+
* `isProcessingNavigation` flag.
|
|
168
|
+
*
|
|
169
|
+
* **Unknown/unmapped path:** does NOT send a `play.route` event (actor state
|
|
170
|
+
* is unchanged). Instead, actively corrects the browser URL by calling
|
|
171
|
+
* `navigateRouter(actor.currentRoute.get())` — keeping the URL in sync with
|
|
172
|
+
* actor state even when the user types an invalid path into the address bar
|
|
173
|
+
* or pushes one programmatically mid-session. `lastSyncedPath` is set to the
|
|
174
|
+
* resolved concrete path before calling `navigateRouter` so the router's own
|
|
175
|
+
* callback for that navigation short-circuits the echo-suppression guard and
|
|
176
|
+
* sends no spurious event.
|
|
100
177
|
*/
|
|
101
178
|
protected syncActorFromRouter(pathname: string, search?: string): void;
|
|
102
179
|
/**
|
|
103
|
-
* Extract path parameters from URL using URLPattern API.
|
|
180
|
+
* Extract path parameters from URL using the URLPattern API.
|
|
181
|
+
*
|
|
182
|
+
* Accesses `globalThis.URLPattern` at runtime — no polyfill is imported by this
|
|
183
|
+
* library. If `URLPattern` is unavailable and the matched route has parameterized
|
|
184
|
+
* segments, a `URLPatternUnavailableError` is thrown — callers must provide a polyfill
|
|
185
|
+
* for environments without native URLPattern support (Node.js < 24, older browsers).
|
|
104
186
|
*
|
|
105
187
|
* @param pathname - The actual URL path (e.g., '/profile/john')
|
|
106
|
-
* @param stateId - The matched state ID for looking up route pattern
|
|
107
|
-
* @returns Extracted path parameters or empty object
|
|
188
|
+
* @param stateId - The matched state ID for looking up the route pattern
|
|
189
|
+
* @returns Extracted path parameters, or empty object if no match
|
|
190
|
+
* @throws {URLPatternUnavailableError} When URLPattern is absent and the route is parameterized
|
|
108
191
|
*/
|
|
109
192
|
protected extractParams(pathname: string, stateId: string): Record<string, string>;
|
|
193
|
+
/**
|
|
194
|
+
* Resolve an actor route value to a concrete URL path for navigation.
|
|
195
|
+
*
|
|
196
|
+
* Bridges that receive raw `actor.currentRoute` values in `navigateRouter`
|
|
197
|
+
* can call this to normalize stateIds (with or without `#` prefix) to paths.
|
|
198
|
+
* Returns `null` when navigation is not possible:
|
|
199
|
+
* - unknown stateId with no route map entry
|
|
200
|
+
* - parameterized or wildcard pattern (e.g. `/profile/:id`, `/docs/*`) — no
|
|
201
|
+
* concrete values available to build a real URL
|
|
202
|
+
* - non-path string that isn't a known stateId
|
|
203
|
+
*
|
|
204
|
+
* StateId lookups try both the `"#stateId"` and bare `"stateId"` forms
|
|
205
|
+
* automatically (via `lookupPathByStateId`), so custom route maps keyed on
|
|
206
|
+
* only one form still resolve.
|
|
207
|
+
*
|
|
208
|
+
* @param route - Raw actor route value (stateId, `#`-stateId, or concrete path)
|
|
209
|
+
* @returns Concrete URL path, or `null` if navigation should be skipped
|
|
210
|
+
*/
|
|
211
|
+
protected resolveNavigationPath(route: string): string | null;
|
|
212
|
+
/**
|
|
213
|
+
* Look up a route map path for an actor route value, trying both stateId forms.
|
|
214
|
+
*
|
|
215
|
+
* The actor may emit `"#stateId"` while a custom route map is keyed on the bare
|
|
216
|
+
* `"stateId"` form (or vice versa). `RouteMap` canonicalizes internally, but the
|
|
217
|
+
* constructor accepts any structural `{ getStateIdByPath, getPathByStateId }`
|
|
218
|
+
* object — plain test objects and adapter-specific maps are documented-legal —
|
|
219
|
+
* so both forms are tried automatically here: raw first, then the `#`-stripped
|
|
220
|
+
* form, then the `#`-prefixed form.
|
|
221
|
+
*
|
|
222
|
+
* @param route - Raw actor route value (stateId with or without `#` prefix)
|
|
223
|
+
* @returns The mapped path, or `null`/`undefined` if no form is registered
|
|
224
|
+
*/
|
|
225
|
+
private lookupPathByStateId;
|
|
110
226
|
/**
|
|
111
227
|
* Extract query parameters from URL search string.
|
|
112
228
|
*
|
|
@@ -115,17 +231,46 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
115
231
|
*/
|
|
116
232
|
protected extractQuery(search: string): Record<string, string>;
|
|
117
233
|
/**
|
|
118
|
-
* Navigate the framework router to the given path.
|
|
119
|
-
*
|
|
120
|
-
* Called when actor's currentRoute signal changes to a new path.
|
|
121
234
|
* Must trigger the framework router's navigation (e.g., router.navigate(path)).
|
|
235
|
+
*
|
|
236
|
+
* Contract: `path` is always a RESOLVED concrete URL path (e.g. `"/home"`).
|
|
237
|
+
* The base class resolves actor route values (stateIds like `"#home"`,
|
|
238
|
+
* parameterized patterns) via `resolveNavigationPath()` before calling this
|
|
239
|
+
* method and skips the push entirely when resolution fails — implementations
|
|
240
|
+
* must NOT re-resolve and can push the value as-is.
|
|
122
241
|
*/
|
|
123
242
|
protected abstract navigateRouter(path: string): void;
|
|
243
|
+
/**
|
|
244
|
+
* Sanitize and validate a raw URL pathname received from the router.
|
|
245
|
+
*
|
|
246
|
+
* Applies the path-length cap (2048 chars), strips query strings and fragments
|
|
247
|
+
* that may have been included in the pathname segment, and normalises
|
|
248
|
+
* consecutive slashes.
|
|
249
|
+
*
|
|
250
|
+
* Implementations that bypass `syncActorFromRouter()` (e.g. when using
|
|
251
|
+
* framework-native reactive watchers that receive pre-parsed route objects)
|
|
252
|
+
* MUST call this method before passing the path to any route-map lookup.
|
|
253
|
+
* `syncActorFromRouter()` calls this internally, so bridges that delegate
|
|
254
|
+
* to it do not need to call `sanitizePath` themselves.
|
|
255
|
+
*
|
|
256
|
+
* @param pathname - Raw URL pathname from the framework router.
|
|
257
|
+
* @returns Sanitized pathname, or `null` if the path is invalid / too long.
|
|
258
|
+
*/
|
|
259
|
+
protected sanitizePath(pathname: string): string | null;
|
|
124
260
|
/**
|
|
125
261
|
* Start watching for router location changes.
|
|
126
262
|
*
|
|
127
263
|
* Called by connect(). Should set up the framework-specific subscription
|
|
128
264
|
* for location changes and call syncActorFromRouter() on each change.
|
|
265
|
+
*
|
|
266
|
+
* **Implementations that call `syncActorFromRouter`** get path sanitization
|
|
267
|
+
* applied automatically — no extra work needed.
|
|
268
|
+
*
|
|
269
|
+
* **Implementations that bypass `syncActorFromRouter`** (e.g. `VueRouterBridge`,
|
|
270
|
+
* which builds the `play.route` event directly from framework-native route params)
|
|
271
|
+
* MUST call `this.sanitizePath(path)` and return early when it returns `null`.
|
|
272
|
+
* Skipping this allows oversized or malformed paths through without the length
|
|
273
|
+
* and content guards that protect the route-map lookup.
|
|
129
274
|
*/
|
|
130
275
|
protected abstract watchRouterChanges(): void;
|
|
131
276
|
/**
|
|
@@ -135,7 +280,7 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
135
280
|
*/
|
|
136
281
|
protected abstract unwatchRouterChanges(): void;
|
|
137
282
|
/**
|
|
138
|
-
* Return the router's current pathname at connect() time
|
|
283
|
+
* Return the router's current pathname at connect() time.
|
|
139
284
|
*
|
|
140
285
|
* Called once during connect() to perform the initial URL → actor sync.
|
|
141
286
|
* router.subscribe() only fires on *future* navigation events; it does not
|
|
@@ -144,9 +289,28 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
144
289
|
* should override this method so that deep-link / direct-URL loads drive the
|
|
145
290
|
* actor to the correct state instead of leaving it at its machine default.
|
|
146
291
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
292
|
+
* Return semantics:
|
|
293
|
+
* - `string` → router has a current path; base connect() will sync actor from router
|
|
294
|
+
* - `null` → router is active but has no current path yet; base connect() will sync router from actor
|
|
295
|
+
* - `undefined` → adapter handles initial sync itself and base connect() should stay out of the way
|
|
296
|
+
*
|
|
297
|
+
* The default returns `undefined`, preserving the previous behaviour for
|
|
298
|
+
* bridges that have not yet implemented this hook.
|
|
299
|
+
*/
|
|
300
|
+
protected getInitialRouterPath(): string | null | undefined;
|
|
301
|
+
/**
|
|
302
|
+
* Return the router's current search string at connect() time.
|
|
303
|
+
*
|
|
304
|
+
* Paired with `getInitialRouterPath()` — called once during connect() to pass
|
|
305
|
+
* the initial URL query string to `syncActorFromRouter()`. If the router's current
|
|
306
|
+
* URL has no search (or the subclass doesn't override this), returns `undefined`
|
|
307
|
+
* and `syncActorFromRouter` will produce an empty `query: {}` in the event.
|
|
308
|
+
*
|
|
309
|
+
* Subclasses that override `getInitialRouterPath()` and have a query string
|
|
310
|
+
* available should also override this method.
|
|
311
|
+
*
|
|
312
|
+
* @returns URL search string (e.g. `"?tab=security"`), or `undefined` if not available.
|
|
149
313
|
*/
|
|
150
|
-
protected
|
|
314
|
+
protected getInitialRouterSearch(): string | undefined;
|
|
151
315
|
}
|
|
152
316
|
//# sourceMappingURL=router-bridge-base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-bridge-base.d.ts","sourceRoot":"","sources":["../src/router-bridge-base.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"router-bridge-base.d.ts","sourceRoot":"","sources":["../src/router-bridge-base.ts"],"names":[],"mappings":"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"}
|