@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.
- package/README.md +93 -76
- package/dist/base-route-map.d.ts +63 -57
- package/dist/base-route-map.d.ts.map +1 -1
- package/dist/base-route-map.js +65 -59
- package/dist/base-route-map.js.map +1 -1
- package/dist/build-tree.d.ts +13 -12
- package/dist/build-tree.d.ts.map +1 -1
- package/dist/build-tree.js +30 -28
- package/dist/build-tree.js.map +1 -1
- package/dist/create-route-map-from-tree.d.ts +15 -15
- package/dist/create-route-map-from-tree.js +15 -15
- package/dist/create-route-map.d.ts +18 -16
- package/dist/create-route-map.d.ts.map +1 -1
- package/dist/create-route-map.js +10 -9
- package/dist/create-route-map.js.map +1 -1
- package/dist/errors.d.ts +40 -38
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +40 -38
- package/dist/errors.js.map +1 -1
- package/dist/extract-routes.d.ts +8 -7
- package/dist/extract-routes.d.ts.map +1 -1
- package/dist/extract-routes.js +31 -27
- package/dist/extract-routes.js.map +1 -1
- package/dist/find-route.d.ts +18 -15
- package/dist/find-route.d.ts.map +1 -1
- package/dist/find-route.js +42 -38
- package/dist/find-route.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/machine-to-graph.d.ts +3 -2
- package/dist/machine-to-graph.d.ts.map +1 -1
- package/dist/machine-to-graph.js +20 -19
- package/dist/machine-to-graph.js.map +1 -1
- package/dist/query.d.ts +39 -37
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +62 -57
- package/dist/query.js.map +1 -1
- package/dist/router-bridge-base.d.ts +208 -190
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +235 -211
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.d.ts +41 -35
- package/dist/router-sync.d.ts.map +1 -1
- package/dist/router-sync.js +53 -45
- package/dist/router-sync.js.map +1 -1
- package/dist/types.d.ts +165 -147
- package/dist/types.d.ts.map +1 -1
- package/dist/url-pattern-utils.d.ts +53 -47
- package/dist/url-pattern-utils.d.ts.map +1 -1
- package/dist/url-pattern-utils.js +61 -55
- package/dist/url-pattern-utils.js.map +1 -1
- package/dist/validate-routes.d.ts +32 -31
- package/dist/validate-routes.d.ts.map +1 -1
- package/dist/validate-routes.js +30 -29
- package/dist/validate-routes.js.map +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Route tree extraction from XState v5 state machines. Part of [@xmachines/play](../play/README.md) Universal Player Architecture.
|
|
4
4
|
|
|
5
|
-
[](https://opensource.org/licenses/MIT) [](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@xmachines/play-router)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Part of the [xmachines-js monorepo](../../README.md).
|
|
7
|
+
This package extracts the routes from a machine graph and looks them up in both directions. The Actor therefore keeps the authority over the navigation.
|
|
10
8
|
|
|
11
9
|
## Installation
|
|
12
10
|
|
|
@@ -21,9 +19,9 @@ pnpm add @xmachines/play-router
|
|
|
21
19
|
|
|
22
20
|
**URLPattern polyfill (Node.js < 24 / older browsers):**
|
|
23
21
|
|
|
24
|
-
`@xmachines/play-router`
|
|
22
|
+
`@xmachines/play-router` matches each dynamic route with the [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern). URLPattern is native in Node.js 24+ and in a modern browser (Chrome 95+, Firefox 117+, Safari 16.4+).
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
In an environment without the native API, load a polyfill **before** you import this package:
|
|
27
25
|
|
|
28
26
|
```typescript
|
|
29
27
|
// Entry point — must run before any @xmachines/play-router import
|
|
@@ -36,7 +34,7 @@ Install the polyfill:
|
|
|
36
34
|
pnpm add urlpattern-polyfill
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
`urlpattern-polyfill` is
|
|
37
|
+
`urlpattern-polyfill` is an optional peer dependency. A package manager does not install it for you. Install it and load it yourself when your runtime has no native URLPattern.
|
|
40
38
|
|
|
41
39
|
## Usage
|
|
42
40
|
|
|
@@ -95,8 +93,13 @@ routeMap.getPathByStateId("profile"); // "/profile/:userId"
|
|
|
95
93
|
### Sending `play.route` events
|
|
96
94
|
|
|
97
95
|
```typescript
|
|
96
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
98
97
|
import type { PlayRouteEvent } from "@xmachines/play-router";
|
|
99
98
|
|
|
99
|
+
// machine: your routable machine (states carry meta.route)
|
|
100
|
+
const actor = definePlayer({ machine })();
|
|
101
|
+
actor.start();
|
|
102
|
+
|
|
100
103
|
// Navigate to a state by ID
|
|
101
104
|
const event: PlayRouteEvent = {
|
|
102
105
|
type: "play.route",
|
|
@@ -120,14 +123,21 @@ actor.send({
|
|
|
120
123
|
});
|
|
121
124
|
```
|
|
122
125
|
|
|
123
|
-
###
|
|
126
|
+
### How to write a `RouterBridgeBase` adapter
|
|
124
127
|
|
|
125
|
-
Extend `RouterBridgeBase
|
|
128
|
+
Extend `RouterBridgeBase`, then implement the three abstract methods for your framework:
|
|
126
129
|
|
|
127
130
|
```typescript
|
|
128
|
-
import { RouterBridgeBase } from "@xmachines/play-router";
|
|
131
|
+
import { RouterBridgeBase, createRouteMap } from "@xmachines/play-router";
|
|
129
132
|
import type { RoutableActor } from "@xmachines/play-router";
|
|
130
133
|
|
|
134
|
+
// Shape of your framework's router — adjust to its real API
|
|
135
|
+
type MyRouter = {
|
|
136
|
+
navigate(path: string): void;
|
|
137
|
+
subscribe(handler: (location: { pathname: string; search: string }) => void): () => void;
|
|
138
|
+
state: { location: { pathname: string } };
|
|
139
|
+
};
|
|
140
|
+
|
|
131
141
|
export class MyRouterBridge extends RouterBridgeBase {
|
|
132
142
|
private unsubscribe: (() => void) | null = null;
|
|
133
143
|
|
|
@@ -163,7 +173,8 @@ export class MyRouterBridge extends RouterBridgeBase {
|
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
// Usage
|
|
176
|
+
// Usage — myRouter: your framework's router instance;
|
|
177
|
+
// machine/actor: your routable machine and its started actor
|
|
167
178
|
const routeMap = createRouteMap(machine);
|
|
168
179
|
const bridge = new MyRouterBridge(myRouter, actor, routeMap);
|
|
169
180
|
bridge.connect();
|
|
@@ -175,79 +186,79 @@ bridge.disconnect();
|
|
|
175
186
|
|
|
176
187
|
### Route Extraction
|
|
177
188
|
|
|
178
|
-
| Export | Description
|
|
179
|
-
| ---------------------------------------- |
|
|
180
|
-
| `extractMachineRoutes(machine)` |
|
|
181
|
-
| `createRouteMap(machine, options?)` |
|
|
182
|
-
| `createRouteMapFromTree(tree, options?)` |
|
|
183
|
-
| `buildRouteTree(routes)` |
|
|
184
|
-
| `machineToGraph(machine)` |
|
|
189
|
+
| Export | Description |
|
|
190
|
+
| ---------------------------------------- | --------------------------------------------------------------------------------- |
|
|
191
|
+
| `extractMachineRoutes(machine)` | Converts an XState machine into a `RouteTree` with the state ID ↔ path maps |
|
|
192
|
+
| `createRouteMap(machine, options?)` | Builds a `RouteMap` directly from a machine. An adapter uses this form |
|
|
193
|
+
| `createRouteMapFromTree(tree, options?)` | Builds a `RouteMap` from a `RouteTree` that you extracted before |
|
|
194
|
+
| `buildRouteTree(routes)` | Builds a `RouteTree` from an array of `RouteInfo` objects |
|
|
195
|
+
| `machineToGraph(machine)` | Converts a machine into a typed `@statelyai/graph` `Graph`, for a graph algorithm |
|
|
185
196
|
|
|
186
197
|
### Route Matching
|
|
187
198
|
|
|
188
|
-
| Export | Description
|
|
189
|
-
| ----------------------------- |
|
|
190
|
-
| `RouteMap` |
|
|
191
|
-
| `findRouteById(tree, id)` |
|
|
192
|
-
| `findRouteByPath(tree, path)` |
|
|
199
|
+
| Export | Description |
|
|
200
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
201
|
+
| `RouteMap` | The `stateId ↔ path` lookup class for both directions. It matches an exact path in O(1) and a pattern in O(k) |
|
|
202
|
+
| `findRouteById(tree, id)` | Finds a `RouteNode` by its state ID |
|
|
203
|
+
| `findRouteByPath(tree, path)` | Finds a `RouteNode` by its URL path. It also matches a dynamic pattern |
|
|
193
204
|
|
|
194
205
|
### Query Utilities
|
|
195
206
|
|
|
196
|
-
| Export | Description
|
|
197
|
-
| ------------------------------------------------- |
|
|
198
|
-
| `getRoutableRoutes(tree)` |
|
|
199
|
-
| `getNavigableRoutes(tree, stateId)` |
|
|
200
|
-
| `routeExists(tree, path)` |
|
|
201
|
-
| `getTransitionReachableRoutes(graph, stateId)` |
|
|
202
|
-
| `isRouteReachable(graph, fromStateId, toStateId)` |
|
|
207
|
+
| Export | Description |
|
|
208
|
+
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
209
|
+
| `getRoutableRoutes(tree)` | Returns every routable `RouteNode` in one flat array |
|
|
210
|
+
| `getNavigableRoutes(tree, stateId)` | Returns the child routes that a state can reach, through the hierarchy and through a transition |
|
|
211
|
+
| `routeExists(tree, path)` | Tells you if the tree holds a path |
|
|
212
|
+
| `getTransitionReachableRoutes(graph, stateId)` | Returns the route paths that a state can reach through an XState transition |
|
|
213
|
+
| `isRouteReachable(graph, fromStateId, toStateId)` | Tells you if a transition path is present between two states |
|
|
203
214
|
|
|
204
215
|
### Router Bridge
|
|
205
216
|
|
|
206
|
-
| Export | Description
|
|
207
|
-
| --------------------------------------- |
|
|
208
|
-
| `RouterBridgeBase` |
|
|
209
|
-
| `sanitizePathname(path)` |
|
|
210
|
-
| `buildPlayRouteEvent(options)` |
|
|
211
|
-
| `extractRouteParams(pathname, pattern)` |
|
|
212
|
-
| `extractQuery(search)` |
|
|
217
|
+
| Export | Description |
|
|
218
|
+
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
219
|
+
| `RouterBridgeBase` | The abstract base class of each framework router adapter. It implements the `RouterBridge` protocol |
|
|
220
|
+
| `sanitizePathname(path)` | Normalizes a raw pathname. It returns `null` for a path of more than 2048 characters, and for malformed input |
|
|
221
|
+
| `buildPlayRouteEvent(options)` | Builds a `PlayRouteEvent` from a pathname and a route-map match result |
|
|
222
|
+
| `extractRouteParams(pathname, pattern)` | Reads the path parameters of a URL with URLPattern |
|
|
223
|
+
| `extractQuery(search)` | Reads the query parameters of a URL search string |
|
|
213
224
|
|
|
214
225
|
### Validation
|
|
215
226
|
|
|
216
|
-
| Export | Description
|
|
217
|
-
| ---------------------------------------- |
|
|
218
|
-
| `validateRouteFormat(route, stateId)` |
|
|
219
|
-
| `validateStateExists(stateId, stateIds)` |
|
|
220
|
-
| `detectDuplicateRoutes(routes)` |
|
|
227
|
+
| Export | Description |
|
|
228
|
+
| ---------------------------------------- | ---------------------------------------------------- |
|
|
229
|
+
| `validateRouteFormat(route, stateId)` | Asserts that the route path is not empty |
|
|
230
|
+
| `validateStateExists(stateId, stateIds)` | Asserts that the machine graph holds the state ID |
|
|
231
|
+
| `detectDuplicateRoutes(routes)` | Throws when two states resolve to the same full path |
|
|
221
232
|
|
|
222
233
|
### Key Types
|
|
223
234
|
|
|
224
|
-
| Export | Description
|
|
225
|
-
| ---------------------------------- |
|
|
226
|
-
| `RouterBridge` |
|
|
227
|
-
| `RouteTree` |
|
|
228
|
-
| `RouteNode` |
|
|
229
|
-
| `RouteInfo` |
|
|
230
|
-
| `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query? }`
|
|
231
|
-
| `RoutableActor` |
|
|
232
|
-
| `PlayActor` |
|
|
233
|
-
| `RouteMapping` | `{ stateId, path }` pair
|
|
234
|
-
| `RouteMapping as BaseRouteMapping` |
|
|
235
|
-
| `MachineGraph` |
|
|
236
|
-
| `WindowLike` |
|
|
237
|
-
| `LocationLike` |
|
|
235
|
+
| Export | Description |
|
|
236
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
237
|
+
| `RouterBridge` | The interface of the `connect()` and `disconnect()` lifecycle |
|
|
238
|
+
| `RouteTree` | The hierarchical tree, with `root`, `byStateId`, `byPath`, and an optional `graph` |
|
|
239
|
+
| `RouteNode` | One node of the tree, with `id`, `path`, `fullPath`, `stateId`, `children`, and `parent` |
|
|
240
|
+
| `RouteInfo` | The flat route descriptor that comes from a state node |
|
|
241
|
+
| `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query? }` |
|
|
242
|
+
| `RoutableActor` | The minimal actor interface that `RouterBridgeBase` requires: `currentRoute`, `initialRoute`, and `send(PlayRouteEvent)` |
|
|
243
|
+
| `PlayActor` | The complete actor interface that `PlayRouterProvider` uses. It extends `RoutableActor` with `currentView` (`Routable + Viewable`) |
|
|
244
|
+
| `RouteMapping` | The `{ stateId, path }` pair that builds a `RouteMap` |
|
|
245
|
+
| `RouteMapping as BaseRouteMapping` | The alias of `RouteMapping`, for compatibility with an earlier version |
|
|
246
|
+
| `MachineGraph` | The typed `@statelyai/graph` Graph, with `MachineNodeData` and `MachineEdgeData` |
|
|
247
|
+
| `WindowLike` | The minimal `window` interface that you can inject for SSR and for a test |
|
|
248
|
+
| `LocationLike` | The minimal `location` interface that you can inject for SSR and for a test |
|
|
238
249
|
|
|
239
250
|
### Errors (subpath `@xmachines/play-router/errors`)
|
|
240
251
|
|
|
241
|
-
| Class | Code | When thrown
|
|
242
|
-
| ---------------------------- | --------------------------------------- |
|
|
243
|
-
| `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()`
|
|
244
|
-
| `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one
|
|
245
|
-
| `URLPatternUnavailableError` | `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE` | URLPattern API is absent and no polyfill is loaded
|
|
246
|
-
| `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` |
|
|
247
|
-
| `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""`
|
|
248
|
-
| `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route
|
|
249
|
-
| `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path
|
|
250
|
-
| `UnknownStateTypeError` | `PLAY_ROUTE_UNKNOWN_STATE_TYPE` | A state node has an
|
|
252
|
+
| Class | Code | When thrown |
|
|
253
|
+
| ---------------------------- | --------------------------------------- | ----------------------------------------------------------------------- |
|
|
254
|
+
| `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()` cannot send a `play.route` event |
|
|
255
|
+
| `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one |
|
|
256
|
+
| `URLPatternUnavailableError` | `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE` | The URLPattern API is absent, and no polyfill is loaded |
|
|
257
|
+
| `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` | The URLPattern constructor refuses a route pattern string |
|
|
258
|
+
| `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""` |
|
|
259
|
+
| `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route names a state ID that the machine graph does not hold |
|
|
260
|
+
| `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path |
|
|
261
|
+
| `UnknownStateTypeError` | `PLAY_ROUTE_UNKNOWN_STATE_TYPE` | A state node has an XState `.type` value that the package does not know |
|
|
251
262
|
|
|
252
263
|
```typescript
|
|
253
264
|
import {
|
|
@@ -256,6 +267,7 @@ import {
|
|
|
256
267
|
URLPatternUnavailableError,
|
|
257
268
|
} from "@xmachines/play-router/errors";
|
|
258
269
|
|
|
270
|
+
// bridge from the adapter example above
|
|
259
271
|
try {
|
|
260
272
|
bridge.connect();
|
|
261
273
|
} catch (err) {
|
|
@@ -271,7 +283,7 @@ try {
|
|
|
271
283
|
|
|
272
284
|
### `meta.route` patterns
|
|
273
285
|
|
|
274
|
-
|
|
286
|
+
Declare the route of an XState state node in its `meta.route` field:
|
|
275
287
|
|
|
276
288
|
```typescript
|
|
277
289
|
states: {
|
|
@@ -296,7 +308,7 @@ states: {
|
|
|
296
308
|
|
|
297
309
|
### Relative vs absolute paths
|
|
298
310
|
|
|
299
|
-
|
|
311
|
+
A child route that starts with `/` is absolute, and it does not inherit the path of its parent. A child route without the first `/` is relative to its nearest routable ancestor:
|
|
300
312
|
|
|
301
313
|
```typescript
|
|
302
314
|
states: {
|
|
@@ -317,7 +329,7 @@ states: {
|
|
|
317
329
|
}
|
|
318
330
|
```
|
|
319
331
|
|
|
320
|
-
Always use `node.fullPath`
|
|
332
|
+
Always use `node.fullPath` to match a browser URL and to build a route map. Never use `node.path` for this.
|
|
321
333
|
|
|
322
334
|
## Testing
|
|
323
335
|
|
|
@@ -329,10 +341,11 @@ pnpm --filter @xmachines/play-router test
|
|
|
329
341
|
pnpm --filter @xmachines/play-router run test:watch
|
|
330
342
|
```
|
|
331
343
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
344
|
+
`@xmachines/play-router-shared` holds a contract test suite of the router bridge, for the
|
|
345
|
+
author of an adapter. That suite drives a real actor. Therefore it is one layer above this
|
|
346
|
+
package, and `@xmachines/play-router` keeps no dependency on an actor runtime.
|
|
347
|
+
`@xmachines/play-router-shared` is a private workspace package. Thus only an adapter author
|
|
348
|
+
in this repository can use the suite:
|
|
336
349
|
|
|
337
350
|
```typescript
|
|
338
351
|
import { runBridgeContractTests } from "@xmachines/play-router-shared/test/router-bridge-contract.js";
|
|
@@ -342,15 +355,19 @@ runBridgeContractTests({
|
|
|
342
355
|
createHarness(initialPath) {
|
|
343
356
|
// return ContractHarness with bridge, actor, simulateNavigation, getLastNavigatedPath
|
|
344
357
|
},
|
|
358
|
+
createRestoredHarness(routedPath) {
|
|
359
|
+
// return ContractHarness whose actor is restored to routedPath
|
|
360
|
+
// while the mock router starts at the machine's initial route
|
|
361
|
+
},
|
|
345
362
|
});
|
|
346
363
|
```
|
|
347
364
|
|
|
348
365
|
## Related Packages
|
|
349
366
|
|
|
350
367
|
- **[@xmachines/play](../play/README.md)** — Core protocol types (`PlayEvent`, `PlayError`)
|
|
351
|
-
- **[@xmachines/play-actor](../play-actor/README.md)** —
|
|
352
|
-
- **[@xmachines/play-signals](../play-signals/README.md)** — TC39 Signals polyfill
|
|
353
|
-
- **[@xmachines/play-xstate](../play-xstate/README.md)** — XState v5 logic adapter
|
|
368
|
+
- **[@xmachines/play-actor](../play-actor/README.md)** — the abstract actor base class (`AbstractActor`, `Routable`). Every `AbstractActor` subclass satisfies `RoutableActor` structurally
|
|
369
|
+
- **[@xmachines/play-signals](../play-signals/README.md)** — the TC39 Signals polyfill that observes the actor route
|
|
370
|
+
- **[@xmachines/play-xstate](../play-xstate/README.md)** — the XState v5 logic adapter, which works with a route tree
|
|
354
371
|
- **[@xmachines/play-tanstack-router](../play-tanstack-router/README.md)** — Shared TanStack Router bridge base (framework-agnostic)
|
|
355
372
|
- **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router/README.md)** — TanStack Router adapter (React)
|
|
356
373
|
- **[@xmachines/play-tanstack-solid-router](../play-tanstack-solid-router/README.md)** — TanStack Router adapter (SolidJS)
|
package/dist/base-route-map.d.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RouteMap —
|
|
2
|
+
* RouteMap — the shared base class of the route map for both directions
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The class gives you the pattern match on buckets, and every framework adapter
|
|
5
|
+
* uses it. An adapter extends this class, and it therefore holds no copy of the
|
|
6
|
+
* pattern match logic.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* where k
|
|
9
|
-
*
|
|
8
|
+
* The algorithm: an exact match in O(1) through a Map, then a pattern match on the
|
|
9
|
+
* buckets in O(k), where k is the number of the routes in the bucket of the first
|
|
10
|
+
* segment, and that number is much smaller than the number of all the routes. The
|
|
11
|
+
* class matches a parameterized route with URLPattern.
|
|
10
12
|
*/
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* One entry of the map between a state ID and a path.
|
|
13
15
|
*
|
|
14
|
-
* Both fields are `readonly
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `RouteMapping
|
|
16
|
+
* Both fields are `readonly`, because a mapping is immutable after the caller gives
|
|
17
|
+
* it to `RouteMap`. An adapter package re-exports a structurally compatible
|
|
18
|
+
* `RouteMapping` type under its own name. `@xmachines/play-router` publishes this
|
|
19
|
+
* type as `RouteMapping`, and its name therefore does not collide with such a local
|
|
20
|
+
* type of an adapter.
|
|
18
21
|
*
|
|
19
22
|
* @example
|
|
20
23
|
* ```typescript
|
|
@@ -24,34 +27,35 @@
|
|
|
24
27
|
* ```
|
|
25
28
|
*/
|
|
26
29
|
export interface RouteMapping {
|
|
27
|
-
/**
|
|
30
|
+
/** The state ID of the state machine, for example `"home"` or `"#profile"` */
|
|
28
31
|
readonly stateId: string;
|
|
29
|
-
/** URL path
|
|
32
|
+
/** The pattern of the URL path, for example `"/"`, `"/profile/:userId"`, or `"/settings/:section?"` */
|
|
30
33
|
readonly path: string;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
36
|
+
* The shared base class of the route map for both directions.
|
|
34
37
|
*
|
|
35
|
-
*
|
|
36
|
-
* own and
|
|
38
|
+
* Every framework adapter uses this class as its route map. An adapter adds no logic
|
|
39
|
+
* of its own, and it inherits the complete public API from here.
|
|
37
40
|
*
|
|
38
|
-
* **
|
|
39
|
-
* -
|
|
40
|
-
* -
|
|
41
|
-
* of routes
|
|
42
|
-
* -
|
|
43
|
-
*
|
|
41
|
+
* **The strategy of a lookup:**
|
|
42
|
+
* - A static path, without a `:param` → a `Map` lookup in O(1)
|
|
43
|
+
* - A dynamic path → a scan of the bucket index in O(k), with `URLPattern`, where
|
|
44
|
+
* `k` is the number of the routes with the same first path segment
|
|
45
|
+
* - The class keeps each result of a first match in an LRU cache. The default size
|
|
46
|
+
* is 500 entries, and the `cacheSize` constructor option changes it
|
|
44
47
|
*
|
|
45
|
-
* **
|
|
46
|
-
* - `:param` —
|
|
47
|
-
* - `:param?` — optional segment
|
|
48
|
-
* - `*` — wildcard
|
|
48
|
+
* **The syntax of a pattern** (`:param`, `:param?`, and `*`):
|
|
49
|
+
* - `:param` — a necessary segment. It matches exactly one segment without a `/`
|
|
50
|
+
* - `:param?` — an optional segment. It matches zero segments or one segment without a `/`
|
|
51
|
+
* - `*` — a wildcard. It matches each number of segments, as URLPattern defines
|
|
49
52
|
*
|
|
50
|
-
* **
|
|
51
|
-
* `"#stateId"` or `"stateId"
|
|
52
|
-
* `getStateIdByPath` returns the stateId exactly as
|
|
53
|
-
* `getPathByStateId` accepts both forms.
|
|
54
|
-
* forms
|
|
53
|
+
* **The forms of a stateId:** you can register a stateId, and you can look one up,
|
|
54
|
+
* in the form `"#stateId"` or in the form `"stateId"`. `RouteMap` makes the
|
|
55
|
+
* canonical form itself. `getStateIdByPath` returns the stateId exactly as you
|
|
56
|
+
* registered it, and `getPathByStateId` accepts both forms. A registration of the
|
|
57
|
+
* same stateId in both forms gives one entry, and the later registration wins for
|
|
58
|
+
* the lookup in the other direction.
|
|
55
59
|
*
|
|
56
60
|
* @example
|
|
57
61
|
* ```typescript
|
|
@@ -73,39 +77,41 @@ export interface RouteMapping {
|
|
|
73
77
|
* ```
|
|
74
78
|
*/
|
|
75
79
|
export declare class RouteMap {
|
|
76
|
-
/**
|
|
80
|
+
/** The key is the canonical stateId form, which is bare and without a `#`. */
|
|
77
81
|
private stateIdToPath;
|
|
78
82
|
private pathToStateId;
|
|
79
83
|
private patternBuckets;
|
|
80
84
|
private pathMatchCache;
|
|
81
85
|
/**
|
|
82
|
-
*
|
|
86
|
+
* Builds a route map from an array of the mappings between a state ID and a path.
|
|
83
87
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* buckets
|
|
88
|
+
* The constructor puts each static path, which holds no `:param`, into a `Map` for a
|
|
89
|
+
* lookup in O(1). It compiles each parameterized path to a `URLPattern`, and it
|
|
90
|
+
* groups the patterns into the buckets of the first segment. The selection of the
|
|
91
|
+
* candidates is therefore efficient.
|
|
87
92
|
*
|
|
88
|
-
* @param mappings -
|
|
89
|
-
* priority when
|
|
90
|
-
* @param options -
|
|
91
|
-
* `options.cacheSize`:
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
93
|
+
* @param mappings - The array of the `{ stateId, path }` entries. The order gives the
|
|
94
|
+
* priority when more than one pattern can match the same path.
|
|
95
|
+
* @param options - The optional configuration.
|
|
96
|
+
* `options.cacheSize`: the maximum number of the resolved parameterized path
|
|
97
|
+
* lookups in the cache. The default is `500`. Raise it for an application with
|
|
98
|
+
* many different values in a parameterized URL, for example a page of a user
|
|
99
|
+
* profile with thousands of different IDs. After an eviction, the path goes to the
|
|
100
|
+
* bucket pattern scan in O(k) again, which is correct but slower. The smallest
|
|
101
|
+
* effective value is `1`, because QuickLRU requires it.
|
|
96
102
|
*/
|
|
97
103
|
constructor(mappings: RouteMapping[], { cacheSize }?: {
|
|
98
104
|
cacheSize?: number;
|
|
99
105
|
});
|
|
100
106
|
/**
|
|
101
|
-
*
|
|
107
|
+
* Resolves a URL path to its state ID.
|
|
102
108
|
*
|
|
103
|
-
*
|
|
104
|
-
* lookup first, then
|
|
105
|
-
*
|
|
109
|
+
* The method removes the query string and the hash fragment before the match. It
|
|
110
|
+
* tries an exact lookup in O(1) first, then it uses the pattern match on the bucket
|
|
111
|
+
* index. It keeps each result of a first pattern match in the cache.
|
|
106
112
|
*
|
|
107
|
-
* @param path - URL pathname
|
|
108
|
-
* @returns The
|
|
113
|
+
* @param path - The URL pathname. It can hold a query and a hash, for example `"/profile/123?ref=nav"`
|
|
114
|
+
* @returns The state ID of the path, or `null` when no route matches
|
|
109
115
|
*
|
|
110
116
|
* @example
|
|
111
117
|
* ```typescript
|
|
@@ -115,19 +121,19 @@ export declare class RouteMap {
|
|
|
115
121
|
*/
|
|
116
122
|
getStateIdByPath(path: string): string | null;
|
|
117
123
|
/**
|
|
118
|
-
*
|
|
124
|
+
* Returns the path pattern of a state ID.
|
|
119
125
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
126
|
+
* The method accepts the stateId in the form `"#stateId"` and in the form
|
|
127
|
+
* `"stateId"`, and the form of the registration has no effect. The method makes the
|
|
128
|
+
* canonical form itself. Therefore a consumer tries never both forms.
|
|
123
129
|
*
|
|
124
|
-
* @param stateId -
|
|
125
|
-
* @returns The registered path pattern, or `null`
|
|
130
|
+
* @param stateId - The state ID of the state machine, for example `"profile"` or `"#settings"`
|
|
131
|
+
* @returns The registered path pattern, or `null` when the state ID is unknown
|
|
126
132
|
*
|
|
127
133
|
* @example
|
|
128
134
|
* ```typescript
|
|
129
135
|
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
130
|
-
* map.getPathByStateId("#profile"); // "/profile/:userId"
|
|
136
|
+
* map.getPathByStateId("#profile"); // "/profile/:userId" — the same entry
|
|
131
137
|
* map.getPathByStateId("missing"); // null
|
|
132
138
|
* ```
|
|
133
139
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-route-map.d.ts","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"base-route-map.d.ts","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0BH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,YAAY;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,QAAQ;IACpB,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,cAAc,CAAkC;IAExD;;;;;;;;;;;;;;;;;OAiBG;gBACS,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,SAAe,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;IAoCtF;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAuB7C;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAGhD"}
|