@xmachines/play-router 2.0.0-alpha.1 → 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 +98 -89
- 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 +21 -30
- 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 +163 -164
- 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 +23 -21
package/README.md
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
<!-- generated-by: gsd-doc-writer -->
|
|
2
|
-
|
|
3
1
|
# @xmachines/play-router
|
|
4
2
|
|
|
5
|
-
Route tree extraction from XState
|
|
3
|
+
Route tree extraction from XState v5 state machines. Part of [@xmachines/play](../play/README.md) Universal Player Architecture.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@xmachines/play-router)
|
|
8
6
|
|
|
9
|
-
|
|
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
|
|
|
13
11
|
```bash
|
|
14
|
-
pnpm add xstate@^
|
|
12
|
+
pnpm add xstate@^5.31.0
|
|
15
13
|
pnpm add @xmachines/play-router
|
|
16
14
|
```
|
|
17
15
|
|
|
18
16
|
**Peer dependencies:**
|
|
19
17
|
|
|
20
|
-
- `xstate` ^
|
|
18
|
+
- `xstate` ^5.31.0 — XState v5 state machine runtime
|
|
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,23 +34,17 @@ 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
|
|
|
43
41
|
### Extract routes from a machine
|
|
44
42
|
|
|
45
43
|
```typescript
|
|
46
|
-
import {
|
|
44
|
+
import { createMachine } from "xstate";
|
|
47
45
|
import { extractMachineRoutes, createRouteMap } from "@xmachines/play-router";
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
// without it, XState v6 types `meta` as `never`.
|
|
51
|
-
const machine = setup({
|
|
52
|
-
schemas: {
|
|
53
|
-
meta: types<{ route?: string }>(),
|
|
54
|
-
},
|
|
55
|
-
}).createMachine({
|
|
47
|
+
const machine = createMachine({
|
|
56
48
|
id: "app",
|
|
57
49
|
initial: "home",
|
|
58
50
|
states: {
|
|
@@ -101,8 +93,13 @@ routeMap.getPathByStateId("profile"); // "/profile/:userId"
|
|
|
101
93
|
### Sending `play.route` events
|
|
102
94
|
|
|
103
95
|
```typescript
|
|
96
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
104
97
|
import type { PlayRouteEvent } from "@xmachines/play-router";
|
|
105
98
|
|
|
99
|
+
// machine: your routable machine (states carry meta.route)
|
|
100
|
+
const actor = definePlayer({ machine })();
|
|
101
|
+
actor.start();
|
|
102
|
+
|
|
106
103
|
// Navigate to a state by ID
|
|
107
104
|
const event: PlayRouteEvent = {
|
|
108
105
|
type: "play.route",
|
|
@@ -126,14 +123,21 @@ actor.send({
|
|
|
126
123
|
});
|
|
127
124
|
```
|
|
128
125
|
|
|
129
|
-
###
|
|
126
|
+
### How to write a `RouterBridgeBase` adapter
|
|
130
127
|
|
|
131
|
-
Extend `RouterBridgeBase
|
|
128
|
+
Extend `RouterBridgeBase`, then implement the three abstract methods for your framework:
|
|
132
129
|
|
|
133
130
|
```typescript
|
|
134
|
-
import { RouterBridgeBase } from "@xmachines/play-router";
|
|
131
|
+
import { RouterBridgeBase, createRouteMap } from "@xmachines/play-router";
|
|
135
132
|
import type { RoutableActor } from "@xmachines/play-router";
|
|
136
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
|
+
|
|
137
141
|
export class MyRouterBridge extends RouterBridgeBase {
|
|
138
142
|
private unsubscribe: (() => void) | null = null;
|
|
139
143
|
|
|
@@ -169,7 +173,8 @@ export class MyRouterBridge extends RouterBridgeBase {
|
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
// Usage
|
|
176
|
+
// Usage — myRouter: your framework's router instance;
|
|
177
|
+
// machine/actor: your routable machine and its started actor
|
|
173
178
|
const routeMap = createRouteMap(machine);
|
|
174
179
|
const bridge = new MyRouterBridge(myRouter, actor, routeMap);
|
|
175
180
|
bridge.connect();
|
|
@@ -181,81 +186,79 @@ bridge.disconnect();
|
|
|
181
186
|
|
|
182
187
|
### Route Extraction
|
|
183
188
|
|
|
184
|
-
| Export | Description
|
|
185
|
-
| ---------------------------------------- |
|
|
186
|
-
| `extractMachineRoutes(machine)` |
|
|
187
|
-
| `createRouteMap(machine, options?)` |
|
|
188
|
-
| `createRouteMapFromTree(tree, options?)` |
|
|
189
|
-
| `buildRouteTree(routes)` |
|
|
190
|
-
| `machineToGraph(machine)` |
|
|
191
|
-
|
|
192
|
-
> **Graph shape under XState v6:** machines wired by `formatPlayRouteTransitions` expose their route navigation as statically-targeted `xstate.route` edges (one per routed state) plus a single dynamic `play.route` forwarder edge — tooling that previously filtered for per-route `play.route` edges should query `xstate.route` instead. Edge data marks guard presence with `guarded: true`; guard _names_ are unrecoverable under v6 (`guardType` is never populated).
|
|
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 |
|
|
193
196
|
|
|
194
197
|
### Route Matching
|
|
195
198
|
|
|
196
|
-
| Export | Description
|
|
197
|
-
| ----------------------------- |
|
|
198
|
-
| `RouteMap` |
|
|
199
|
-
| `findRouteById(tree, id)` |
|
|
200
|
-
| `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 |
|
|
201
204
|
|
|
202
205
|
### Query Utilities
|
|
203
206
|
|
|
204
|
-
| Export | Description
|
|
205
|
-
| ------------------------------------------------- |
|
|
206
|
-
| `getRoutableRoutes(tree)` |
|
|
207
|
-
| `getNavigableRoutes(tree, stateId)` |
|
|
208
|
-
| `routeExists(tree, path)` |
|
|
209
|
-
| `getTransitionReachableRoutes(graph, stateId)` |
|
|
210
|
-
| `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 |
|
|
211
214
|
|
|
212
215
|
### Router Bridge
|
|
213
216
|
|
|
214
|
-
| Export | Description
|
|
215
|
-
| --------------------------------------- |
|
|
216
|
-
| `RouterBridgeBase` |
|
|
217
|
-
| `sanitizePathname(path)` |
|
|
218
|
-
| `buildPlayRouteEvent(options)` |
|
|
219
|
-
| `extractRouteParams(pathname, pattern)` |
|
|
220
|
-
| `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 |
|
|
221
224
|
|
|
222
225
|
### Validation
|
|
223
226
|
|
|
224
|
-
| Export | Description
|
|
225
|
-
| ---------------------------------------- |
|
|
226
|
-
| `validateRouteFormat(route, stateId)` |
|
|
227
|
-
| `validateStateExists(stateId, stateIds)` |
|
|
228
|
-
| `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 |
|
|
229
232
|
|
|
230
233
|
### Key Types
|
|
231
234
|
|
|
232
|
-
| Export | Description
|
|
233
|
-
| ---------------------------------- |
|
|
234
|
-
| `RouterBridge` |
|
|
235
|
-
| `RouteTree` |
|
|
236
|
-
| `RouteNode` |
|
|
237
|
-
| `RouteInfo` |
|
|
238
|
-
| `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query? }`
|
|
239
|
-
| `RoutableActor` |
|
|
240
|
-
| `PlayActor` |
|
|
241
|
-
| `RouteMapping` | `{ stateId, path }` pair
|
|
242
|
-
| `RouteMapping as BaseRouteMapping` |
|
|
243
|
-
| `MachineGraph` |
|
|
244
|
-
| `WindowLike` |
|
|
245
|
-
| `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 |
|
|
246
249
|
|
|
247
250
|
### Errors (subpath `@xmachines/play-router/errors`)
|
|
248
251
|
|
|
249
|
-
| Class | Code | When thrown
|
|
250
|
-
| ---------------------------- | --------------------------------------- |
|
|
251
|
-
| `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()`
|
|
252
|
-
| `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one
|
|
253
|
-
| `URLPatternUnavailableError` | `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE` | URLPattern API is absent and no polyfill is loaded
|
|
254
|
-
| `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` |
|
|
255
|
-
| `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""`
|
|
256
|
-
| `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route
|
|
257
|
-
| `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path
|
|
258
|
-
| `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 |
|
|
259
262
|
|
|
260
263
|
```typescript
|
|
261
264
|
import {
|
|
@@ -264,6 +267,7 @@ import {
|
|
|
264
267
|
URLPatternUnavailableError,
|
|
265
268
|
} from "@xmachines/play-router/errors";
|
|
266
269
|
|
|
270
|
+
// bridge from the adapter example above
|
|
267
271
|
try {
|
|
268
272
|
bridge.connect();
|
|
269
273
|
} catch (err) {
|
|
@@ -279,7 +283,7 @@ try {
|
|
|
279
283
|
|
|
280
284
|
### `meta.route` patterns
|
|
281
285
|
|
|
282
|
-
|
|
286
|
+
Declare the route of an XState state node in its `meta.route` field:
|
|
283
287
|
|
|
284
288
|
```typescript
|
|
285
289
|
states: {
|
|
@@ -304,7 +308,7 @@ states: {
|
|
|
304
308
|
|
|
305
309
|
### Relative vs absolute paths
|
|
306
310
|
|
|
307
|
-
|
|
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:
|
|
308
312
|
|
|
309
313
|
```typescript
|
|
310
314
|
states: {
|
|
@@ -325,7 +329,7 @@ states: {
|
|
|
325
329
|
}
|
|
326
330
|
```
|
|
327
331
|
|
|
328
|
-
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.
|
|
329
333
|
|
|
330
334
|
## Testing
|
|
331
335
|
|
|
@@ -337,10 +341,11 @@ pnpm --filter @xmachines/play-router test
|
|
|
337
341
|
pnpm --filter @xmachines/play-router run test:watch
|
|
338
342
|
```
|
|
339
343
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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:
|
|
344
349
|
|
|
345
350
|
```typescript
|
|
346
351
|
import { runBridgeContractTests } from "@xmachines/play-router-shared/test/router-bridge-contract.js";
|
|
@@ -350,15 +355,19 @@ runBridgeContractTests({
|
|
|
350
355
|
createHarness(initialPath) {
|
|
351
356
|
// return ContractHarness with bridge, actor, simulateNavigation, getLastNavigatedPath
|
|
352
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
|
+
},
|
|
353
362
|
});
|
|
354
363
|
```
|
|
355
364
|
|
|
356
365
|
## Related Packages
|
|
357
366
|
|
|
358
367
|
- **[@xmachines/play](../play/README.md)** — Core protocol types (`PlayEvent`, `PlayError`)
|
|
359
|
-
- **[@xmachines/play-actor](../play-actor/README.md)** —
|
|
360
|
-
- **[@xmachines/play-signals](../play-signals/README.md)** — TC39 Signals polyfill
|
|
361
|
-
- **[@xmachines/play-xstate](../play-xstate/README.md)** — XState
|
|
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
|
|
362
371
|
- **[@xmachines/play-tanstack-router](../play-tanstack-router/README.md)** — Shared TanStack Router bridge base (framework-agnostic)
|
|
363
372
|
- **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router/README.md)** — TanStack Router adapter (React)
|
|
364
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"}
|