@xmachines/play-router 2.1.0 → 2.2.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 +270 -11
- package/dist/base-path.d.ts +209 -0
- package/dist/base-path.d.ts.map +1 -0
- package/dist/base-path.js +418 -0
- package/dist/base-path.js.map +1 -0
- package/dist/base-route-map.d.ts.map +1 -1
- package/dist/base-route-map.js +5 -0
- package/dist/base-route-map.js.map +1 -1
- package/dist/errors.d.ts +87 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +97 -4
- package/dist/errors.js.map +1 -1
- package/dist/framework-params.d.ts +144 -0
- package/dist/framework-params.d.ts.map +1 -0
- package/dist/framework-params.js +291 -0
- package/dist/framework-params.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/provider-lifecycle.d.ts +179 -0
- package/dist/provider-lifecycle.d.ts.map +1 -0
- package/dist/provider-lifecycle.js +153 -0
- package/dist/provider-lifecycle.js.map +1 -0
- package/dist/query.d.ts +49 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +59 -0
- package/dist/query.js.map +1 -1
- package/dist/router-bridge-base.d.ts +353 -15
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +998 -83
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/url-pattern-utils.d.ts +0 -30
- package/dist/url-pattern-utils.d.ts.map +1 -1
- package/dist/url-pattern-utils.js +52 -1
- package/dist/url-pattern-utils.js.map +1 -1
- package/package.json +4 -4
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
* @see [Play RFC](../../docs/rfc/play.md) - invariant INV-04
|
|
44
44
|
*/
|
|
45
45
|
import { Signal } from "@xmachines/play-signals";
|
|
46
|
-
import type { RoutableActor } from "./types.js";
|
|
47
|
-
import type
|
|
46
|
+
import type { MountableRouterBridge, RoutableActor } from "./types.js";
|
|
47
|
+
import { type BasePathOptions } from "./base-path.js";
|
|
48
48
|
/**
|
|
49
49
|
* The narrow interface of the TC39 Signal watcher. `RouterBridgeBase` uses it to
|
|
50
50
|
* observe each change of `actor.currentRoute`.
|
|
@@ -63,14 +63,7 @@ export interface RouteWatcherHandle {
|
|
|
63
63
|
/** Stops the observation and frees the watcher. */
|
|
64
64
|
unwatch(): void;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
* The abstract base class of every router adapter bridge of `@xmachines`.
|
|
68
|
-
*
|
|
69
|
-
* The class implements the RouterBridge protocol, and it holds every part of the
|
|
70
|
-
* bridge logic that the adapters share. A subclass implements the 3 abstract methods
|
|
71
|
-
* that are different in each framework, and it implements nothing more.
|
|
72
|
-
*/
|
|
73
|
-
export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
66
|
+
export declare abstract class RouterBridgeBase implements MountableRouterBridge {
|
|
74
67
|
protected readonly actor: RoutableActor;
|
|
75
68
|
protected readonly routeMap: {
|
|
76
69
|
getStateIdByPath(path: string): string | null | undefined;
|
|
@@ -78,7 +71,35 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
78
71
|
};
|
|
79
72
|
protected isConnected: boolean;
|
|
80
73
|
protected hasConnectedOnce: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The machine-side location that the router holds, as far as this bridge knows. It
|
|
76
|
+
* carries the query and the fragment, and it carries NO `basePath`.
|
|
77
|
+
*
|
|
78
|
+
* Only code that saw a location, or that wrote one, writes this field:
|
|
79
|
+
* `performInitialSync` when it finds the two sides in step, `takeReturnUnderMount`
|
|
80
|
+
* for the same test under a mount, `pushResolvedRoute` after it resolves a route to
|
|
81
|
+
* a concrete path, and `syncActorFromRouter` for the location that the router
|
|
82
|
+
* reports. `null` means that this bridge placed no location yet, so it knows nothing
|
|
83
|
+
* about the address bar, and every test against this field must then answer "no".
|
|
84
|
+
*
|
|
85
|
+
* The field held the route of the actor as well, from the seed of the constructor
|
|
86
|
+
* and from a route that resolves to no URL. Those two values describe no location
|
|
87
|
+
* that a router ever showed, and each reader of this field asks about a location, so
|
|
88
|
+
* each of them read them wrongly. {@link lastActorRoute} holds them now.
|
|
89
|
+
*/
|
|
81
90
|
protected lastSyncedPath: string | null;
|
|
91
|
+
/**
|
|
92
|
+
* The raw value of `actor.currentRoute` that this bridge accounted for already.
|
|
93
|
+
*
|
|
94
|
+
* The field answers ONE question: "did the bridge see this value of the signal
|
|
95
|
+
* before?". It is therefore a string, and not a location — a route that resolves to
|
|
96
|
+
* no URL, such as an unknown stateId or a parameterized pattern, still stops a
|
|
97
|
+
* second attempt at the same value.
|
|
98
|
+
*
|
|
99
|
+
* The constructor and `connect()` seed it, so that the first fire of the watcher of
|
|
100
|
+
* the signal pushes nothing for a route that moved nowhere.
|
|
101
|
+
*/
|
|
102
|
+
protected lastActorRoute: string | null;
|
|
82
103
|
/**
|
|
83
104
|
* The flag guards `syncActorFromRouter` against a re-entrant call from a guard
|
|
84
105
|
* redirect of the actor itself. Such a call has this sequence: the bridge sends to
|
|
@@ -87,11 +108,73 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
87
108
|
*
|
|
88
109
|
* The flag is NOT the echo suppression of the direction from the actor to the
|
|
89
110
|
* router. `lastSyncedPath` does that work alone: the bridge writes it before the
|
|
90
|
-
* `navigateRouter()` call. Therefore each router callback of the same
|
|
91
|
-
* the
|
|
111
|
+
* `navigateRouter()` call. Therefore each router callback of the same location stops
|
|
112
|
+
* at the {@link isEchoOfLastSync} test in `syncActorFromRouter`.
|
|
92
113
|
*/
|
|
93
114
|
protected isProcessingNavigation: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Who owns the address bar, and what this bridge owes it.
|
|
117
|
+
*
|
|
118
|
+
* - `"under-mount"` — the location lies under the mount, so this bridge owns the
|
|
119
|
+
* address bar and it writes the location for every route of its actor.
|
|
120
|
+
* - `"outside"` — the location lies outside the mount, so the HOST owns it. The
|
|
121
|
+
* bridge sends no event and it corrects no URL, which is what lets the routes of
|
|
122
|
+
* the host and the routes of the machine share one router.
|
|
123
|
+
* - `"outside-push-owed"` — outside, AND the route of the actor changed while the
|
|
124
|
+
* host owned the address bar. The push waits here instead of going to the router,
|
|
125
|
+
* and {@link RouterBridgeBase.takeReturnUnderMount} spends it when the location
|
|
126
|
+
* comes back. The machine therefore keeps its place while the user reads a page of
|
|
127
|
+
* the host.
|
|
128
|
+
*
|
|
129
|
+
* ONE value, and not a latch plus a memory. The memory only ever means something
|
|
130
|
+
* while the latch is up, so two fields could hold a fourth combination that says
|
|
131
|
+
* "the bridge owns the address bar AND a push waits for a return" — a state with no
|
|
132
|
+
* meaning, which a return would spend at a location the router already holds. Two
|
|
133
|
+
* writes also cannot happen together: every site that lowered the latch wrote the
|
|
134
|
+
* memory on the NEXT line, and the value between the two lines was that fourth
|
|
135
|
+
* combination. `takeReturnUnderMount` reads the value it replaces instead.
|
|
136
|
+
*
|
|
137
|
+
* `syncActorFromRouter` is the one place that sees a location, so it is the one
|
|
138
|
+
* place that decides between `"under-mount"` and `"outside"`.
|
|
139
|
+
* `performInitialSync` decides it for the location that the page loaded with.
|
|
140
|
+
*
|
|
141
|
+
* A bridge with no mount never leaves its mount: `stripBasePath` returns the path
|
|
142
|
+
* unchanged for an empty prefix, and never `null`. The value therefore stays
|
|
143
|
+
* `"under-mount"` for every bridge that takes no `basePath`.
|
|
144
|
+
*/
|
|
145
|
+
private mountState;
|
|
146
|
+
/**
|
|
147
|
+
* True once the first synchronization ran against a location INSIDE the mount.
|
|
148
|
+
*
|
|
149
|
+
* `performInitialSync` separates a deep link from a restore, and it can make that
|
|
150
|
+
* decision only for a location that lies under the mount. A bridge that connects
|
|
151
|
+
* while the host owns the address bar therefore reaches no decision at all: it
|
|
152
|
+
* raises the latch and returns. The flag says that the decision is still owed, and
|
|
153
|
+
* `syncActorFromRouter` pays it at the first location that IS under the mount —
|
|
154
|
+
* through {@link RouterBridgeBase.takeReturnUnderMount} for a RETURN, which needs the
|
|
155
|
+
* decision itself, and directly for every other location under the mount.
|
|
156
|
+
*
|
|
157
|
+
* A bridge with no mount never leaves its mount, so `performInitialSync` writes this
|
|
158
|
+
* flag on the first `connect()` and nothing reads it again.
|
|
159
|
+
*/
|
|
160
|
+
private hasSynchronizedUnderMount;
|
|
94
161
|
protected routeWatcher: RouteWatcherHandle | null;
|
|
162
|
+
/**
|
|
163
|
+
* The mount point of the machine inside the host router: the resolved prefix, and
|
|
164
|
+
* the values of each `:param` of that prefix.
|
|
165
|
+
*
|
|
166
|
+
* The prefix lives here, and NOT in the route map, for two reasons. A route map is
|
|
167
|
+
* static, it is shared, and it holds an LRU cache inside, while a mount point is
|
|
168
|
+
* dynamic. One route map therefore serves every `machineId` without a rebuild. And
|
|
169
|
+
* the bridge is the boundary between a location of the host router and a path of
|
|
170
|
+
* the machine already, which is exactly what the prefix separates.
|
|
171
|
+
*
|
|
172
|
+
* `lastSyncedPath` stays MACHINE-side, and it carries no prefix. The code adds the
|
|
173
|
+
* prefix at the one `navigateRouter()` call in `pushResolvedRoute`, and it removes
|
|
174
|
+
* the prefix at the one inbound entry point `syncActorFromRouter`. Every test of
|
|
175
|
+
* the echo suppression therefore keeps comparing like with like.
|
|
176
|
+
*/
|
|
177
|
+
private mount;
|
|
95
178
|
/**
|
|
96
179
|
* @param actor - A `RoutableActor`, with `currentRoute`, `initialRoute`, and `send`.
|
|
97
180
|
* @param routeMap - The route map of both directions, for the resolution between a
|
|
@@ -104,7 +187,113 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
104
187
|
constructor(actor: RoutableActor, routeMap: {
|
|
105
188
|
getStateIdByPath(path: string): string | null | undefined;
|
|
106
189
|
getPathByStateId(id: string): string | null | undefined;
|
|
107
|
-
});
|
|
190
|
+
}, options?: BasePathOptions);
|
|
191
|
+
/**
|
|
192
|
+
* The resolved URL prefix that the machine of this bridge is mounted under, or `""`
|
|
193
|
+
* when the machine owns the complete router.
|
|
194
|
+
*/
|
|
195
|
+
get basePath(): string;
|
|
196
|
+
/**
|
|
197
|
+
* The resolved values of the `:param` segments of the mount, or `{}` for a prefix
|
|
198
|
+
* without a param.
|
|
199
|
+
*
|
|
200
|
+
* These params belong to the HOST: the host wrote the prefix and resolved the
|
|
201
|
+
* values, and they describe the route of the host and not the route of the machine.
|
|
202
|
+
* They therefore travel in NO `play.route` event. The machine is authoritative over
|
|
203
|
+
* its own params, and `event.params` holds what the pattern of the machine
|
|
204
|
+
* declares, and nothing else.
|
|
205
|
+
*
|
|
206
|
+
* They live here instead, because a value that reached the actor on a navigation
|
|
207
|
+
* ALONE would go stale: `setBasePath()` can move the mount while the machine stays
|
|
208
|
+
* on the same route, no event goes out, and `event.params` would then contradict
|
|
209
|
+
* `basePath`. A read of this accessor cannot go stale.
|
|
210
|
+
*
|
|
211
|
+
* A machine that needs the identity of its host — a `machineId`, a tenant — takes
|
|
212
|
+
* it through the `input` of the actor, where it belongs: that identity decides
|
|
213
|
+
* WHICH machine runs, and it is not a param of a route inside the machine.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* bridge.basePath; // "/abc123/play"
|
|
218
|
+
* bridge.basePathParams; // { machineId: "abc123" }
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
get basePathParams(): Readonly<Record<string, string>>;
|
|
222
|
+
/**
|
|
223
|
+
* Moves the machine to a different mount point, while it stays connected.
|
|
224
|
+
*
|
|
225
|
+
* This is the "load and unload" half of a shared router. It moves WHERE an actor is
|
|
226
|
+
* mounted, and never WHICH actor is mounted: the actor, the route map, and its LRU
|
|
227
|
+
* cache all stay, and nothing goes away.
|
|
228
|
+
*
|
|
229
|
+
* An actor never changes identity. A prefix that IDENTIFIES the actor — a
|
|
230
|
+
* `machineId` that names the document it runs — therefore never moves through this
|
|
231
|
+
* method: a new identity is a new actor, and a new actor takes a new bridge, because
|
|
232
|
+
* `connect()` permits one bridge for each actor. The segments that move here are the
|
|
233
|
+
* ones that LOCATE: a region, a locale, a tenant, a workspace slug.
|
|
234
|
+
*
|
|
235
|
+
* The call then brings the location in step with the NEW prefix. A location inside
|
|
236
|
+
* the new mount runs the same first-synchronization decision as `connect()`: it
|
|
237
|
+
* drives the actor, and a restore keeps the route of the actor. A location OUTSIDE
|
|
238
|
+
* the new mount is the old mount in practice, so the bridge writes the new one
|
|
239
|
+
* itself, and it keeps the route of the actor while it does so — nothing else moves
|
|
240
|
+
* the address bar. A call that resolves to the current prefix changes no location,
|
|
241
|
+
* and it therefore reconciles nothing.
|
|
242
|
+
*
|
|
243
|
+
* @param basePath - The new prefix, as a pattern or as a concrete path. An absent
|
|
244
|
+
* value, `""`, and `"/"` all remove the prefix, and they give the machine the
|
|
245
|
+
* complete router again.
|
|
246
|
+
* @param basePathParams - The values of the `:param` segments of `basePath`.
|
|
247
|
+
* @throws {InvalidBasePathError} For a prefix that resolves to one concrete path never.
|
|
248
|
+
* @throws {MissingBasePathParamError} When a `:param` of the prefix has no value.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```typescript
|
|
252
|
+
* // The host moved this actor to another place in its URL space. `machineId` names
|
|
253
|
+
* // WHICH document the actor runs, and it does not change: a new identity is a new
|
|
254
|
+
* // actor, and therefore a new bridge, because one actor takes one bridge.
|
|
255
|
+
* bridge.setBasePath("/:region/:machineId/play", { region: "us", machineId });
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
setBasePath(basePath?: BasePathOptions["basePath"], basePathParams?: BasePathOptions["basePathParams"]): void;
|
|
259
|
+
/**
|
|
260
|
+
* Brings the location of the router in step with a mount that just moved.
|
|
261
|
+
*
|
|
262
|
+
* **The location lies under the new mount:** run the same first-synchronization
|
|
263
|
+
* decision as `connect()`, so a location inside the mount drives the actor and a
|
|
264
|
+
* restore keeps the route of the actor.
|
|
265
|
+
*
|
|
266
|
+
* **The location lies outside it:** the bridge writes the new mount ITSELF, and it
|
|
267
|
+
* keeps the route of the actor while it does so. A host that re-points a mount
|
|
268
|
+
* changes no location by itself, and nothing else moves the address bar. Without
|
|
269
|
+
* this push the machine sits at a mount that no location is under: it hears no
|
|
270
|
+
* `play.route`, `lastSyncedPath` is clear already, and the stale prefix stays in the
|
|
271
|
+
* URL until the route of the actor happens to change — which then jumps the URL with
|
|
272
|
+
* no reason that the history of the user explains.
|
|
273
|
+
*
|
|
274
|
+
* `pushResolvedRoute` writes `lastSyncedPath` before it navigates, so the callback
|
|
275
|
+
* of the router for this push stops at the echo suppression, and it sends no event.
|
|
276
|
+
*/
|
|
277
|
+
private reconcileLocationWithMount;
|
|
278
|
+
/**
|
|
279
|
+
* Pushes a route to the router, and it holds {@link isProcessingNavigation} across
|
|
280
|
+
* the write.
|
|
281
|
+
*
|
|
282
|
+
* Every corrective push of the bridge needs that flag: `navigateRouter` makes the
|
|
283
|
+
* router call `syncActorFromRouter` again inside it, and a router that NORMALIZES the
|
|
284
|
+
* location it receives — a `validateSearch` that injects a default, a trailing slash
|
|
285
|
+
* that a history adds — reports a value that the echo test misses. The bridge would
|
|
286
|
+
* then answer its own write with a `play.route` event, and on a press of BACK that
|
|
287
|
+
* answer is a history entry that FORWARD cannot return to.
|
|
288
|
+
*
|
|
289
|
+
* The one push that does NOT come through here is `syncRouterFromActor`: that push
|
|
290
|
+
* follows the actor, and the echo suppression of `lastSyncedPath` is what stops it.
|
|
291
|
+
*
|
|
292
|
+
* @param route - The raw value of the actor route.
|
|
293
|
+
* @param resolved - The concrete path of that route, when the caller resolved it
|
|
294
|
+
* already. `undefined` resolves it in `pushResolvedRoute`.
|
|
295
|
+
*/
|
|
296
|
+
private pushHoldingNavigation;
|
|
108
297
|
/**
|
|
109
298
|
* Connects the router bridge to the Actor.
|
|
110
299
|
*
|
|
@@ -113,7 +302,7 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
113
302
|
* in its own way.
|
|
114
303
|
*
|
|
115
304
|
* The order of these steps is part of the contract of the bridge:
|
|
116
|
-
* - The constructor seeds `
|
|
305
|
+
* - The constructor seeds `lastActorRoute` from `actor.currentRoute`, and it records no location
|
|
117
306
|
* - The method installs the actor watcher before the router subscriptions of the adapter
|
|
118
307
|
* - The first synchronization then separates a deep link from a restore, with `actor.initialRoute`
|
|
119
308
|
*
|
|
@@ -122,6 +311,33 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
122
311
|
* `connect()`.
|
|
123
312
|
*/
|
|
124
313
|
connect(): void;
|
|
314
|
+
/**
|
|
315
|
+
* Brings the actor and the router in step one time, and decides which side wins.
|
|
316
|
+
*
|
|
317
|
+
* The method is separate from `connect()`, so that `connect()` can roll the whole
|
|
318
|
+
* first synchronization back when it throws. See the `try` block there.
|
|
319
|
+
*
|
|
320
|
+
* The first synchronization: the direction from the router to the actor has the
|
|
321
|
+
* priority over the other direction.
|
|
322
|
+
*
|
|
323
|
+
* The page can load on a URL that is not the initial state of the actor. For
|
|
324
|
+
* example, the user types "/about", or the user follows a deep link. The router
|
|
325
|
+
* holds the correct path then, and the actor is still at its initial state ("/").
|
|
326
|
+
* The code must therefore drive the actor to the URL, and it must not write the
|
|
327
|
+
* default of the actor to the URL.
|
|
328
|
+
*
|
|
329
|
+
* router.subscribe() fires on a *later* navigation event only. It does NOT fire for
|
|
330
|
+
* the location that the page loaded. A subclass that can read the current location
|
|
331
|
+
* of its router synchronously overrides getInitialRouterPath().
|
|
332
|
+
*
|
|
333
|
+
* @param initialRouterPath - The location of the router. Every caller reads it and
|
|
334
|
+
* passes it, and this parameter carries NO default: a default fires for an
|
|
335
|
+
* explicit `undefined` too, so `reconcileLocationWithMount` asked the adapter a
|
|
336
|
+
* SECOND time for exactly the value that says "the adapter does the first
|
|
337
|
+
* synchronization itself" — and a read belongs to the adapter, which can walk the
|
|
338
|
+
* state of a router and answer differently on the second call.
|
|
339
|
+
*/
|
|
340
|
+
private performInitialSync;
|
|
125
341
|
/**
|
|
126
342
|
* Disconnects the router bridge from the Actor.
|
|
127
343
|
*
|
|
@@ -150,6 +366,68 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
150
366
|
* redirect.
|
|
151
367
|
*/
|
|
152
368
|
protected syncRouterFromActor(route: string | null | unknown): void;
|
|
369
|
+
/**
|
|
370
|
+
* Records that the location lies outside the mount, so the HOST owns the address bar.
|
|
371
|
+
*
|
|
372
|
+
* A bridge that is outside ALREADY keeps the value it holds: `"outside-push-owed"`
|
|
373
|
+
* says that a push waits for the address bar to come back, and a second location of
|
|
374
|
+
* the host is no reason to forget it.
|
|
375
|
+
*/
|
|
376
|
+
private markOutside;
|
|
377
|
+
/**
|
|
378
|
+
* Takes the return of the address bar under the mount.
|
|
379
|
+
*
|
|
380
|
+
* The actor wins that return when it moved while the host owned the location AND the
|
|
381
|
+
* address bar came back to the location it still held: the method pushes the route of
|
|
382
|
+
* the actor, and the caller sends no `play.route` event. A return to any other route
|
|
383
|
+
* of the mount belongs to the host, so the memory goes and the caller drives the
|
|
384
|
+
* actor from the location instead.
|
|
385
|
+
*
|
|
386
|
+
* The method also pays the first synchronization that the mount still owes. A bridge
|
|
387
|
+
* that connected while the host owned the address bar reached no decision between a
|
|
388
|
+
* deep link and a restore: `performInitialSync` can decide only for a location under
|
|
389
|
+
* the mount, and it found none. This is the first such location, so the decision runs
|
|
390
|
+
* here, with the same rule.
|
|
391
|
+
*
|
|
392
|
+
* The method holds `isProcessingNavigation` across each push, because
|
|
393
|
+
* `navigateRouter` makes the router call `syncActorFromRouter` again.
|
|
394
|
+
*
|
|
395
|
+
* @returns `true` when the method handled the location, so the caller returns.
|
|
396
|
+
*/
|
|
397
|
+
private takeReturnUnderMount;
|
|
398
|
+
/**
|
|
399
|
+
* Writes the route of the actor to a router that just came under the mount.
|
|
400
|
+
*
|
|
401
|
+
* The method holds `isProcessingNavigation` across the push, because
|
|
402
|
+
* `navigateRouter` makes the router call `syncActorFromRouter` again inside it.
|
|
403
|
+
*
|
|
404
|
+
* A re-entrant call pushes nothing inside another push, and the memory of a
|
|
405
|
+
* suppressed push DOES survive that case: the outer push is still in flight, and a
|
|
406
|
+
* clear here would throw the remembered route away with nothing left to replay it.
|
|
407
|
+
*
|
|
408
|
+
* @param route - The raw value of the actor route.
|
|
409
|
+
* @returns `true` when the method wrote the location, so that the caller returns and
|
|
410
|
+
* sends no event. `false` when the route resolves to no concrete path, so that the
|
|
411
|
+
* caller drives the actor from the location instead.
|
|
412
|
+
*/
|
|
413
|
+
private pushUnderMount;
|
|
414
|
+
/**
|
|
415
|
+
* Tests a route of the actor against the last synchronized location.
|
|
416
|
+
*
|
|
417
|
+
* The exact string test covers a stateId, for example `"#home"`, which
|
|
418
|
+
* `pushResolvedRoute` writes to the field when it cannot resolve a path. The query
|
|
419
|
+
* test covers a location that carries a query: the order of the keys of a query is
|
|
420
|
+
* not significant, so `?b=2&a=1` and `?a=1&b=2` are the same location, and the
|
|
421
|
+
* bridge must push neither of them back.
|
|
422
|
+
*
|
|
423
|
+
* The FRAGMENT counts in this direction, and the inbound test ignores it. The two
|
|
424
|
+
* are different questions. `navigateRouter` writes the whole value that the actor
|
|
425
|
+
* gave — `buildRouteUrl` appends `context.hash` — so `"/docs"` and `"/docs#intro"`
|
|
426
|
+
* are two locations to write, and a test that read them as one left the fragment out
|
|
427
|
+
* of the address bar for ever. `isEchoOfLastSync` compares against what the ROUTER
|
|
428
|
+
* reports, and `sanitizePathname` has removed the fragment from that already.
|
|
429
|
+
*/
|
|
430
|
+
private testEchoOfLastPush;
|
|
153
431
|
/**
|
|
154
432
|
* Resolves an actor route to its concrete URL path, and pushes that path to the
|
|
155
433
|
* router.
|
|
@@ -165,6 +443,11 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
165
443
|
* literal `*` into the browser URL. The method still writes the raw route to
|
|
166
444
|
* lastSyncedPath. Therefore the dedup guard fires correctly on the next identical
|
|
167
445
|
* value of the signal.
|
|
446
|
+
*
|
|
447
|
+
* @param route - The raw value of the actor route.
|
|
448
|
+
* @param resolved - The concrete path of that route. The default resolves it here. A
|
|
449
|
+
* caller that resolved it already passes it, so that `resolveNavigationPath` runs
|
|
450
|
+
* one time: the method is `protected`, and a subclass can override it.
|
|
168
451
|
*/
|
|
169
452
|
private pushResolvedRoute;
|
|
170
453
|
/**
|
|
@@ -214,10 +497,43 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
214
497
|
* through `lookupPathByStateId`. Therefore a route map of your own that holds one
|
|
215
498
|
* form only still resolves the path.
|
|
216
499
|
*
|
|
500
|
+
* The result is MACHINE-side: it carries no `basePath`, because it is also the
|
|
501
|
+
* value that `lastSyncedPath` stores and that the code compares against an actor
|
|
502
|
+
* route. The code adds the prefix one time, at the `navigateRouter()` call in
|
|
503
|
+
* `pushResolvedRoute`.
|
|
504
|
+
*
|
|
217
505
|
* @param route - The raw value of the actor route: a stateId, a stateId with a `#`, or a concrete path
|
|
218
|
-
* @returns The concrete URL path, or `null` when the bridge must skip the navigation
|
|
506
|
+
* @returns The concrete machine-side URL path, or `null` when the bridge must skip the navigation
|
|
219
507
|
*/
|
|
220
508
|
protected resolveNavigationPath(route: string): string | null;
|
|
509
|
+
/**
|
|
510
|
+
* Tells you whether the actor already stands at a location of the router.
|
|
511
|
+
*
|
|
512
|
+
* The `currentRoute` of the actor can be a stateId, for example `"#app.home"`, while a
|
|
513
|
+
* location of the router is a URL path, for example `"/home"`. Therefore the method
|
|
514
|
+
* resolves the path to a stateId before it compares. Without this step, a false "the
|
|
515
|
+
* two are different" starts a synchronization that nothing needs — and, in
|
|
516
|
+
* `takeReturnUnderMount`, it reads a location that the HOST chose as a restore of the
|
|
517
|
+
* actor and writes over it.
|
|
518
|
+
*
|
|
519
|
+
* The actor is at the location in three cases: the stateId of the match IS the actor
|
|
520
|
+
* route, the path of the match IS the actor route, or the actor route is a stateId
|
|
521
|
+
* that resolves to the same registered path as the stateId of the match. Each lookup
|
|
522
|
+
* goes through {@link lookupPathByStateId}, which tries the form `"#stateId"` and also
|
|
523
|
+
* the bare form `"stateId"`. Therefore a route map with `"#about"` recognizes an actor
|
|
524
|
+
* route of `"about"`, and the opposite also works, and this is correct for a
|
|
525
|
+
* structural map of your own that holds one form. An actor route with a concrete path,
|
|
526
|
+
* which starts with `"/"`, goes to a direct comparison with the path of the router
|
|
527
|
+
* only.
|
|
528
|
+
*
|
|
529
|
+
* Each lookup runs ONE time. `getPathByStateId` belongs to the route map: a map of a
|
|
530
|
+
* consumer can answer differently on a second call, and `RouteMap` pays a cache lookup
|
|
531
|
+
* for each of them.
|
|
532
|
+
*
|
|
533
|
+
* @param machinePath - The machine half of the location of the router.
|
|
534
|
+
* @param actorRoute - The raw value of the actor route.
|
|
535
|
+
*/
|
|
536
|
+
private isActorAtLocation;
|
|
221
537
|
/**
|
|
222
538
|
* Returns the path of the route map for a value of an actor route, and it tries both
|
|
223
539
|
* forms of a stateId.
|
|
@@ -241,6 +557,28 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
|
|
|
241
557
|
* @returns The query parameters of the read, or an empty object
|
|
242
558
|
*/
|
|
243
559
|
protected extractQuery(search: string): Record<string, string>;
|
|
560
|
+
/**
|
|
561
|
+
* True when a location of the router is the location that this bridge pushed.
|
|
562
|
+
*
|
|
563
|
+
* `lastSyncedPath` holds the value of `actor.currentRoute`, and that value carries
|
|
564
|
+
* the query string of `context.query`, for example `"/dates?trip=one-way"`. A router
|
|
565
|
+
* callback reports the pathname and the search SEPARATELY, and `sanitizePathname`
|
|
566
|
+
* removes the query. A comparison of the pathname alone therefore misses for every
|
|
567
|
+
* route with a query, and the push of this bridge comes back as a `play.route` event
|
|
568
|
+
* of its own navigation. That echo is not only wasted work: it writes to the signal
|
|
569
|
+
* graph from inside the watcher callback of the actor route, and the watcher then
|
|
570
|
+
* arms itself again on a computed that is dirty already, which ends the direction
|
|
571
|
+
* from the actor to the router for the rest of the session.
|
|
572
|
+
*
|
|
573
|
+
* The query is compared as a MAP, and not as a string: a framework can re-serialize
|
|
574
|
+
* a search string in another order, and `URLSearchParams` also accepts the form with
|
|
575
|
+
* a leading `?` and the form without one.
|
|
576
|
+
*
|
|
577
|
+
* @param sanitized - The clean pathname of the router callback, with no query.
|
|
578
|
+
* @param search - The search string of the router callback, when it has one.
|
|
579
|
+
* @returns True when the location is the echo of the last push of this bridge.
|
|
580
|
+
*/
|
|
581
|
+
private isEchoOfLastSync;
|
|
244
582
|
/**
|
|
245
583
|
* The method must start the navigation of the framework router, for example with a
|
|
246
584
|
* router.navigate(path) call.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-bridge-base.d.ts","sourceRoot":"","sources":["../src/router-bridge-base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,MAAM,EAAe,MAAM,yBAAyB,CAAC;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"router-bridge-base.d.ts","sourceRoot":"","sources":["../src/router-bridge-base.ts"],"names":[],"mappings":"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"}
|