@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.
Files changed (83) hide show
  1. package/README.md +103 -109
  2. package/dist/base-route-map.d.ts +7 -1
  3. package/dist/base-route-map.d.ts.map +1 -1
  4. package/dist/base-route-map.js +25 -12
  5. package/dist/base-route-map.js.map +1 -1
  6. package/dist/build-tree.d.ts +16 -1
  7. package/dist/build-tree.d.ts.map +1 -1
  8. package/dist/build-tree.js +23 -6
  9. package/dist/build-tree.js.map +1 -1
  10. package/dist/create-route-map-from-tree.d.ts +12 -6
  11. package/dist/create-route-map-from-tree.d.ts.map +1 -1
  12. package/dist/create-route-map-from-tree.js +13 -5
  13. package/dist/create-route-map-from-tree.js.map +1 -1
  14. package/dist/errors.d.ts +2 -134
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +11 -151
  17. package/dist/errors.js.map +1 -1
  18. package/dist/find-route.d.ts.map +1 -1
  19. package/dist/find-route.js +14 -19
  20. package/dist/find-route.js.map +1 -1
  21. package/dist/index.d.ts +7 -16
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +10 -11
  24. package/dist/index.js.map +1 -1
  25. package/dist/provider-lifecycle.d.ts +28 -6
  26. package/dist/provider-lifecycle.d.ts.map +1 -1
  27. package/dist/provider-lifecycle.js +16 -2
  28. package/dist/provider-lifecycle.js.map +1 -1
  29. package/dist/router-bridge-base.d.ts +42 -23
  30. package/dist/router-bridge-base.d.ts.map +1 -1
  31. package/dist/router-bridge-base.js +257 -84
  32. package/dist/router-bridge-base.js.map +1 -1
  33. package/dist/router-sync.d.ts +1 -1
  34. package/dist/router-sync.d.ts.map +1 -1
  35. package/dist/router-sync.js +28 -13
  36. package/dist/router-sync.js.map +1 -1
  37. package/dist/types.d.ts +186 -87
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/{create-route-map.d.ts → xstate/create-route-map.d.ts} +6 -20
  40. package/dist/xstate/create-route-map.d.ts.map +1 -0
  41. package/dist/{create-route-map.js → xstate/create-route-map.js} +5 -3
  42. package/dist/xstate/create-route-map.js.map +1 -0
  43. package/dist/{extract-routes.d.ts → xstate/extract-routes.d.ts} +3 -2
  44. package/dist/xstate/extract-routes.d.ts.map +1 -0
  45. package/dist/{extract-routes.js → xstate/extract-routes.js} +8 -3
  46. package/dist/xstate/extract-routes.js.map +1 -0
  47. package/dist/xstate/index.d.ts +35 -0
  48. package/dist/xstate/index.d.ts.map +1 -0
  49. package/dist/xstate/index.js +33 -0
  50. package/dist/xstate/index.js.map +1 -0
  51. package/dist/{machine-to-graph.d.ts → xstate/machine-to-graph.d.ts} +1 -1
  52. package/dist/xstate/machine-to-graph.d.ts.map +1 -0
  53. package/dist/{machine-to-graph.js → xstate/machine-to-graph.js} +2 -2
  54. package/dist/xstate/machine-to-graph.js.map +1 -0
  55. package/dist/xstate/machine-types.d.ts +42 -0
  56. package/dist/xstate/machine-types.d.ts.map +1 -0
  57. package/dist/xstate/machine-types.js +2 -0
  58. package/dist/xstate/machine-types.js.map +1 -0
  59. package/dist/{query.d.ts → xstate/query.d.ts} +6 -4
  60. package/dist/xstate/query.d.ts.map +1 -0
  61. package/dist/{query.js → xstate/query.js} +3 -2
  62. package/dist/xstate/query.js.map +1 -0
  63. package/package.json +26 -20
  64. package/dist/base-path.d.ts +0 -209
  65. package/dist/base-path.d.ts.map +0 -1
  66. package/dist/base-path.js +0 -418
  67. package/dist/base-path.js.map +0 -1
  68. package/dist/create-route-map.d.ts.map +0 -1
  69. package/dist/create-route-map.js.map +0 -1
  70. package/dist/extract-routes.d.ts.map +0 -1
  71. package/dist/extract-routes.js.map +0 -1
  72. package/dist/framework-params.d.ts +0 -144
  73. package/dist/framework-params.d.ts.map +0 -1
  74. package/dist/framework-params.js +0 -291
  75. package/dist/framework-params.js.map +0 -1
  76. package/dist/machine-to-graph.d.ts.map +0 -1
  77. package/dist/machine-to-graph.js.map +0 -1
  78. package/dist/query.d.ts.map +0 -1
  79. package/dist/query.js.map +0 -1
  80. package/dist/url-pattern-utils.d.ts +0 -93
  81. package/dist/url-pattern-utils.d.ts.map +0 -1
  82. package/dist/url-pattern-utils.js +0 -233
  83. 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
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-2.2.0-blue)](https://www.npmjs.com/package/@xmachines/play-router)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Version](https://img.shields.io/badge/version-4.0.0-blue)](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.31.0
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.31.0 — XState v5 state machine runtime
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
- `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.
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
- **When you need it:** `RouteMap` compiles each parameterized route in its CONSTRUCTOR, and it throws a `URLPatternUnavailableError` there when no URLPattern is available. One route that holds a `:param` or a `*` therefore makes the polyfill a startup requirement on such a runtime. A route map whose paths are all static needs URLPattern never.
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, and they
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
- > and with no polyfill, but restricted to the names that the pattern of the machine
323
- > declares. A splat of a catch-all, and a param of a wrapper route, reach the actor
324
- > never.
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 segment reaches URLPattern never. `/settings` is the
327
- > bare form of `/settings/:section?`, so the params are `{}`, and the adapters read that
328
- > from the path alone rather than from their framework. The route map that HOLDS that
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, createRouteMap } from "@xmachines/play-router";
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
- | `extractMachineRoutes(machine)` | Converts an XState machine into a `RouteTree` with the state ID ↔ path maps |
397
- | `createRouteMap(machine, options?)` | Builds a `RouteMap` directly from a machine. An adapter uses this form |
398
- | `createRouteMapFromTree(tree, options?)` | Builds a `RouteMap` from a `RouteTree` that you extracted before |
399
- | `buildRouteTree(routes)` | Builds a `RouteTree` from an array of `RouteInfo` objects |
400
- | `machineToGraph(machine)` | Converts a machine into a typed `@statelyai/graph` `Graph`, for a graph algorithm |
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
- ### Query Utilities
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()`, and the mount to read and to move |
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 | Description |
492
- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
493
- | `RouterBridge` | The interface of the `connect()` and `disconnect()` lifecycle |
494
- | `MountableRouterBridge` | A `RouterBridge` whose mount can move — it adds `basePath` and `setBasePath()` |
495
- | `RouteTree` | The hierarchical tree, with `root`, `byStateId`, `byPath`, and an optional `graph` |
496
- | `RouteNode` | One node of the tree, with `id`, `path`, `fullPath`, `stateId`, `children`, and `parent` |
497
- | `RouteInfo` | The flat route descriptor that comes from a state node |
498
- | `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query? }` |
499
- | `RoutableActor` | The minimal actor interface that `RouterBridgeBase` requires: `currentRoute`, `initialRoute`, and `send(PlayRouteEvent)` |
500
- | `PlayActor` | The complete actor interface that `PlayRouterProvider` uses. It extends `RoutableActor` with `currentView` (`Routable + Viewable`) |
501
- | `RouteMapping` | The `{ stateId, path }` pair that builds a `RouteMap` |
502
- | `RouteMapping as BaseRouteMapping` | The alias of `RouteMapping`, for compatibility with an earlier version |
503
- | `MachineGraph` | The typed `@statelyai/graph` Graph, with `MachineNodeData` and `MachineEdgeData` |
504
- | `WindowLike` | The minimal `window` interface that you can inject for SSR and for a test |
505
- | `LocationLike` | The minimal `location` interface that you can inject for SSR and for a test |
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 | Code | When thrown |
510
- | ---------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
511
- | `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()` cannot send a `play.route` event |
512
- | `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one |
513
- | `URLPatternUnavailableError` | `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE` | The URLPattern API is absent, and no polyfill is loaded |
514
- | `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` | A route pattern does not compile, or two of its params land on one URLPattern group |
515
- | `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""` |
516
- | `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route names a state ID that the machine graph does not hold |
517
- | `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path |
518
- | `UnknownStateTypeError` | `PLAY_ROUTE_UNKNOWN_STATE_TYPE` | A state node has an XState `.type` value that the package does not know |
519
- | `InvalidBasePathError` | `PLAY_ROUTER_INVALID_BASE_PATH` | A `basePath` resolves to one concrete prefix never (`*`, `:p?`, `$p`, `?`, `#`, a scheme, `.`/`..`, whitespace) |
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 abstract actor base class (`AbstractActor`, `Routable`). Every `AbstractActor` subclass satisfies `RoutableActor` structurally
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 v7 adapter
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
 
@@ -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** (`:param`, `:param?`, and `*`):
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;IAwCtF;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAuB7C;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAGhD"}
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"}
@@ -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 { getURLPatternCtor, getCompiledPattern, getIndexKey, getCandidates, isParameterizedPattern, } from "./url-pattern-utils.js";
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** (`:param`, `:param?`, and `*`):
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
- if (isParameterizedPattern(path)) {
108
- const URLPatternCtorFn = getURLPatternCtor();
109
- if (!URLPatternCtorFn) {
110
- throw new URLPatternUnavailableError();
111
- }
112
- const bucketKey = getIndexKey(path);
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, URLPatternCtorFn),
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
- const cleanPath = path.split("?")[0].split("#")[0];
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":"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"}
1
+ {"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA4B,SAAS,EAAE,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,EAAE,KAAG,SAyEpD,CAAC"}
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"}
@@ -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: "__root__",
38
+ id: ROOT_NODE_ID,
24
39
  path: "/",
25
40
  fullPath: "/",
26
- stateId: "__root__",
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
- // oxlint-disable-next-line unicorn/no-array-sort
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 (byStateId.has(parentStateId)) {
49
- parentNode = byStateId.get(parentStateId);
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"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"}
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 gives a `RouteTree` from
8
- * `extractMachineRoutes()`, and not when it calls `createRouteMap()` directly.
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 { extractMachineRoutes, createRouteMapFromTree } from '@xmachines/play-router';
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
  * ```