@xmachines/play-router 2.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -109
- package/dist/base-route-map.d.ts +7 -1
- package/dist/base-route-map.d.ts.map +1 -1
- package/dist/base-route-map.js +25 -12
- package/dist/base-route-map.js.map +1 -1
- package/dist/build-tree.d.ts +16 -1
- package/dist/build-tree.d.ts.map +1 -1
- package/dist/build-tree.js +23 -6
- package/dist/build-tree.js.map +1 -1
- package/dist/create-route-map-from-tree.d.ts +12 -6
- package/dist/create-route-map-from-tree.d.ts.map +1 -1
- package/dist/create-route-map-from-tree.js +13 -5
- package/dist/create-route-map-from-tree.js.map +1 -1
- package/dist/errors.d.ts +2 -134
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -151
- package/dist/errors.js.map +1 -1
- package/dist/find-route.d.ts.map +1 -1
- package/dist/find-route.js +14 -19
- package/dist/find-route.js.map +1 -1
- package/dist/index.d.ts +7 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -11
- package/dist/index.js.map +1 -1
- package/dist/provider-lifecycle.d.ts +28 -6
- package/dist/provider-lifecycle.d.ts.map +1 -1
- package/dist/provider-lifecycle.js +16 -2
- package/dist/provider-lifecycle.js.map +1 -1
- package/dist/router-bridge-base.d.ts +42 -23
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +257 -84
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.d.ts +1 -1
- package/dist/router-sync.d.ts.map +1 -1
- package/dist/router-sync.js +28 -13
- package/dist/router-sync.js.map +1 -1
- package/dist/types.d.ts +186 -87
- package/dist/types.d.ts.map +1 -1
- package/dist/{create-route-map.d.ts → xstate/create-route-map.d.ts} +6 -20
- package/dist/xstate/create-route-map.d.ts.map +1 -0
- package/dist/{create-route-map.js → xstate/create-route-map.js} +5 -3
- package/dist/xstate/create-route-map.js.map +1 -0
- package/dist/{extract-routes.d.ts → xstate/extract-routes.d.ts} +3 -2
- package/dist/xstate/extract-routes.d.ts.map +1 -0
- package/dist/{extract-routes.js → xstate/extract-routes.js} +8 -3
- package/dist/xstate/extract-routes.js.map +1 -0
- package/dist/xstate/index.d.ts +35 -0
- package/dist/xstate/index.d.ts.map +1 -0
- package/dist/xstate/index.js +33 -0
- package/dist/xstate/index.js.map +1 -0
- package/dist/{machine-to-graph.d.ts → xstate/machine-to-graph.d.ts} +1 -1
- package/dist/xstate/machine-to-graph.d.ts.map +1 -0
- package/dist/{machine-to-graph.js → xstate/machine-to-graph.js} +2 -2
- package/dist/xstate/machine-to-graph.js.map +1 -0
- package/dist/xstate/machine-types.d.ts +42 -0
- package/dist/xstate/machine-types.d.ts.map +1 -0
- package/dist/xstate/machine-types.js +2 -0
- package/dist/xstate/machine-types.js.map +1 -0
- package/dist/{query.d.ts → xstate/query.d.ts} +6 -4
- package/dist/xstate/query.d.ts.map +1 -0
- package/dist/{query.js → xstate/query.js} +3 -2
- package/dist/xstate/query.js.map +1 -0
- package/package.json +26 -20
- package/dist/base-path.d.ts +0 -209
- package/dist/base-path.d.ts.map +0 -1
- package/dist/base-path.js +0 -418
- package/dist/base-path.js.map +0 -1
- package/dist/create-route-map.d.ts.map +0 -1
- package/dist/create-route-map.js.map +0 -1
- package/dist/extract-routes.d.ts.map +0 -1
- package/dist/extract-routes.js.map +0 -1
- package/dist/framework-params.d.ts +0 -144
- package/dist/framework-params.d.ts.map +0 -1
- package/dist/framework-params.js +0 -291
- package/dist/framework-params.js.map +0 -1
- package/dist/machine-to-graph.d.ts.map +0 -1
- package/dist/machine-to-graph.js.map +0 -1
- package/dist/query.d.ts.map +0 -1
- package/dist/query.js.map +0 -1
- package/dist/url-pattern-utils.d.ts +0 -93
- package/dist/url-pattern-utils.d.ts.map +0 -1
- package/dist/url-pattern-utils.js +0 -233
- package/dist/url-pattern-utils.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,41 +2,29 @@
|
|
|
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
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.
|
|
8
8
|
|
|
9
|
+
> **Browser floor: Chrome 110, Firefox 115, Safari 16.4.** This package calls the ES2023
|
|
10
|
+
> change-by-copy array methods, so a browser below that floor throws
|
|
11
|
+
> `TypeError: ... is not a function` at the extraction of the routes. The root README
|
|
12
|
+
> carries the table.
|
|
13
|
+
|
|
9
14
|
## Installation
|
|
10
15
|
|
|
11
16
|
```bash
|
|
12
|
-
pnpm add xstate@^5.
|
|
17
|
+
pnpm add xstate@^5.33.0
|
|
13
18
|
pnpm add @xmachines/play-router
|
|
14
19
|
```
|
|
15
20
|
|
|
16
21
|
**Peer dependencies:**
|
|
17
22
|
|
|
18
|
-
- `xstate` ^5.
|
|
19
|
-
|
|
20
|
-
**URLPattern polyfill (Node.js < 24 / older browsers):**
|
|
21
|
-
|
|
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+).
|
|
23
|
-
|
|
24
|
-
In an environment without the native API, load a polyfill **before** you import this package:
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// Entry point — must run before any @xmachines/play-router import
|
|
28
|
-
import "urlpattern-polyfill";
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Install the polyfill:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
pnpm add urlpattern-polyfill
|
|
35
|
-
```
|
|
23
|
+
- `xstate` ^5.33.0 — XState v5 state machine runtime (an OPTIONAL peer: the `@xmachines/play-router/xstate` subpath alone reads it)
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
**URLPattern:** this package matches each route with the [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern). It uses the native API when the runtime has one, and it falls back to `urlpattern-polyfill`, which it carries as an ordinary dependency. **You install nothing and you load nothing.**
|
|
38
26
|
|
|
39
|
-
|
|
27
|
+
The [routing guide](../docs/guides/routing.md) states the pattern language: the forms, the modifiers, the prefix rule, and the one divergence from the standard.
|
|
40
28
|
|
|
41
29
|
## Usage
|
|
42
30
|
|
|
@@ -44,7 +32,7 @@ pnpm add urlpattern-polyfill
|
|
|
44
32
|
|
|
45
33
|
```typescript
|
|
46
34
|
import { createMachine } from "xstate";
|
|
47
|
-
import { extractMachineRoutes, createRouteMap } from "@xmachines/play-router";
|
|
35
|
+
import { extractMachineRoutes, createRouteMap } from "@xmachines/play-router/xstate";
|
|
48
36
|
|
|
49
37
|
const machine = createMachine({
|
|
50
38
|
id: "app",
|
|
@@ -292,7 +280,7 @@ To register the routes of the machine in a host router that declares real route
|
|
|
292
280
|
objects, ask for the list — and drop it again when the machine unloads:
|
|
293
281
|
|
|
294
282
|
```typescript
|
|
295
|
-
import { extractMachineRoutes, getRouteMappings } from "@xmachines/play-router";
|
|
283
|
+
import { extractMachineRoutes, getRouteMappings } from "@xmachines/play-router/xstate";
|
|
296
284
|
|
|
297
285
|
const tree = extractMachineRoutes(machine);
|
|
298
286
|
|
|
@@ -315,25 +303,25 @@ event always carries the prefixed form.
|
|
|
315
303
|
> matched a route of the HOST by construction, because the machine owns the suffix of
|
|
316
304
|
> the path only. Those params therefore describe the route of the machine never, even
|
|
317
305
|
> when a name collides — a collision carries the value of the HOST. Both adapters
|
|
318
|
-
> resolve each param from the stripped path with `URLPattern` instead
|
|
319
|
-
> therefore need a polyfill on an older runtime when they are mounted.
|
|
306
|
+
> resolve each param from the stripped path with `URLPattern` instead.
|
|
320
307
|
>
|
|
321
|
-
> Without a prefix both adapters keep the parse of their framework, with its decoding
|
|
322
|
-
>
|
|
323
|
-
>
|
|
324
|
-
>
|
|
308
|
+
> Without a prefix both adapters keep the parse of their framework, with its decoding,
|
|
309
|
+
> but restricted to the names that the pattern of the machine declares. A splat of a
|
|
310
|
+
> catch-all, and a param of a wrapper route, reach the actor never. A name that the
|
|
311
|
+
> pattern makes OPTIONAL is covered whether the framework reports it or not: a part that
|
|
312
|
+
> did not match has no value, and that is a complete answer.
|
|
325
313
|
>
|
|
326
|
-
> A location that fills NO optional
|
|
327
|
-
>
|
|
328
|
-
>
|
|
329
|
-
> pattern still needed URLPattern when it was built.
|
|
314
|
+
> A location that fills NO optional part runs no match at all. `/settings` is the bare
|
|
315
|
+
> form of `/settings/:section?`, so the params are `{}`, and the adapters read that from
|
|
316
|
+
> the path alone rather than from their framework.
|
|
330
317
|
|
|
331
318
|
### How to write a `RouterBridgeBase` adapter
|
|
332
319
|
|
|
333
320
|
Extend `RouterBridgeBase`, then implement the three abstract methods for your framework:
|
|
334
321
|
|
|
335
322
|
```typescript
|
|
336
|
-
import { RouterBridgeBase
|
|
323
|
+
import { RouterBridgeBase } from "@xmachines/play-router";
|
|
324
|
+
import { createRouteMap } from "@xmachines/play-router/xstate";
|
|
337
325
|
import type { RoutableActor } from "@xmachines/play-router";
|
|
338
326
|
|
|
339
327
|
// Shape of your framework's router — adjust to its real API
|
|
@@ -391,13 +379,14 @@ bridge.disconnect();
|
|
|
391
379
|
|
|
392
380
|
### Route Extraction
|
|
393
381
|
|
|
394
|
-
| Export | Description
|
|
395
|
-
| ---------------------------------------- |
|
|
396
|
-
| `
|
|
397
|
-
| `
|
|
398
|
-
| `
|
|
399
|
-
|
|
400
|
-
|
|
382
|
+
| Export | Description |
|
|
383
|
+
| ---------------------------------------- | ----------------------------------------------------------------------- |
|
|
384
|
+
| `buildRouteTree(routes)` | Builds a `RouteTree` from an array of `RouteInfo` objects |
|
|
385
|
+
| `createRouteMapFromTree(tree, options?)` | Builds a `RouteMap` from a `RouteTree` that you extracted before |
|
|
386
|
+
| `ROOT_NODE_ID` | The ID of the synthetic root node. A walk of a tree must skip that node |
|
|
387
|
+
|
|
388
|
+
The MAIN entry point reads no state machine. The machine half is
|
|
389
|
+
`@xmachines/play-router/xstate`, below.
|
|
401
390
|
|
|
402
391
|
### Route Matching
|
|
403
392
|
|
|
@@ -407,16 +396,32 @@ bridge.disconnect();
|
|
|
407
396
|
| `findRouteById(tree, id)` | Finds a `RouteNode` by its state ID |
|
|
408
397
|
| `findRouteByPath(tree, path)` | Finds a `RouteNode` by its URL path. It also matches a dynamic pattern |
|
|
409
398
|
|
|
410
|
-
###
|
|
399
|
+
### The machine half — subpath `@xmachines/play-router/xstate`
|
|
400
|
+
|
|
401
|
+
A route of XMachines is a fact about a STATE. This entry point reads that fact out of an
|
|
402
|
+
XState v5 machine. The main entry point reads it never, so `xstate` is an OPTIONAL peer
|
|
403
|
+
dependency of this package, and a bundler drops `@statelyai/graph` for a consumer that
|
|
404
|
+
imports this entry point nowhere.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { createRouteMap, extractMachineRoutes } from "@xmachines/play-router/xstate";
|
|
408
|
+
```
|
|
411
409
|
|
|
412
410
|
| Export | Description |
|
|
413
411
|
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
412
|
+
| `extractMachineRoutes(machine)` | Converts an XState machine into a `RouteTree` with the state ID ↔ path maps |
|
|
413
|
+
| `createRouteMap(machine, options?)` | Builds a `RouteMap` directly from a machine. An adapter uses this form |
|
|
414
|
+
| `machineToGraph(machine)` | Converts a machine into a typed `@statelyai/graph` `Graph`, for a graph algorithm |
|
|
414
415
|
| `getRoutableRoutes(tree)` | Returns every routable `RouteNode` in one flat array |
|
|
415
416
|
| `getNavigableRoutes(tree, stateId)` | Returns the child routes that a state can reach, through the hierarchy and through a transition |
|
|
416
417
|
| `routeExists(tree, path)` | Tells you if the tree holds a path |
|
|
417
418
|
| `getRouteMappings(tree, options?)` | The `{ stateId, path }` entries for a route table of a host, with an optional prefix |
|
|
418
419
|
| `getTransitionReachableRoutes(graph, stateId)` | Returns the route paths that a state can reach through an XState transition |
|
|
419
420
|
| `isRouteReachable(graph, fromStateId, toStateId)` | Tells you if a transition path is present between two states |
|
|
421
|
+
| `MachineGraph` | The typed `@statelyai/graph` Graph, with `MachineNodeData` and `MachineEdgeData` |
|
|
422
|
+
|
|
423
|
+
No framework router adapter re-exports `createRouteMap`. Each one names no state machine,
|
|
424
|
+
so a consumer of an adapter imports the function from `@xmachines/play-router/xstate`.
|
|
420
425
|
|
|
421
426
|
### Router Bridge
|
|
422
427
|
|
|
@@ -472,7 +477,7 @@ own effects and nothing else.
|
|
|
472
477
|
| `mountKey(basePath, params?)` | A key that changes when the mount changes, compared BY VALUE |
|
|
473
478
|
| `isMountableBridge(bridge)` | The run-time probe for a bridge that can move its mount |
|
|
474
479
|
| `createRouterConnection(bridge)` | Wraps a live bridge in the callable `RouterConnection` that `connectRouter` returns |
|
|
475
|
-
| `RouterConnection` | The callable handle: `disconnect()`,
|
|
480
|
+
| `RouterConnection` | The callable handle: `disconnect()`, the mount to read and to move, and a `Disposable` |
|
|
476
481
|
|
|
477
482
|
Nothing here imports a framework, so this package keeps no framework dependency —
|
|
478
483
|
`tests/provider-factory-parity.test.ts` holds that. `TNode` is the only thing in the
|
|
@@ -488,43 +493,40 @@ props that a framework decides, which is why it is a type parameter.
|
|
|
488
493
|
|
|
489
494
|
### Key Types
|
|
490
495
|
|
|
491
|
-
| Export
|
|
492
|
-
|
|
|
493
|
-
| `RouterBridge`
|
|
494
|
-
| `MountableRouterBridge`
|
|
495
|
-
| `RouteTree`
|
|
496
|
-
| `RouteNode`
|
|
497
|
-
| `RouteInfo`
|
|
498
|
-
| `PlayRouteEvent`
|
|
499
|
-
| `
|
|
500
|
-
| `
|
|
501
|
-
| `RouteMapping`
|
|
502
|
-
| `
|
|
503
|
-
| `
|
|
504
|
-
| `
|
|
505
|
-
| `
|
|
496
|
+
| Export | Description |
|
|
497
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
498
|
+
| `RouterBridge` | The interface of the `connect()` and `disconnect()` lifecycle |
|
|
499
|
+
| `MountableRouterBridge` | A `RouterBridge` whose mount can move — it adds `basePath` and `setBasePath()` |
|
|
500
|
+
| `RouteTree` | The hierarchical tree, with `root`, `byStateId`, `byPath`, and an optional `graph` |
|
|
501
|
+
| `RouteNode` | One node of the tree, with `id`, `path`, `fullPath`, `stateId`, `children`, and `parent` |
|
|
502
|
+
| `RouteInfo` | The flat route descriptor that comes from a state node |
|
|
503
|
+
| `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query?, data? }` |
|
|
504
|
+
| `Routable` | The optional routing capability of an actor: `currentRoute` and `initialRoute` |
|
|
505
|
+
| `RoutableActor` | The actor interface that `RouterBridgeBase` and `PlayRouterProvider` require: `currentRoute`, `initialRoute`, and `send(PlayRouteEvent)` |
|
|
506
|
+
| `RouteMapping` | The `{ stateId, path }` pair that builds a `RouteMap` |
|
|
507
|
+
| `RouteObject` | The object form of `meta.route`: `path`, and the `handler`, `reenter` and `data` fields |
|
|
508
|
+
| `RouteMetadata` | The union of the two forms of `meta.route`: the path string, or a `RouteObject` |
|
|
509
|
+
| `RouteData` | The resolved extra data of a route, which reaches the machine as `context.data` |
|
|
510
|
+
| `RouteDataResolver` | The function form of `RouteObject.data`: `({ context, event }) => RouteData` |
|
|
511
|
+
| `WindowLike` | The minimal `window` interface that you can inject for SSR and for a test |
|
|
512
|
+
| `LocationLike` | The minimal `location` interface that you can inject for SSR and for a test |
|
|
506
513
|
|
|
507
514
|
### Errors (subpath `@xmachines/play-router/errors`)
|
|
508
515
|
|
|
509
|
-
| Class
|
|
510
|
-
|
|
|
511
|
-
| `RouterSyncError`
|
|
512
|
-
| `DuplicateBridgeError`
|
|
513
|
-
| `
|
|
514
|
-
| `
|
|
515
|
-
| `
|
|
516
|
-
| `
|
|
517
|
-
| `
|
|
518
|
-
| `
|
|
519
|
-
| `
|
|
520
|
-
| `MissingBasePathParamError` | `PLAY_ROUTER_MISSING_BASE_PATH_PARAM` | A `:param` of a `basePath` has no value in `basePathParams` |
|
|
516
|
+
| Class | Code | When thrown |
|
|
517
|
+
| --------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
518
|
+
| `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()` cannot send a `play.route` event |
|
|
519
|
+
| `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one |
|
|
520
|
+
| `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` | A route pattern does not compile, or two of its params land on one URLPattern group |
|
|
521
|
+
| `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""` |
|
|
522
|
+
| `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route names a state ID that the machine graph does not hold |
|
|
523
|
+
| `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path |
|
|
524
|
+
| `UnknownStateTypeError` | `PLAY_ROUTE_UNKNOWN_STATE_TYPE` | A state node has an XState `.type` value that the package does not know |
|
|
525
|
+
| `InvalidBasePathError` | `PLAY_ROUTER_INVALID_BASE_PATH` | A `basePath` resolves to one concrete prefix never (`*`, `:p?`, `$p`, `?`, `#`, a scheme, `.`/`..`, whitespace) |
|
|
526
|
+
| `MissingBasePathParamError` | `PLAY_ROUTER_MISSING_BASE_PATH_PARAM` | A `:param` of a `basePath` has no value in `basePathParams` |
|
|
521
527
|
|
|
522
528
|
```typescript
|
|
523
|
-
import {
|
|
524
|
-
RouterSyncError,
|
|
525
|
-
DuplicateBridgeError,
|
|
526
|
-
URLPatternUnavailableError,
|
|
527
|
-
} from "@xmachines/play-router/errors";
|
|
529
|
+
import { RouterSyncError, DuplicateBridgeError } from "@xmachines/play-router/errors";
|
|
528
530
|
|
|
529
531
|
// bridge from the adapter example above
|
|
530
532
|
try {
|
|
@@ -565,6 +567,28 @@ states: {
|
|
|
565
567
|
}
|
|
566
568
|
```
|
|
567
569
|
|
|
570
|
+
### The object form
|
|
571
|
+
|
|
572
|
+
The object form carries the path and any metadata of your own, such as a title or a
|
|
573
|
+
breadcrumb. It also carries three fields that the routing layer reads:
|
|
574
|
+
|
|
575
|
+
| Field | Default | What it does |
|
|
576
|
+
| --------- | -------- | ------------------------------------------------------------------------------- |
|
|
577
|
+
| `path` | required | The template of the route path |
|
|
578
|
+
| `handler` | `"root"` | Where the generated `play.route` transition sits: `"root"`, `"local"`, `"both"` |
|
|
579
|
+
| `reenter` | `false` | Whether the transition re-enters its own domain. See the note below. |
|
|
580
|
+
| `data` | none | The extra data of the route: a literal value, or a function of the event |
|
|
581
|
+
|
|
582
|
+
`@xmachines/play-xstate` reads `handler`, `reenter`, and `data` when it generates the
|
|
583
|
+
transitions. See the README of that package for what each value does.
|
|
584
|
+
|
|
585
|
+
**The four names are RESERVED, and this is a change of behaviour.** An earlier release
|
|
586
|
+
read `path` alone, so a state could put any name of its own beside it. A state that
|
|
587
|
+
carries its own `handler` value now throws an `InvalidRouteHandlerError` at the format
|
|
588
|
+
time, and the machine builds never; a state that carries its own `data` value now writes
|
|
589
|
+
that value to `context.data`, where the `/context` projection of every view reads it.
|
|
590
|
+
Give such a field another name.
|
|
591
|
+
|
|
568
592
|
### Relative vs absolute paths
|
|
569
593
|
|
|
570
594
|
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:
|
|
@@ -590,47 +614,17 @@ states: {
|
|
|
590
614
|
|
|
591
615
|
Always use `node.fullPath` to match a browser URL and to build a route map. Never use `node.path` for this.
|
|
592
616
|
|
|
593
|
-
## Testing
|
|
594
|
-
|
|
595
|
-
```bash
|
|
596
|
-
# Run tests for this package
|
|
597
|
-
pnpm --filter @xmachines/play-router test
|
|
598
|
-
|
|
599
|
-
# Watch mode
|
|
600
|
-
pnpm --filter @xmachines/play-router run test:watch
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
`@xmachines/play-router-shared` holds a contract test suite of the router bridge, for the
|
|
604
|
-
author of an adapter. That suite drives a real actor. Therefore it is one layer above this
|
|
605
|
-
package, and `@xmachines/play-router` keeps no dependency on an actor runtime.
|
|
606
|
-
`@xmachines/play-router-shared` is a private workspace package. Thus only an adapter author
|
|
607
|
-
in this repository can use the suite:
|
|
608
|
-
|
|
609
|
-
```typescript
|
|
610
|
-
import { runBridgeContractTests } from "@xmachines/play-router-shared/test/router-bridge-contract.js";
|
|
611
|
-
|
|
612
|
-
runBridgeContractTests({
|
|
613
|
-
name: "MyRouterBridge",
|
|
614
|
-
createHarness(initialPath) {
|
|
615
|
-
// return ContractHarness with bridge, actor, simulateNavigation, getLastNavigatedPath
|
|
616
|
-
},
|
|
617
|
-
createRestoredHarness(routedPath) {
|
|
618
|
-
// return ContractHarness whose actor is restored to routedPath
|
|
619
|
-
// while the mock router starts at the machine's initial route
|
|
620
|
-
},
|
|
621
|
-
});
|
|
622
|
-
```
|
|
623
|
-
|
|
624
617
|
## Related Packages
|
|
625
618
|
|
|
626
619
|
- **[@xmachines/play](../play/README.md)** — Core protocol types (`PlayEvent`, `PlayError`)
|
|
627
|
-
- **[@xmachines/play-actor](../play-actor/README.md)** — the
|
|
620
|
+
- **[@xmachines/play-actor](../play-actor/README.md)** — the actor contract (`PlayActor`). An actor that adds `Routable` of this package satisfies `RoutableActor` structurally
|
|
621
|
+
- **[@xmachines/play-url](../play-url/README.md)** — the URL language. This package re-exports its base path and its framework params, for an adapter on `RouterBridgeBase`
|
|
628
622
|
- **[@xmachines/play-signals](../play-signals/README.md)** — the TC39 Signals polyfill that observes the actor route
|
|
629
623
|
- **[@xmachines/play-xstate](../play-xstate/README.md)** — the XState v5 logic adapter, which works with a route tree
|
|
630
624
|
- **[@xmachines/play-tanstack-router](../play-tanstack-router/README.md)** — Shared TanStack Router bridge base (framework-agnostic)
|
|
631
625
|
- **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router/README.md)** — TanStack Router adapter (React)
|
|
632
626
|
- **[@xmachines/play-tanstack-solid-router](../play-tanstack-solid-router/README.md)** — TanStack Router adapter (SolidJS)
|
|
633
|
-
- **[@xmachines/play-react-router](../play-react-router/README.md)** — React Router
|
|
627
|
+
- **[@xmachines/play-react-router](../play-react-router/README.md)** — React Router 7/8 adapter
|
|
634
628
|
- **[@xmachines/play-vue-router](../play-vue-router/README.md)** — Vue Router adapter
|
|
635
629
|
- **[@xmachines/play-solid-router](../play-solid-router/README.md)** — SolidJS Router adapter
|
|
636
630
|
|
package/dist/base-route-map.d.ts
CHANGED
|
@@ -45,11 +45,17 @@ export interface RouteMapping {
|
|
|
45
45
|
* - The class keeps each result of a first match in an LRU cache. The default size
|
|
46
46
|
* is 500 entries, and the `cacheSize` constructor option changes it
|
|
47
47
|
*
|
|
48
|
-
* **The syntax of a pattern
|
|
48
|
+
* **The syntax of a pattern:** the whole URLPattern pathname grammar. The forms used most
|
|
49
|
+
* often are:
|
|
49
50
|
* - `:param` — a necessary segment. It matches exactly one segment without a `/`
|
|
50
51
|
* - `:param?` — an optional segment. It matches zero segments or one segment without a `/`
|
|
51
52
|
* - `*` — a wildcard. It matches each number of segments, as URLPattern defines
|
|
52
53
|
*
|
|
54
|
+
* A `{…}` group, a `:param(\d+)` constraint, a `+` modifier and a `\:` escape are read
|
|
55
|
+
* too. The [routing guide](../../docs/guides/routing.md) states every form, the prefix
|
|
56
|
+
* rule, and the one divergence from the standard: a param name of this library can hold a
|
|
57
|
+
* hyphen.
|
|
58
|
+
*
|
|
53
59
|
* **The forms of a stateId:** you can register a stateId, and you can look one up,
|
|
54
60
|
* in the form `"#stateId"` or in the form `"stateId"`. `RouteMap` makes the
|
|
55
61
|
* canonical form itself. `getStateIdByPath` returns the stateId exactly as you
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;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;IA0CtF;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA4B7C;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAGhD"}
|
package/dist/base-route-map.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* segment, and that number is much smaller than the number of all the routes. The
|
|
11
11
|
* class matches a parameterized route with URLPattern.
|
|
12
12
|
*/
|
|
13
|
+
import { firstMarkIndex, getCandidates, getCompiledPattern, getIndexKey, parsePattern, } from "@xmachines/play-url";
|
|
13
14
|
import QuickLRU from "quick-lru";
|
|
14
|
-
import {
|
|
15
|
-
import { URLPatternUnavailableError, InvalidRoutePatternError } from "./errors.js";
|
|
15
|
+
import { InvalidRoutePatternError } from "./errors.js";
|
|
16
16
|
/**
|
|
17
17
|
* The canonical key of a stateId: the bare form, without a `#` at its start.
|
|
18
18
|
*
|
|
@@ -38,11 +38,17 @@ function canonicalStateIdKey(stateId) {
|
|
|
38
38
|
* - The class keeps each result of a first match in an LRU cache. The default size
|
|
39
39
|
* is 500 entries, and the `cacheSize` constructor option changes it
|
|
40
40
|
*
|
|
41
|
-
* **The syntax of a pattern
|
|
41
|
+
* **The syntax of a pattern:** the whole URLPattern pathname grammar. The forms used most
|
|
42
|
+
* often are:
|
|
42
43
|
* - `:param` — a necessary segment. It matches exactly one segment without a `/`
|
|
43
44
|
* - `:param?` — an optional segment. It matches zero segments or one segment without a `/`
|
|
44
45
|
* - `*` — a wildcard. It matches each number of segments, as URLPattern defines
|
|
45
46
|
*
|
|
47
|
+
* A `{…}` group, a `:param(\d+)` constraint, a `+` modifier and a `\:` escape are read
|
|
48
|
+
* too. The [routing guide](../../docs/guides/routing.md) states every form, the prefix
|
|
49
|
+
* rule, and the one divergence from the standard: a param name of this library can hold a
|
|
50
|
+
* hyphen.
|
|
51
|
+
*
|
|
46
52
|
* **The forms of a stateId:** you can register a stateId, and you can look one up,
|
|
47
53
|
* in the form `"#stateId"` or in the form `"stateId"`. `RouteMap` makes the
|
|
48
54
|
* canonical form itself. `getStateIdByPath` returns the stateId exactly as you
|
|
@@ -104,16 +110,18 @@ export class RouteMap {
|
|
|
104
110
|
// "#stateId" and also "stateId". The map keeps the registered form for its return
|
|
105
111
|
// values.
|
|
106
112
|
this.stateIdToPath.set(canonicalStateIdKey(stateId), path);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
// ONE parse answers both questions: is the path a pattern, and which bucket does
|
|
114
|
+
// it belong to. The key comes from that parse, and not from a split of the raw
|
|
115
|
+
// string. A pattern such as `/books{/:id}?` keeps the literal first segment
|
|
116
|
+
// `books`, and a split on `/` read the key `books{` and put the route in a bucket
|
|
117
|
+
// that the path `/books/1` reaches never.
|
|
118
|
+
const parsed = parsePattern(path);
|
|
119
|
+
if (parsed.parameterized) {
|
|
120
|
+
const bucketKey = parsed.indexKey;
|
|
113
121
|
const bucket = this.patternBuckets.get(bucketKey) ?? [];
|
|
114
122
|
try {
|
|
115
123
|
bucket.push({
|
|
116
|
-
pattern: getCompiledPattern(path
|
|
124
|
+
pattern: getCompiledPattern(path),
|
|
117
125
|
stateId,
|
|
118
126
|
order: patternOrder++,
|
|
119
127
|
});
|
|
@@ -150,8 +158,13 @@ export class RouteMap {
|
|
|
150
158
|
* ```
|
|
151
159
|
*/
|
|
152
160
|
getStateIdByPath(path) {
|
|
153
|
-
// Remove the query string and the hash fragment before the match
|
|
154
|
-
|
|
161
|
+
// Remove the query string and the hash fragment before the match. `firstMarkIndex`
|
|
162
|
+
// is the ONE answer to "where does a pathname end", and `RouterBridgeBase` and
|
|
163
|
+
// `framework-params.ts` read it too, so the three cannot disagree. Two splits
|
|
164
|
+
// allocated two arrays of every part on the hottest lookup of the library, and this
|
|
165
|
+
// function then read one element of each.
|
|
166
|
+
const mark = firstMarkIndex(path);
|
|
167
|
+
const cleanPath = mark === -1 ? path : path.slice(0, mark);
|
|
155
168
|
const exactMatch = this.pathToStateId.get(cleanPath);
|
|
156
169
|
if (exactMatch !== undefined)
|
|
157
170
|
return exactMatch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACN,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,YAAY,GAEZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAAC,OAAe;IAC3C,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAC7D,CAAC;AAyBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,OAAO,QAAQ;IACpB,8EAA8E;IACtE,aAAa,CAAsB;IACnC,aAAa,CAAsB;IACnC,cAAc,CAGpB;IACM,cAAc,CAAkC;IAExD;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,QAAwB,EAAE,EAAE,SAAS,GAAG,GAAG,KAA6B,EAAE;QACrF,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;QAChC,IAAI,CAAC,cAAc,GAAG,IAAI,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;QACxE,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1C,yEAAyE;YACzE,kFAAkF;YAClF,UAAU;YACV,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;YAE3D,iFAAiF;YACjF,+EAA+E;YAC/E,4EAA4E;YAC5E,kFAAkF;YAClF,0CAA0C;YAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC;gBAClC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBACxD,IAAI,CAAC;oBACJ,MAAM,CAAC,IAAI,CAAC;wBACX,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC;wBACjC,OAAO;wBACP,KAAK,EAAE,YAAY,EAAE;qBACrB,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,8EAA8E;oBAC9E,uEAAuE;oBACvE,4DAA4D;oBAC5D,IAAI,GAAG,YAAY,wBAAwB;wBAAE,MAAM,GAAG,CAAC;oBACvD,MAAM,IAAI,wBAAwB,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;iBAAM,CAAC;gBACP,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvC,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAY;QAC5B,mFAAmF;QACnF,+EAA+E;QAC/E,8EAA8E;QAC9E,oFAAoF;QACpF,0CAA0C;QAC1C,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAE3D,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrD,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC;QAEhD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,WAAW,KAAK,SAAS;YAAE,OAAO,WAAW,CAAC;QAElD,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9E,KAAK,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,UAAU,EAAE,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;YACpD,IAAI,KAAK,EAAE,CAAC;gBACX,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBAC5C,OAAO,OAAO,CAAC;YAChB,CAAC;QACF,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAe;QAC/B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC;IACrE,CAAC;CACD"}
|
package/dist/build-tree.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { RouteInfo, RouteTree } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The ID and the state ID of the synthetic root node of every `RouteTree`.
|
|
4
|
+
*
|
|
5
|
+
* `buildRouteTree` always makes this node, and no state of a machine declares it. It
|
|
6
|
+
* gives the tree one parent for each absolute route, and it holds `routable: false`.
|
|
7
|
+
*
|
|
8
|
+
* A consumer that walks a tree must SKIP this node. `createRouteMapFromTree` skips it,
|
|
9
|
+
* and `getRoutableRoutes` of `@xmachines/play-router/xstate` skips it. A walk that keeps
|
|
10
|
+
* it maps the path `/` to a state that the machine does not hold.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ROOT_NODE_ID = "__root__";
|
|
2
13
|
/**
|
|
3
14
|
* Builds the hierarchical route tree from the flat list of the routes
|
|
4
15
|
*
|
|
@@ -13,9 +24,13 @@ import type { RouteInfo, RouteTree } from "./types.js";
|
|
|
13
24
|
* strings that resolve to the same complete path are not: those two overwrite each
|
|
14
25
|
* other in `byPath` in silence.
|
|
15
26
|
*
|
|
27
|
+
* `TGraph` names the type of the graph that a CALLER attaches afterwards. The builder
|
|
28
|
+
* attaches none: it reads a list of routes and writes the maps of the tree.
|
|
29
|
+
* `extractMachineRoutes` of `@xmachines/play-router/xstate` binds it to `MachineGraph`.
|
|
30
|
+
*
|
|
16
31
|
* @param routes - The flat list of the RouteInfo objects, from the extraction
|
|
17
32
|
* @returns The RouteTree, with its root, its byStateId map, and its byPath map
|
|
18
33
|
* @throws {DuplicateRoutePathError} When two states resolve to the same complete path
|
|
19
34
|
*/
|
|
20
|
-
export declare const buildRouteTree: (routes: RouteInfo[]) => RouteTree
|
|
35
|
+
export declare const buildRouteTree: <TGraph = unknown>(routes: RouteInfo[]) => RouteTree<TGraph>;
|
|
21
36
|
//# sourceMappingURL=build-tree.d.ts.map
|
package/dist/build-tree.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,SAAS,EAAE,MAAM,YAAY,CAAC;AAGlE;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,GAAG,OAAO,EAAE,QAAQ,SAAS,EAAE,KAAG,SAAS,CAAC,MAAM,CA0EtF,CAAC"}
|
package/dist/build-tree.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { detectDuplicateRoutes } from "./validate-routes.js";
|
|
2
|
+
/**
|
|
3
|
+
* The ID and the state ID of the synthetic root node of every `RouteTree`.
|
|
4
|
+
*
|
|
5
|
+
* `buildRouteTree` always makes this node, and no state of a machine declares it. It
|
|
6
|
+
* gives the tree one parent for each absolute route, and it holds `routable: false`.
|
|
7
|
+
*
|
|
8
|
+
* A consumer that walks a tree must SKIP this node. `createRouteMapFromTree` skips it,
|
|
9
|
+
* and `getRoutableRoutes` of `@xmachines/play-router/xstate` skips it. A walk that keeps
|
|
10
|
+
* it maps the path `/` to a state that the machine does not hold.
|
|
11
|
+
*/
|
|
12
|
+
export const ROOT_NODE_ID = "__root__";
|
|
2
13
|
/**
|
|
3
14
|
* Builds the hierarchical route tree from the flat list of the routes
|
|
4
15
|
*
|
|
@@ -13,6 +24,10 @@ import { detectDuplicateRoutes } from "./validate-routes.js";
|
|
|
13
24
|
* strings that resolve to the same complete path are not: those two overwrite each
|
|
14
25
|
* other in `byPath` in silence.
|
|
15
26
|
*
|
|
27
|
+
* `TGraph` names the type of the graph that a CALLER attaches afterwards. The builder
|
|
28
|
+
* attaches none: it reads a list of routes and writes the maps of the tree.
|
|
29
|
+
* `extractMachineRoutes` of `@xmachines/play-router/xstate` binds it to `MachineGraph`.
|
|
30
|
+
*
|
|
16
31
|
* @param routes - The flat list of the RouteInfo objects, from the extraction
|
|
17
32
|
* @returns The RouteTree, with its root, its byStateId map, and its byPath map
|
|
18
33
|
* @throws {DuplicateRoutePathError} When two states resolve to the same complete path
|
|
@@ -20,10 +35,10 @@ import { detectDuplicateRoutes } from "./validate-routes.js";
|
|
|
20
35
|
export const buildRouteTree = (routes) => {
|
|
21
36
|
// 1. Create the root node
|
|
22
37
|
const root = {
|
|
23
|
-
id:
|
|
38
|
+
id: ROOT_NODE_ID,
|
|
24
39
|
path: "/",
|
|
25
40
|
fullPath: "/",
|
|
26
|
-
stateId:
|
|
41
|
+
stateId: ROOT_NODE_ID,
|
|
27
42
|
routable: false, // The root is a state without a route
|
|
28
43
|
children: [],
|
|
29
44
|
parent: null,
|
|
@@ -33,8 +48,7 @@ export const buildRouteTree = (routes) => {
|
|
|
33
48
|
const byStateId = new Map([[root.stateId, root]]);
|
|
34
49
|
const byPath = new Map([[root.fullPath, root]]);
|
|
35
50
|
// 3. Sort the routes by their depth. The shallowest route comes first, for the link to each parent
|
|
36
|
-
|
|
37
|
-
const sorted = routes.slice().sort((a, b) => a.statePath.length - b.statePath.length);
|
|
51
|
+
const sorted = routes.toSorted((a, b) => a.statePath.length - b.statePath.length);
|
|
38
52
|
// 4. Build the tree, and collect each node for the detection of a duplicate resolved path
|
|
39
53
|
const insertedNodes = [];
|
|
40
54
|
for (const route of sorted) {
|
|
@@ -45,8 +59,11 @@ export const buildRouteTree = (routes) => {
|
|
|
45
59
|
let parentNode = root;
|
|
46
60
|
for (let i = route.statePath.length - 2; i >= 0; i--) {
|
|
47
61
|
const parentStateId = route.statePath[i]; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
48
|
-
if (
|
|
49
|
-
|
|
62
|
+
if (parentStateId === undefined)
|
|
63
|
+
continue;
|
|
64
|
+
const candidate = byStateId.get(parentStateId);
|
|
65
|
+
if (candidate !== undefined) {
|
|
66
|
+
parentNode = candidate;
|
|
50
67
|
break;
|
|
51
68
|
}
|
|
52
69
|
}
|
package/dist/build-tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D
|
|
1
|
+
{"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { RouteTree } from "./types.js";
|
|
1
|
+
import type { RouteTree, RouteMapOptions } from "./types.js";
|
|
2
2
|
import { RouteMap } from "./base-route-map.js";
|
|
3
|
-
import type { RouteMapOptions } from "./create-route-map.js";
|
|
4
3
|
/**
|
|
5
4
|
* Creates a `RouteMap` from the node structure of a `RouteTree`.
|
|
6
5
|
*
|
|
7
|
-
* A framework router adapter uses this function when it
|
|
8
|
-
* `extractMachineRoutes()`, and not when it calls
|
|
6
|
+
* A framework router adapter uses this function when it holds a `RouteTree`, for example
|
|
7
|
+
* from `extractMachineRoutes()` of `@xmachines/play-router/xstate`, and not when it calls
|
|
8
|
+
* `createRouteMap()` of that package directly.
|
|
9
9
|
*
|
|
10
10
|
* The function walks every node, and it collects the pairs
|
|
11
11
|
* `{ stateId: node.id, path: node.fullPath }`. `node.fullPath` is always the
|
|
@@ -13,6 +13,11 @@ import type { RouteMapOptions } from "./create-route-map.js";
|
|
|
13
13
|
* that path for the match of a browser URL. `createRouteMap(machine)` behaves in the
|
|
14
14
|
* same way, because it also uses `node.fullPath`.
|
|
15
15
|
*
|
|
16
|
+
* The walk SKIPS the synthetic root node. `buildRouteTree` makes that node to hold each
|
|
17
|
+
* absolute route, and no state of a machine declares it. `createRouteMap` skips it as
|
|
18
|
+
* well, through `getRoutableRoutes`, and the two functions therefore answer the same map
|
|
19
|
+
* for the same tree.
|
|
20
|
+
*
|
|
16
21
|
* @param routeTree - A `RouteTree`, as `extractMachineRoutes()` returns it.
|
|
17
22
|
* @param options - The optional configuration, for example `{ cacheSize }` to change the size of the LRU cache.
|
|
18
23
|
* @returns A `RouteMap` for each adapter on `RouterBridgeBase`.
|
|
@@ -20,11 +25,12 @@ import type { RouteMapOptions } from "./create-route-map.js";
|
|
|
20
25
|
* @example
|
|
21
26
|
* ```typescript
|
|
22
27
|
* // The preferred form — one call for an XState machine:
|
|
23
|
-
* import { createRouteMap } from '@xmachines/play-router';
|
|
28
|
+
* import { createRouteMap } from '@xmachines/play-router/xstate';
|
|
24
29
|
* const routeMap = createRouteMap(machine); // it takes an AnyStateMachine
|
|
25
30
|
*
|
|
26
31
|
* // The two-step form, for a framework adapter that works with a route tree:
|
|
27
|
-
* import {
|
|
32
|
+
* import { createRouteMapFromTree } from '@xmachines/play-router';
|
|
33
|
+
* import { extractMachineRoutes } from '@xmachines/play-router/xstate';
|
|
28
34
|
* const routeTree = extractMachineRoutes(machine);
|
|
29
35
|
* const routeMap = createRouteMapFromTree(routeTree); // it uses node.fullPath, which is absolute
|
|
30
36
|
* ```
|