@xmachines/play-router 2.0.0 → 2.1.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.
Files changed (58) hide show
  1. package/README.md +93 -76
  2. package/dist/base-route-map.d.ts +63 -57
  3. package/dist/base-route-map.d.ts.map +1 -1
  4. package/dist/base-route-map.js +65 -59
  5. package/dist/base-route-map.js.map +1 -1
  6. package/dist/build-tree.d.ts +13 -12
  7. package/dist/build-tree.d.ts.map +1 -1
  8. package/dist/build-tree.js +30 -28
  9. package/dist/build-tree.js.map +1 -1
  10. package/dist/create-route-map-from-tree.d.ts +15 -15
  11. package/dist/create-route-map-from-tree.js +15 -15
  12. package/dist/create-route-map.d.ts +18 -16
  13. package/dist/create-route-map.d.ts.map +1 -1
  14. package/dist/create-route-map.js +10 -9
  15. package/dist/create-route-map.js.map +1 -1
  16. package/dist/errors.d.ts +40 -38
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/errors.js +40 -38
  19. package/dist/errors.js.map +1 -1
  20. package/dist/extract-routes.d.ts +8 -7
  21. package/dist/extract-routes.d.ts.map +1 -1
  22. package/dist/extract-routes.js +31 -27
  23. package/dist/extract-routes.js.map +1 -1
  24. package/dist/find-route.d.ts +18 -15
  25. package/dist/find-route.d.ts.map +1 -1
  26. package/dist/find-route.js +42 -38
  27. package/dist/find-route.js.map +1 -1
  28. package/dist/index.d.ts +6 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +11 -10
  31. package/dist/index.js.map +1 -1
  32. package/dist/machine-to-graph.d.ts +3 -2
  33. package/dist/machine-to-graph.d.ts.map +1 -1
  34. package/dist/machine-to-graph.js +20 -19
  35. package/dist/machine-to-graph.js.map +1 -1
  36. package/dist/query.d.ts +39 -37
  37. package/dist/query.d.ts.map +1 -1
  38. package/dist/query.js +62 -57
  39. package/dist/query.js.map +1 -1
  40. package/dist/router-bridge-base.d.ts +208 -190
  41. package/dist/router-bridge-base.d.ts.map +1 -1
  42. package/dist/router-bridge-base.js +235 -211
  43. package/dist/router-bridge-base.js.map +1 -1
  44. package/dist/router-sync.d.ts +41 -35
  45. package/dist/router-sync.d.ts.map +1 -1
  46. package/dist/router-sync.js +53 -45
  47. package/dist/router-sync.js.map +1 -1
  48. package/dist/types.d.ts +165 -147
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/url-pattern-utils.d.ts +53 -47
  51. package/dist/url-pattern-utils.d.ts.map +1 -1
  52. package/dist/url-pattern-utils.js +61 -55
  53. package/dist/url-pattern-utils.js.map +1 -1
  54. package/dist/validate-routes.d.ts +32 -31
  55. package/dist/validate-routes.d.ts.map +1 -1
  56. package/dist/validate-routes.js +30 -29
  57. package/dist/validate-routes.js.map +1 -1
  58. package/package.json +6 -5
@@ -1,17 +1,18 @@
1
1
  /**
2
- * RouterBridgeBase — Abstract base class for all framework router adapters
2
+ * RouterBridgeBase — the abstract base class of every framework router adapter
3
3
  *
4
- * Captures the 90% identical logic from all existing router bridges:
5
- * - TC39 Signal watcher for actor → router direction
6
- * - `lastSyncedPath` for echo suppression in the actorrouter direction
7
- * - `isProcessingNavigation` flag for guard-redirect loop prevention in `syncActorFromRouter` only
8
- * - `syncRouterFromActor` / `syncActorFromRouter` with URL parameter extraction
9
- * - connect / disconnect lifecycle matching the RouterBridge protocol
4
+ * The class holds the logic that each router bridge shares, and that is 90% of the
5
+ * code of every bridge:
6
+ * - The TC39 Signal watcher of the direction from the actor to the router
7
+ * - `lastSyncedPath`, for the echo suppression in the direction from the actor to the router
8
+ * - The `isProcessingNavigation` flag, which stops a loop of a guard redirect in `syncActorFromRouter` only
9
+ * - `syncRouterFromActor` and `syncActorFromRouter`, with the read of each URL parameter
10
+ * - The connect and disconnect lifecycle of the RouterBridge protocol
10
11
  *
11
- * Subclasses implement only the 3 framework-specific abstract methods:
12
- * - navigateRouter(path): How to tell the framework router to navigate
13
- * - watchRouterChanges(): How to subscribe to router location changes
14
- * - unwatchRouterChanges(): How to unsubscribe from router location changes
12
+ * A subclass implements the 3 abstract methods of its framework only:
13
+ * - navigateRouter(path): how the bridge tells the framework router to navigate
14
+ * - watchRouterChanges(): how the bridge subscribes to each location change of the router
15
+ * - unwatchRouterChanges(): how the bridge cancels that subscription
15
16
  *
16
17
  * @example
17
18
  * ```typescript
@@ -39,35 +40,35 @@
39
40
  * }
40
41
  * ```
41
42
  *
42
- * @see [Play RFC](../../docs/rfc/play.md) - Invariant INV-04
43
+ * @see [Play RFC](../../docs/rfc/play.md) - invariant INV-04
43
44
  */
44
45
  import { Signal } from "@xmachines/play-signals";
45
46
  import type { RoutableActor } from "./types.js";
46
47
  import type { RouterBridge } from "./types.js";
47
48
  /**
48
- * Narrow interface for the TC39 Signal watcher used by `RouterBridgeBase` to
49
- * monitor `actor.currentRoute` changes.
49
+ * The narrow interface of the TC39 Signal watcher. `RouterBridgeBase` uses it to
50
+ * observe each change of `actor.currentRoute`.
50
51
  *
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
52
+ * The interface hides the complete `Signal.subtle.Watcher` surface. It exposes the
53
+ * two operations that `RouterBridgeBase` needs:
54
+ * - `watch(signal)` — it arms the watcher on one signal
55
+ * - `unwatch()` — it stops the watch and frees the resources
55
56
  *
56
- * Framework adapter subclasses never interact with this handle directly; it is
57
- * created and managed internally by `RouterBridgeBase`.
57
+ * A framework adapter subclass touches this handle never. `RouterBridgeBase` makes
58
+ * it and manages it internally.
58
59
  */
59
60
  export interface RouteWatcherHandle {
60
- /** Arm the watcher to observe the given signal. */
61
+ /** Arms the watcher on the given signal. */
61
62
  watch(signal: Signal.Computed<string | null>): void;
62
- /** Stop observing and release the watcher. */
63
+ /** Stops the observation and frees the watcher. */
63
64
  unwatch(): void;
64
65
  }
65
66
  /**
66
- * Abstract base class for all `@xmachines` router adapter bridges.
67
+ * The abstract base class of every router adapter bridge of `@xmachines`.
67
68
  *
68
- * Implements RouterBridge protocol and contains all common bridge logic.
69
- * Subclasses only need to implement the 3 abstract methods that differ
70
- * between frameworks.
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.
71
72
  */
72
73
  export declare abstract class RouterBridgeBase implements RouterBridge {
73
74
  protected readonly actor: RoutableActor;
@@ -79,237 +80,254 @@ export declare abstract class RouterBridgeBase implements RouterBridge {
79
80
  protected hasConnectedOnce: boolean;
80
81
  protected lastSyncedPath: string | null;
81
82
  /**
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).
85
- *
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`.
83
+ * The flag guards `syncActorFromRouter` against a re-entrant call from a guard
84
+ * redirect of the actor itself. Such a call has this sequence: the bridge sends to
85
+ * the actor, the signal fires, the bridge pushes to the router, and a second
86
+ * `syncActorFromRouter` call starts before the first one returns.
87
+ *
88
+ * The flag is NOT the echo suppression of the direction from the actor to the
89
+ * router. `lastSyncedPath` does that work alone: the bridge writes it before the
90
+ * `navigateRouter()` call. Therefore each router callback of the same path stops at
91
+ * the `sanitized === lastSyncedPath` test in `syncActorFromRouter`.
90
92
  */
91
93
  protected isProcessingNavigation: boolean;
92
94
  protected routeWatcher: RouteWatcherHandle | null;
93
95
  /**
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.
96
+ * @param actor - A `RoutableActor`, with `currentRoute`, `initialRoute`, and `send`.
97
+ * @param routeMap - The route map of both directions, for the resolution between a
98
+ * `stateId` and a `path`. Give `getStateIdByPath` and `getPathByStateId`. A
99
+ * framework adapter usually wraps the result of `createRouteMap(machine)`, or an
100
+ * equivalent value. The key of `getPathByStateId` is the form `"#stateId"` or the
101
+ * bare form `"stateId"`. The bridge tries both forms. Therefore an implementation
102
+ * of your own, for example a plain test object, handles one form only.
101
103
  */
102
104
  constructor(actor: RoutableActor, routeMap: {
103
105
  getStateIdByPath(path: string): string | null | undefined;
104
106
  getPathByStateId(id: string): string | null | undefined;
105
107
  });
106
108
  /**
107
- * Connect the router bridge to the Actor.
109
+ * Connects the router bridge to the Actor.
108
110
  *
109
- * Sets up the TC39 Signal watcher for actor router direction and
110
- * starts watching router changes (framework-specific).
111
+ * The method installs the TC39 Signal watcher of the direction from the actor to the
112
+ * router. It then starts the watch of the router changes, which each framework does
113
+ * in its own way.
111
114
  *
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`
115
+ * The order of these steps is part of the contract of the bridge:
116
+ * - The constructor seeds `lastSyncedPath` from `actor.currentRoute`
117
+ * - The method installs the actor watcher before the router subscriptions of the adapter
118
+ * - The first synchronization then separates a deep link from a restore, with `actor.initialRoute`
116
119
  *
117
- * Adapters that need custom initial-sync behavior should override
118
- * `getInitialRouterPath()` rather than reordering `connect()` steps.
120
+ * An adapter that needs a different behavior of the first synchronization overrides
121
+ * `getInitialRouterPath()`. It does not change the order of the steps of
122
+ * `connect()`.
119
123
  */
120
124
  connect(): void;
121
125
  /**
122
- * Disconnect the router bridge from the Actor.
126
+ * Disconnects the router bridge from the Actor.
123
127
  *
124
- * Stops signal watching and unregisters framework-specific router listener.
128
+ * The method stops the watch of the signal, and it removes the router listener of the
129
+ * framework.
125
130
  */
126
131
  disconnect(): void;
127
132
  /**
128
- * Sync router location when actor route signal changes.
129
- *
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.
133
+ * Writes the location of the router when the route signal of the actor changes.
134
+ *
135
+ * The method resolves the actor route to a concrete URL path, then it calls
136
+ * navigateRouter() for the navigation of the framework. When it cannot resolve the
137
+ * route, which happens for an unknown stateId and for a parameterized pattern
138
+ * without concrete values, it skips the push completely: `navigateRouter` receives a
139
+ * raw stateId or a pattern never.
140
+ *
141
+ * `lastSyncedPath` does the complete echo suppression, which stops the callback of
142
+ * the router from a send to the actor: the method writes the resolved path to
143
+ * `lastSyncedPath` before the `navigateRouter()` call. Therefore each
144
+ * `syncActorFromRouter` call of the same path stops at the
145
+ * `sanitized === lastSyncedPath` test, and it sends no event. This is correct for a
146
+ * synchronous callback and for an asynchronous callback.
147
+ *
148
+ * The method does NOT set `isProcessingNavigation`. That flag lives inside
149
+ * `syncActorFromRouter`, and it guards against a re-entrant loop of a guard
150
+ * redirect.
145
151
  */
146
152
  protected syncRouterFromActor(route: string | null | unknown): void;
147
153
  /**
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.
154
+ * Resolves an actor route to its concrete URL path, and pushes that path to the
155
+ * router.
156
+ *
157
+ * lastSyncedPath must hold the concrete path, because the callback of the router
158
+ * fires with the concrete path. The two values therefore match, and
159
+ * syncActorFromRouter stops. A raw stateId in that field, for example "#home", does
160
+ * not match the sanitized path ("/") in the watcher.
161
+ *
162
+ * When resolveNavigationPath returns null, which happens for a parameterized
163
+ * pattern, for a wildcard pattern, and for an unknown id, the method skips the push
164
+ * completely: a push of the raw value writes a stateId, a `:param` pattern, or a
165
+ * literal `*` into the browser URL. The method still writes the raw route to
166
+ * lastSyncedPath. Therefore the dedup guard fires correctly on the next identical
167
+ * value of the signal.
160
168
  */
161
169
  private pushResolvedRoute;
162
170
  /**
163
- * Sync actor state when router location changes.
164
- *
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.
171
+ * Writes the actor state when the location of the router changes.
172
+ *
173
+ * **A known path:** the method sends a `play.route` event to the actor, with the
174
+ * stateId of the match, the params, and the query. The `isProcessingNavigation` flag
175
+ * stops a circular update.
176
+ *
177
+ * **An unknown path, or a path with no entry in the map:** the method sends NO
178
+ * `play.route` event, and the actor state stays as it is. It corrects the browser URL
179
+ * instead, with a `navigateRouter(actor.currentRoute.get())` call. The URL therefore
180
+ * follows the actor state, also when the user types an invalid path in the address
181
+ * bar, or when the code pushes such a path during a session. The method writes the
182
+ * resolved concrete path to `lastSyncedPath` before the `navigateRouter` call.
183
+ * Therefore the callback of the router for that navigation stops at the guard of the
184
+ * echo suppression, and it sends no false event.
177
185
  */
178
186
  protected syncActorFromRouter(pathname: string, search?: string): void;
179
187
  /**
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).
186
- *
187
- * @param pathname - The actual URL path (e.g., '/profile/john')
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
188
+ * Reads the path parameters of a URL, with the URLPattern API.
189
+ *
190
+ * The method reads `globalThis.URLPattern` at run time, because this library imports
191
+ * no polyfill. When `URLPattern` is absent and the route of the match holds a
192
+ * parameterized segment, the method throws a `URLPatternUnavailableError`. A caller
193
+ * must therefore give a polyfill in an environment without the native URLPattern:
194
+ * Node.js < 24, and an older browser.
195
+ *
196
+ * @param pathname - The real URL path, for example '/profile/john'
197
+ * @param stateId - The stateId of the match, for the lookup of the route pattern
198
+ * @returns The path parameters of the read, or an empty object when nothing matches
199
+ * @throws {URLPatternUnavailableError} When URLPattern is absent and the route holds a parameter
191
200
  */
192
201
  protected extractParams(pathname: string, stateId: string): Record<string, string>;
193
202
  /**
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
203
+ * Resolves a value of an actor route to a concrete URL path for a navigation.
204
+ *
205
+ * A bridge that receives a raw `actor.currentRoute` value in `navigateRouter` calls
206
+ * this method to convert a stateId, with a `#` prefix or without one, into a path.
207
+ * The method returns `null` when a navigation is not possible:
208
+ * - an unknown stateId, with no entry in the route map
209
+ * - a parameterized pattern or a wildcard pattern, for example `/profile/:id` or
210
+ * `/docs/*`, because no concrete value is available for a real URL
211
+ * - a string that is no path and no known stateId
212
+ *
213
+ * A lookup of a stateId tries the form `"#stateId"` and the bare form `"stateId"`,
214
+ * through `lookupPathByStateId`. Therefore a route map of your own that holds one
215
+ * form only still resolves the path.
216
+ *
217
+ * @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
210
219
  */
211
220
  protected resolveNavigationPath(route: string): string | null;
212
221
  /**
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
222
+ * Returns the path of the route map for a value of an actor route, and it tries both
223
+ * forms of a stateId.
224
+ *
225
+ * The actor can emit `"#stateId"` while a route map of your own holds the bare form
226
+ * `"stateId"`, and also the opposite. `RouteMap` makes the canonical form
227
+ * internally, but the constructor accepts every structural
228
+ * `{ getStateIdByPath, getPathByStateId }` object: a plain test object and a map of
229
+ * one adapter are legal, and the documentation says so. Therefore this method tries
230
+ * both forms: the raw form first, then the form without the `#`, then the form with
231
+ * the `#`.
232
+ *
233
+ * @param route - The raw value of the actor route: a stateId, with a `#` prefix or without one
234
+ * @returns The path of the map, or `null` or `undefined` when no form is registered
224
235
  */
225
236
  private lookupPathByStateId;
226
237
  /**
227
- * Extract query parameters from URL search string.
238
+ * Reads the query parameters of a URL search string.
228
239
  *
229
- * @param search - URL search string (e.g., '?tab=security&page=1')
230
- * @returns Extracted query parameters or empty object
240
+ * @param search - The URL search string, for example '?tab=security&page=1'
241
+ * @returns The query parameters of the read, or an empty object
231
242
  */
232
243
  protected extractQuery(search: string): Record<string, string>;
233
244
  /**
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.
245
+ * The method must start the navigation of the framework router, for example with a
246
+ * router.navigate(path) call.
247
+ *
248
+ * The contract: `path` is always a RESOLVED concrete URL path, for example
249
+ * `"/home"`. The base class resolves each value of an actor route, which includes a
250
+ * stateId such as `"#home"` and a parameterized pattern, with
251
+ * `resolveNavigationPath()` before this call. It skips the push completely when the
252
+ * resolution fails. Therefore an implementation must NOT resolve the value again,
253
+ * and it pushes the value without a change.
241
254
  */
242
255
  protected abstract navigateRouter(path: string): void;
243
256
  /**
244
- * Sanitize and validate a raw URL pathname received from the router.
257
+ * Cleans a raw URL pathname of the router, and checks it.
245
258
  *
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.
259
+ * The method applies the limit of the path length, which is 2048 characters. It
260
+ * removes a query string and a fragment from the segment of the pathname, and it
261
+ * normalizes each sequence of slashes.
249
262
  *
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.
263
+ * An implementation that passes around `syncActorFromRouter()`, for example with a
264
+ * reactive watcher of its framework that receives a route object of a parse before,
265
+ * MUST call this method before it gives the path to a lookup in the route map.
266
+ * `syncActorFromRouter()` calls the method internally. Therefore a bridge that uses
267
+ * that method calls `sanitizePath` never itself.
255
268
  *
256
- * @param pathname - Raw URL pathname from the framework router.
257
- * @returns Sanitized pathname, or `null` if the path is invalid / too long.
269
+ * @param pathname - The raw URL pathname of the framework router.
270
+ * @returns The clean pathname, or `null` when the path is invalid or too long.
258
271
  */
259
272
  protected sanitizePath(pathname: string): string | null;
260
273
  /**
261
- * Start watching for router location changes.
274
+ * Starts the watch of the location changes of the router.
262
275
  *
263
- * Called by connect(). Should set up the framework-specific subscription
264
- * for location changes and call syncActorFromRouter() on each change.
276
+ * `connect()` calls this method. The method installs the subscription of the
277
+ * framework for each location change, and it calls syncActorFromRouter() on each
278
+ * change.
265
279
  *
266
- * **Implementations that call `syncActorFromRouter`** get path sanitization
267
- * applied automatically no extra work needed.
280
+ * **An implementation that calls `syncActorFromRouter`** receives the clean path
281
+ * automatically, and it needs no more work.
268
282
  *
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.
283
+ * **An implementation that passes around `syncActorFromRouter`**, for example
284
+ * `VueRouterBridge`, which builds the `play.route` event from the route params of its
285
+ * framework, MUST call `this.sanitizePath(path)` and return at once when that
286
+ * method returns `null`. Without this call, a path that is too long or malformed
287
+ * passes the guards of the length and of the contents, and those guards protect the
288
+ * lookup in the route map.
274
289
  */
275
290
  protected abstract watchRouterChanges(): void;
276
291
  /**
277
- * Stop watching for router location changes.
292
+ * Stops the watch of the location changes of the router.
278
293
  *
279
- * Called by disconnect(). Should clean up the framework-specific subscription.
294
+ * `disconnect()` calls this method. The method removes the subscription of the
295
+ * framework.
280
296
  */
281
297
  protected abstract unwatchRouterChanges(): void;
282
298
  /**
283
- * Return the router's current pathname at connect() time.
284
- *
285
- * Called once during connect() to perform the initial URL actor sync.
286
- * router.subscribe() only fires on *future* navigation events; it does not
287
- * replay the already-loaded location. Subclasses that can read the router's
288
- * current location synchronously (e.g. `router.state.location.pathname`)
289
- * should override this method so that deep-link / direct-URL loads drive the
290
- * actor to the correct state instead of leaving it at its machine default.
291
- *
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
+ * Returns the current pathname of the router at the moment of `connect()`.
300
+ *
301
+ * `connect()` calls this method one time, for the first synchronization from the URL
302
+ * to the actor. router.subscribe() fires on a *later* navigation event only. It does
303
+ * not repeat the location that the router loaded before. A subclass that can read the
304
+ * current location of its router synchronously, for example with
305
+ * `router.state.location.pathname`, overrides this method. A deep link and a direct
306
+ * URL then drive the actor to the correct state, and they leave it not at the default
307
+ * of its machine.
308
+ *
309
+ * The meaning of each return value:
310
+ * - `string` → the router has a current path, and the base `connect()` writes the actor state from the router
311
+ * - `null` → the router is active, but it has no current path yet, and the base `connect()` writes the router from the actor
312
+ * - `undefined` → the adapter does the first synchronization itself, and the base `connect()` does nothing
313
+ *
314
+ * The default value is `undefined`. Therefore a bridge without this hook keeps its
315
+ * earlier behavior.
299
316
  */
300
317
  protected getInitialRouterPath(): string | null | undefined;
301
318
  /**
302
- * Return the router's current search string at connect() time.
319
+ * Returns the current search string of the router at the moment of `connect()`.
303
320
  *
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.
321
+ * This method is the pair of `getInitialRouterPath()`. `connect()` calls it one time,
322
+ * to give the query string of the first URL to `syncActorFromRouter()`. It returns
323
+ * `undefined` when the current URL of the router has no search string, and also when
324
+ * the subclass overrides the method not. `syncActorFromRouter` then makes an empty
325
+ * `query: {}` in the event.
308
326
  *
309
- * Subclasses that override `getInitialRouterPath()` and have a query string
310
- * available should also override this method.
327
+ * A subclass that overrides `getInitialRouterPath()` and has a query string
328
+ * overrides this method too.
311
329
  *
312
- * @returns URL search string (e.g. `"?tab=security"`), or `undefined` if not available.
330
+ * @returns The URL search string, for example `"?tab=security"`, or `undefined` when it is not available.
313
331
  */
314
332
  protected getInitialRouterSearch(): string | undefined;
315
333
  }
@@ -1 +1 @@
1
- {"version":3,"file":"router-bridge-base.d.ts","sourceRoot":"","sources":["../src/router-bridge-base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router-bridge-base.d.ts","sourceRoot":"","sources":["../src/router-bridge-base.ts"],"names":[],"mappings":"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"}