@xmachines/play-router 3.0.0 → 5.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 (85) hide show
  1. package/README.md +111 -116
  2. package/dist/base-route-map.d.ts +24 -13
  3. package/dist/base-route-map.d.ts.map +1 -1
  4. package/dist/base-route-map.js +116 -27
  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 +22 -4
  9. package/dist/build-tree.js.map +1 -1
  10. package/dist/create-route-map-from-tree.d.ts +13 -7
  11. package/dist/create-route-map-from-tree.d.ts.map +1 -1
  12. package/dist/create-route-map-from-tree.js +14 -6
  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 +13 -1
  19. package/dist/find-route.d.ts.map +1 -1
  20. package/dist/find-route.js +33 -26
  21. package/dist/find-route.js.map +1 -1
  22. package/dist/index.d.ts +8 -18
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -12
  25. package/dist/index.js.map +1 -1
  26. package/dist/provider-lifecycle.d.ts +18 -6
  27. package/dist/provider-lifecycle.d.ts.map +1 -1
  28. package/dist/provider-lifecycle.js +10 -5
  29. package/dist/provider-lifecycle.js.map +1 -1
  30. package/dist/router-bridge-base.d.ts +73 -38
  31. package/dist/router-bridge-base.d.ts.map +1 -1
  32. package/dist/router-bridge-base.js +304 -124
  33. package/dist/router-bridge-base.js.map +1 -1
  34. package/dist/router-sync.d.ts +3 -27
  35. package/dist/router-sync.d.ts.map +1 -1
  36. package/dist/router-sync.js +27 -45
  37. package/dist/router-sync.js.map +1 -1
  38. package/dist/types.d.ts +206 -102
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/xstate/create-route-map.d.ts +30 -0
  41. package/dist/xstate/create-route-map.d.ts.map +1 -0
  42. package/dist/{create-route-map.js → xstate/create-route-map.js} +11 -6
  43. package/dist/xstate/create-route-map.js.map +1 -0
  44. package/dist/{extract-routes.d.ts → xstate/extract-routes.d.ts} +4 -2
  45. package/dist/xstate/extract-routes.d.ts.map +1 -0
  46. package/dist/{extract-routes.js → xstate/extract-routes.js} +11 -6
  47. package/dist/xstate/extract-routes.js.map +1 -0
  48. package/dist/xstate/index.d.ts +35 -0
  49. package/dist/xstate/index.d.ts.map +1 -0
  50. package/dist/xstate/index.js +33 -0
  51. package/dist/xstate/index.js.map +1 -0
  52. package/dist/{machine-to-graph.d.ts → xstate/machine-to-graph.d.ts} +1 -1
  53. package/dist/xstate/machine-to-graph.d.ts.map +1 -0
  54. package/dist/{machine-to-graph.js → xstate/machine-to-graph.js} +2 -2
  55. package/dist/xstate/machine-to-graph.js.map +1 -0
  56. package/dist/xstate/machine-types.d.ts +42 -0
  57. package/dist/xstate/machine-types.d.ts.map +1 -0
  58. package/dist/xstate/machine-types.js +2 -0
  59. package/dist/xstate/machine-types.js.map +1 -0
  60. package/dist/{query.d.ts → xstate/query.d.ts} +6 -4
  61. package/dist/xstate/query.d.ts.map +1 -0
  62. package/dist/{query.js → xstate/query.js} +3 -2
  63. package/dist/xstate/query.js.map +1 -0
  64. package/package.json +30 -21
  65. package/dist/base-path.d.ts +0 -209
  66. package/dist/base-path.d.ts.map +0 -1
  67. package/dist/base-path.js +0 -418
  68. package/dist/base-path.js.map +0 -1
  69. package/dist/create-route-map.d.ts +0 -44
  70. package/dist/create-route-map.d.ts.map +0 -1
  71. package/dist/create-route-map.js.map +0 -1
  72. package/dist/extract-routes.d.ts.map +0 -1
  73. package/dist/extract-routes.js.map +0 -1
  74. package/dist/framework-params.d.ts +0 -144
  75. package/dist/framework-params.d.ts.map +0 -1
  76. package/dist/framework-params.js +0 -291
  77. package/dist/framework-params.js.map +0 -1
  78. package/dist/machine-to-graph.d.ts.map +0 -1
  79. package/dist/machine-to-graph.js.map +0 -1
  80. package/dist/query.d.ts.map +0 -1
  81. package/dist/query.js.map +0 -1
  82. package/dist/url-pattern-utils.d.ts +0 -93
  83. package/dist/url-pattern-utils.d.ts.map +0 -1
  84. package/dist/url-pattern-utils.js +0 -233
  85. package/dist/url-pattern-utils.js.map +0 -1
package/README.md CHANGED
@@ -2,51 +2,40 @@
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-3.0.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-5.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
9
  > **Browser floor: Chrome 110, Firefox 115, Safari 16.4.** This package calls the ES2023
10
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. Vite 8 resolves its
12
- > default `baseline-widely-available` target to Firefox 114, which is below it — raise
13
- > `build.target` when you bundle for the browser. The root README carries the table.
14
- >
15
- > A **parameterized route** raises Firefox to **117**, which is the version that has the
16
- > URLPattern API. Load the `urlpattern-polyfill` for an older target, as the Installation
17
- > section below says.
11
+ > `TypeError: ... is not a function` at the extraction of the routes. The root README
12
+ > carries the table.
18
13
 
19
14
  ## Installation
20
15
 
21
16
  ```bash
22
- pnpm add xstate@^5.31.0
23
- pnpm add @xmachines/play-router
17
+ pnpm add @xmachines/play-router @xmachines/play @xmachines/play-atom
24
18
  ```
25
19
 
26
- **Peer dependencies:**
27
-
28
- - `xstate` ^5.31.0 — XState v5 state machine runtime
20
+ **Peer dependencies.** Install them with the package:
29
21
 
30
- **URLPattern polyfill (Node.js < 24 / older browsers):**
22
+ - [`@xmachines/play`](../play/README.md) — the core protocol. This package reads `asCleanup` and `DISPOSE` from it, and the error classes from its `errors` subpath.
23
+ - [`@xmachines/play-atom`](../play-atom/README.md) — the atom primitives. The bridge reads the route of the actor with `watchAtom`.
31
24
 
32
- `@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
+ **Optional peers.** The `@xmachines/play-router/xstate` subpath alone reads them. Install them when you read that subpath, and not before:
33
26
 
34
- In an environment without the native API, load a polyfill **before** you import this package:
35
-
36
- ```typescript
37
- // Entry point — must run before any @xmachines/play-router import
38
- import "urlpattern-polyfill";
27
+ ```bash
28
+ pnpm add xstate@^5.33.0 @statelyai/graph@^2.4.0
39
29
  ```
40
30
 
41
- Install the polyfill:
31
+ - `xstate` ^5.33.0 — the XState v5 runtime.
32
+ - `@statelyai/graph` ^2.4.0 — the graph of the machine that `extractMachineRoutes` walks.
42
33
 
43
- ```bash
44
- pnpm add urlpattern-polyfill
45
- ```
34
+ A consumer of the root barrel installs neither one. `@statelyai/graph` was an ordinary dependency until this release, so every consumer of the root carried it; it now follows the same rule as `xstate`, which the same files read.
46
35
 
47
- `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.
36
+ **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 [`@xmachines/play-url`](../play-url/README.md) carries as an ordinary dependency. **You install nothing and you load nothing.**
48
37
 
49
- **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.
38
+ 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.
50
39
 
51
40
  ## Usage
52
41
 
@@ -54,7 +43,7 @@ pnpm add urlpattern-polyfill
54
43
 
55
44
  ```typescript
56
45
  import { createMachine } from "xstate";
57
- import { extractMachineRoutes, createRouteMap } from "@xmachines/play-router";
46
+ import { extractMachineRoutes, createRouteMap } from "@xmachines/play-router/xstate";
58
47
 
59
48
  const machine = createMachine({
60
49
  id: "app",
@@ -302,7 +291,7 @@ To register the routes of the machine in a host router that declares real route
302
291
  objects, ask for the list — and drop it again when the machine unloads:
303
292
 
304
293
  ```typescript
305
- import { extractMachineRoutes, getRouteMappings } from "@xmachines/play-router";
294
+ import { extractMachineRoutes, getRouteMappings } from "@xmachines/play-router/xstate";
306
295
 
307
296
  const tree = extractMachineRoutes(machine);
308
297
 
@@ -320,30 +309,30 @@ Each `stateId` comes from the route tree, so it carries NO `#`. A host that keys
320
309
  route table on the target of a `play.route` event adds the `#` itself, because the
321
310
  event always carries the prefixed form.
322
311
 
323
- > Under a mount, `@xmachines/play-vue-router` and `@xmachines/play-solid-router` read
312
+ > Under a mount, [`@xmachines/play-vue-router`](../play-vue-router/README.md) and [`@xmachines/play-solid-router`](../play-solid-router/README.md) read
324
313
  > the pre-parsed route params of their framework never: under a prefix the framework
325
314
  > matched a route of the HOST by construction, because the machine owns the suffix of
326
315
  > the path only. Those params therefore describe the route of the machine never, even
327
316
  > when a name collides — a collision carries the value of the HOST. Both adapters
328
- > resolve each param from the stripped path with `URLPattern` instead, and they
329
- > therefore need a polyfill on an older runtime when they are mounted.
317
+ > resolve each param from the stripped path with `URLPattern` instead.
330
318
  >
331
- > Without a prefix both adapters keep the parse of their framework, with its decoding
332
- > and with no polyfill, but restricted to the names that the pattern of the machine
333
- > declares. A splat of a catch-all, and a param of a wrapper route, reach the actor
334
- > never.
319
+ > Without a prefix both adapters keep the parse of their framework, with its decoding,
320
+ > but restricted to the names that the pattern of the machine declares. A splat of a
321
+ > catch-all, and a param of a wrapper route, reach the actor never. A name that the
322
+ > pattern makes OPTIONAL is covered whether the framework reports it or not: a part that
323
+ > did not match has no value, and that is a complete answer.
335
324
  >
336
- > A location that fills NO optional segment reaches URLPattern never. `/settings` is the
337
- > bare form of `/settings/:section?`, so the params are `{}`, and the adapters read that
338
- > from the path alone rather than from their framework. The route map that HOLDS that
339
- > pattern still needed URLPattern when it was built.
325
+ > A location that fills NO optional part runs no match at all. `/settings` is the bare
326
+ > form of `/settings/:section?`, so the params are `{}`, and the adapters read that from
327
+ > the path alone rather than from their framework.
340
328
 
341
329
  ### How to write a `RouterBridgeBase` adapter
342
330
 
343
331
  Extend `RouterBridgeBase`, then implement the three abstract methods for your framework:
344
332
 
345
333
  ```typescript
346
- import { RouterBridgeBase, createRouteMap } from "@xmachines/play-router";
334
+ import { RouterBridgeBase } from "@xmachines/play-router";
335
+ import { createRouteMap } from "@xmachines/play-router/xstate";
347
336
  import type { RoutableActor } from "@xmachines/play-router";
348
337
 
349
338
  // Shape of your framework's router — adjust to its real API
@@ -401,13 +390,14 @@ bridge.disconnect();
401
390
 
402
391
  ### Route Extraction
403
392
 
404
- | Export | Description |
405
- | ---------------------------------------- | --------------------------------------------------------------------------------- |
406
- | `extractMachineRoutes(machine)` | Converts an XState machine into a `RouteTree` with the state ID ↔ path maps |
407
- | `createRouteMap(machine, options?)` | Builds a `RouteMap` directly from a machine. An adapter uses this form |
408
- | `createRouteMapFromTree(tree, options?)` | Builds a `RouteMap` from a `RouteTree` that you extracted before |
409
- | `buildRouteTree(routes)` | Builds a `RouteTree` from an array of `RouteInfo` objects |
410
- | `machineToGraph(machine)` | Converts a machine into a typed `@statelyai/graph` `Graph`, for a graph algorithm |
393
+ | Export | Description |
394
+ | ---------------------------------------- | ----------------------------------------------------------------------- |
395
+ | `buildRouteTree(routes)` | Builds a `RouteTree` from an array of `RouteInfo` objects |
396
+ | `createRouteMapFromTree(tree, options?)` | Builds a `RouteMap` from a `RouteTree` that you extracted before |
397
+ | `ROOT_NODE_ID` | The ID of the synthetic root node. A walk of a tree must skip that node |
398
+
399
+ The MAIN entry point reads no state machine. The machine half is
400
+ `@xmachines/play-router/xstate`, below.
411
401
 
412
402
  ### Route Matching
413
403
 
@@ -417,16 +407,32 @@ bridge.disconnect();
417
407
  | `findRouteById(tree, id)` | Finds a `RouteNode` by its state ID |
418
408
  | `findRouteByPath(tree, path)` | Finds a `RouteNode` by its URL path. It also matches a dynamic pattern |
419
409
 
420
- ### Query Utilities
410
+ ### The machine half — subpath `@xmachines/play-router/xstate`
411
+
412
+ A route of XMachines is a fact about a STATE. This entry point reads that fact out of an
413
+ XState v5 machine. The main entry point reads it never, so `xstate` and `@statelyai/graph`
414
+ are OPTIONAL peer dependencies of this package: a consumer that imports this entry point
415
+ nowhere installs neither one.
416
+
417
+ ```typescript
418
+ import { createRouteMap, extractMachineRoutes } from "@xmachines/play-router/xstate";
419
+ ```
421
420
 
422
421
  | Export | Description |
423
422
  | ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
423
+ | `extractMachineRoutes(machine)` | Converts an XState machine into a `RouteTree` with the state ID ↔ path maps |
424
+ | `createRouteMap(machine, options?)` | Builds a `RouteMap` directly from a machine. An adapter uses this form |
425
+ | `machineToGraph(machine)` | Converts a machine into a typed `@statelyai/graph` `Graph`, for a graph algorithm |
424
426
  | `getRoutableRoutes(tree)` | Returns every routable `RouteNode` in one flat array |
425
427
  | `getNavigableRoutes(tree, stateId)` | Returns the child routes that a state can reach, through the hierarchy and through a transition |
426
428
  | `routeExists(tree, path)` | Tells you if the tree holds a path |
427
429
  | `getRouteMappings(tree, options?)` | The `{ stateId, path }` entries for a route table of a host, with an optional prefix |
428
430
  | `getTransitionReachableRoutes(graph, stateId)` | Returns the route paths that a state can reach through an XState transition |
429
431
  | `isRouteReachable(graph, fromStateId, toStateId)` | Tells you if a transition path is present between two states |
432
+ | `MachineGraph` | The typed `@statelyai/graph` Graph, with `MachineNodeData` and `MachineEdgeData` |
433
+
434
+ No framework router adapter re-exports `createRouteMap`. Each one names no state machine,
435
+ so a consumer of an adapter imports the function from `@xmachines/play-router/xstate`.
430
436
 
431
437
  ### Router Bridge
432
438
 
@@ -461,8 +467,8 @@ it safe is the same in both, so it lives here.
461
467
  | Export | Description |
462
468
  | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
463
469
  | `resolveFrameworkParams(source)` | Decides which params describe the route of the machine: `{}`, the parse of the framework, or the fallback |
464
- | `getPatternParamNames(pattern)` | The names of every `:param` of a route pattern. A `*` wildcard carries none |
465
- | `getRequiredPatternParamNames(pattern)` | The names the pattern REQUIRES, so an optional `:name?` is left out |
470
+ | `parsePattern(pattern).names` | The names of every `:param` of a route pattern. An anonymous param carries none |
471
+ | `parsePattern(pattern).requiredNames` | The names the pattern REQUIRES, so an optional `:name?` is left out |
466
472
  | `pickOwnParams(params, names, requiredNames?)` | The params that the pattern declares, or `null` when the framework covers them not. `requiredNames` says which names may NOT be absent; it defaults to every name, so a two-argument call treats an optional `:name?` as a gap |
467
473
  | `cleanFrameworkParams(params)` | The params of a framework with no absent value, each one a string |
468
474
 
@@ -498,43 +504,40 @@ props that a framework decides, which is why it is a type parameter.
498
504
 
499
505
  ### Key Types
500
506
 
501
- | Export | Description |
502
- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
503
- | `RouterBridge` | The interface of the `connect()` and `disconnect()` lifecycle |
504
- | `MountableRouterBridge` | A `RouterBridge` whose mount can move — it adds `basePath` and `setBasePath()` |
505
- | `RouteTree` | The hierarchical tree, with `root`, `byStateId`, `byPath`, and an optional `graph` |
506
- | `RouteNode` | One node of the tree, with `id`, `path`, `fullPath`, `stateId`, `children`, and `parent` |
507
- | `RouteInfo` | The flat route descriptor that comes from a state node |
508
- | `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query? }` |
509
- | `RoutableActor` | The minimal actor interface that `RouterBridgeBase` requires: `currentRoute`, `initialRoute`, and `send(PlayRouteEvent)` |
510
- | `PlayActor` | The complete actor interface that `PlayRouterProvider` uses. It extends `RoutableActor` with `currentView` (`Routable + Viewable`) |
511
- | `RouteMapping` | The `{ stateId, path }` pair that builds a `RouteMap` |
512
- | `RouteMapping as BaseRouteMapping` | The alias of `RouteMapping`, for compatibility with an earlier version |
513
- | `MachineGraph` | The typed `@statelyai/graph` Graph, with `MachineNodeData` and `MachineEdgeData` |
514
- | `WindowLike` | The minimal `window` interface that you can inject for SSR and for a test |
515
- | `LocationLike` | The minimal `location` interface that you can inject for SSR and for a test |
507
+ | Export | Description |
508
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
509
+ | `RouterBridge` | The interface of the `connect()` and `disconnect()` lifecycle |
510
+ | `MountableRouterBridge` | A `RouterBridge` whose mount can move — it adds `basePath` and `setBasePath()` |
511
+ | `RouteTree` | The hierarchical tree, with `root`, `byStateId`, `byPath`, and an optional `graph` |
512
+ | `RouteNode` | One node of the tree, with `id`, `path`, `fullPath`, `stateId`, `children`, and `parent` |
513
+ | `RouteInfo` | The flat route descriptor that comes from a state node |
514
+ | `PlayRouteEvent` | Routing event `{ type: "play.route", to, params?, query?, data? }` |
515
+ | `Routable` | The optional routing capability of an actor: `currentRoute` and `initialRoute` |
516
+ | `RoutableActor` | The actor interface that `RouterBridgeBase` and `PlayRouterProvider` require: `currentRoute`, `initialRoute`, and `send(PlayRouteEvent)` |
517
+ | `RouteMapping` | The `{ stateId, path }` pair that builds a `RouteMap` |
518
+ | `RouteObject` | The object form of `meta.route`: `path`, and the `handler`, `reenter` and `data` fields |
519
+ | `RouteMetadata` | The union of the two forms of `meta.route`: the path string, or a `RouteObject` |
520
+ | `RouteData` | The resolved extra data of a route, which reaches the machine as `context.data` |
521
+ | `RouteDataResolver` | The function form of `RouteObject.data`: `({ context, event }) => RouteData` |
522
+ | `WindowLike` | The minimal `window` interface that you can inject for SSR and for a test |
523
+ | `LocationLike` | The minimal `location` interface that you can inject for SSR and for a test |
516
524
 
517
525
  ### Errors (subpath `@xmachines/play-router/errors`)
518
526
 
519
- | Class | Code | When thrown |
520
- | ---------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
521
- | `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()` cannot send a `play.route` event |
522
- | `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one |
523
- | `URLPatternUnavailableError` | `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE` | The URLPattern API is absent, and no polyfill is loaded |
524
- | `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` | A route pattern does not compile, or two of its params land on one URLPattern group |
525
- | `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""` |
526
- | `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route names a state ID that the machine graph does not hold |
527
- | `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path |
528
- | `UnknownStateTypeError` | `PLAY_ROUTE_UNKNOWN_STATE_TYPE` | A state node has an XState `.type` value that the package does not know |
529
- | `InvalidBasePathError` | `PLAY_ROUTER_INVALID_BASE_PATH` | A `basePath` resolves to one concrete prefix never (`*`, `:p?`, `$p`, `?`, `#`, a scheme, `.`/`..`, whitespace) |
530
- | `MissingBasePathParamError` | `PLAY_ROUTER_MISSING_BASE_PATH_PARAM` | A `:param` of a `basePath` has no value in `basePathParams` |
527
+ | Class | Code | When thrown |
528
+ | --------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
529
+ | `RouterSyncError` | `PLAY_ROUTER_SYNC_FAILED` | `syncActorFromRouter()` cannot send a `play.route` event |
530
+ | `DuplicateBridgeError` | `PLAY_ROUTER_DUPLICATE_BRIDGE` | A second bridge tries to connect to an actor that already has one |
531
+ | `InvalidRoutePatternError` | `PLAY_ROUTE_MAP_INVALID_PATTERN` | A route pattern does not compile, or two of its params land on one URLPattern group |
532
+ | `EmptyRoutePathError` | `PLAY_ROUTE_EMPTY_PATH` | A state declares `meta.route: ""` |
533
+ | `InvalidStateIdError` | `PLAY_ROUTE_INVALID_STATE_ID` | A route names a state ID that the machine graph does not hold |
534
+ | `DuplicateRoutePathError` | `PLAY_ROUTE_DUPLICATE_PATH` | Two or more states share the same URL path |
535
+ | `UnknownStateTypeError` | `PLAY_ROUTE_UNKNOWN_STATE_TYPE` | A state node has an XState `.type` value that the package does not know |
536
+ | `InvalidBasePathError` | `PLAY_ROUTER_INVALID_BASE_PATH` | A `basePath` resolves to one concrete prefix never (`*`, `:p?`, `$p`, `?`, `#`, a scheme, `.`/`..`, whitespace) |
537
+ | `MissingBasePathParamError` | `PLAY_ROUTER_MISSING_BASE_PATH_PARAM` | A `:param` of a `basePath` has no value in `basePathParams` |
531
538
 
532
539
  ```typescript
533
- import {
534
- RouterSyncError,
535
- DuplicateBridgeError,
536
- URLPatternUnavailableError,
537
- } from "@xmachines/play-router/errors";
540
+ import { RouterSyncError, DuplicateBridgeError } from "@xmachines/play-router/errors";
538
541
 
539
542
  // bridge from the adapter example above
540
543
  try {
@@ -575,6 +578,28 @@ states: {
575
578
  }
576
579
  ```
577
580
 
581
+ ### The object form
582
+
583
+ The object form carries the path and any metadata of your own, such as a title or a
584
+ breadcrumb. It also carries three fields that the routing layer reads:
585
+
586
+ | Field | Default | What it does |
587
+ | --------- | -------- | ------------------------------------------------------------------------------- |
588
+ | `path` | required | The template of the route path |
589
+ | `handler` | `"root"` | Where the generated `play.route` transition sits: `"root"`, `"local"`, `"both"` |
590
+ | `reenter` | `false` | Whether the transition re-enters its own domain. See the note below. |
591
+ | `data` | none | The extra data of the route: a literal value, or a function of the event |
592
+
593
+ [`@xmachines/play-xstate`](../play-xstate/README.md) reads `handler`, `reenter`, and `data` when it generates the
594
+ transitions. See the README of that package for what each value does.
595
+
596
+ **The four names are RESERVED, and this is a change of behaviour.** An earlier release
597
+ read `path` alone, so a state could put any name of its own beside it. A state that
598
+ carries its own `handler` value now throws an `InvalidRouteHandlerError` at the format
599
+ time, and the machine builds never; a state that carries its own `data` value now writes
600
+ that value to `context.data`, where the `/context` projection of every view reads it.
601
+ Give such a field another name.
602
+
578
603
  ### Relative vs absolute paths
579
604
 
580
605
  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:
@@ -600,47 +625,17 @@ states: {
600
625
 
601
626
  Always use `node.fullPath` to match a browser URL and to build a route map. Never use `node.path` for this.
602
627
 
603
- ## Testing
604
-
605
- ```bash
606
- # Run tests for this package
607
- pnpm --filter @xmachines/play-router test
608
-
609
- # Watch mode
610
- pnpm --filter @xmachines/play-router run test:watch
611
- ```
612
-
613
- `@xmachines/play-router-shared` holds a contract test suite of the router bridge, for the
614
- author of an adapter. That suite drives a real actor. Therefore it is one layer above this
615
- package, and `@xmachines/play-router` keeps no dependency on an actor runtime.
616
- `@xmachines/play-router-shared` is a private workspace package. Thus only an adapter author
617
- in this repository can use the suite:
618
-
619
- ```typescript
620
- import { runBridgeContractTests } from "@xmachines/play-router-shared/test/router-bridge-contract.js";
621
-
622
- runBridgeContractTests({
623
- name: "MyRouterBridge",
624
- createHarness(initialPath) {
625
- // return ContractHarness with bridge, actor, simulateNavigation, getLastNavigatedPath
626
- },
627
- createRestoredHarness(routedPath) {
628
- // return ContractHarness whose actor is restored to routedPath
629
- // while the mock router starts at the machine's initial route
630
- },
631
- });
632
- ```
633
-
634
628
  ## Related Packages
635
629
 
636
630
  - **[@xmachines/play](../play/README.md)** — Core protocol types (`PlayEvent`, `PlayError`)
637
- - **[@xmachines/play-actor](../play-actor/README.md)** — the abstract actor base class (`AbstractActor`, `Routable`). Every `AbstractActor` subclass satisfies `RoutableActor` structurally
638
- - **[@xmachines/play-signals](../play-signals/README.md)** — the TC39 Signals polyfill that observes the actor route
631
+ - **[@xmachines/play-actor](../play-actor/README.md)** — the actor contract (`PlayActor`). An actor that adds `Routable` of this package satisfies `RoutableActor` structurally
632
+ - **[@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`
633
+ - **[@xmachines/play-atom](../play-atom/README.md)** — the atom primitives that observe the actor route
639
634
  - **[@xmachines/play-xstate](../play-xstate/README.md)** — the XState v5 logic adapter, which works with a route tree
640
635
  - **[@xmachines/play-tanstack-router](../play-tanstack-router/README.md)** — Shared TanStack Router bridge base (framework-agnostic)
641
636
  - **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router/README.md)** — TanStack Router adapter (React)
642
637
  - **[@xmachines/play-tanstack-solid-router](../play-tanstack-solid-router/README.md)** — TanStack Router adapter (SolidJS)
643
- - **[@xmachines/play-react-router](../play-react-router/README.md)** — React Router v7 adapter
638
+ - **[@xmachines/play-react-router](../play-react-router/README.md)** — React Router 7/8 adapter
644
639
  - **[@xmachines/play-vue-router](../play-vue-router/README.md)** — Vue Router adapter
645
640
  - **[@xmachines/play-solid-router](../play-solid-router/README.md)** — SolidJS Router adapter
646
641
 
@@ -10,6 +10,7 @@
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 type { RouteMapOptions } from "./types.js";
13
14
  /**
14
15
  * One entry of the map between a state ID and a path.
15
16
  *
@@ -42,14 +43,21 @@ export interface RouteMapping {
42
43
  * - A static path, without a `:param` → a `Map` lookup in O(1)
43
44
  * - A dynamic path → a scan of the bucket index in O(k), with `URLPattern`, where
44
45
  * `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
46
+ * - The class keeps each result of a first match in an LRU cache. The default cache
47
+ * holds between 500 and 1000 entries, and the `pathCache` constructor option
48
+ * replaces it
47
49
  *
48
- * **The syntax of a pattern** (`:param`, `:param?`, and `*`):
50
+ * **The syntax of a pattern:** the whole URLPattern pathname grammar. The forms used most
51
+ * often are:
49
52
  * - `:param` — a necessary segment. It matches exactly one segment without a `/`
50
53
  * - `:param?` — an optional segment. It matches zero segments or one segment without a `/`
51
54
  * - `*` — a wildcard. It matches each number of segments, as URLPattern defines
52
55
  *
56
+ * A `{…}` group, a `:param(\d+)` constraint, a `+` modifier and a `\:` escape are read
57
+ * too. The [routing guide](../../docs/guides/routing.md) states every form, the prefix
58
+ * rule, and the one divergence from the standard: a param name of this library can hold a
59
+ * hyphen.
60
+ *
53
61
  * **The forms of a stateId:** you can register a stateId, and you can look one up,
54
62
  * in the form `"#stateId"` or in the form `"stateId"`. `RouteMap` makes the
55
63
  * canonical form itself. `getStateIdByPath` returns the stateId exactly as you
@@ -82,6 +90,16 @@ export declare class RouteMap {
82
90
  private pathToStateId;
83
91
  private patternBuckets;
84
92
  private pathMatchCache;
93
+ /** The key space of this ROUTE TABLE inside {@link pathMatchCache}, which a caller may share. */
94
+ private readonly pathCacheKey;
95
+ /**
96
+ * The caches that every parse and every compilation of this map reads.
97
+ *
98
+ * Each default stays SHARED on purpose. A map that built one of its own would keep its
99
+ * patterns out of the cache that `find-route.ts` and `router-sync.ts` read, so the same
100
+ * pattern would parse two times. A caller that wants the isolation passes a cache.
101
+ */
102
+ private compileOptions;
85
103
  /**
86
104
  * Builds a route map from an array of the mappings between a state ID and a path.
87
105
  *
@@ -92,17 +110,10 @@ export declare class RouteMap {
92
110
  *
93
111
  * @param mappings - The array of the `{ stateId, path }` entries. The order gives the
94
112
  * 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.
113
+ * @param options - See {@link RouteMapOptions}. A map holds TWO caches, so no option is
114
+ * named `cache`: each one names the cache that it carries.
102
115
  */
103
- constructor(mappings: RouteMapping[], { cacheSize }?: {
104
- cacheSize?: number;
105
- });
116
+ constructor(mappings: RouteMapping[], options?: RouteMapOptions);
106
117
  /**
107
118
  * Resolves a URL path to its state ID.
108
119
  *
@@ -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;AAeH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAelD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,YAAY;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AA+DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,qBAAa,QAAQ;IACpB,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,cAAc,CAA8B;IACpD,iGAAiG;IACjG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA2B;IACxD;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,CAAiB;IAEvC;;;;;;;;;;;;OAYG;gBACS,QAAQ,EAAE,YAAY,EAAE,EAAE,OAAO,GAAE,eAAoB;IAqDnE;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA4B7C;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAGhD"}