@xmachines/play-tanstack-solid-router 1.0.0-beta.7 → 1.0.0-beta.9

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 CHANGED
@@ -6,7 +6,7 @@ Signals-native integration with TanStack Solid Router enabling logic-driven navi
6
6
 
7
7
  ## Overview
8
8
 
9
- `@xmachines/play-tanstack-solid-router` provides seamless integration between TanStack Solid Router and XMachines state machines. Built on Solid's reactive primitives (`createEffect`), it implements the RouterBridge protocol for bidirectional synchronization.
9
+ `@xmachines/play-tanstack-solid-router` provides seamless integration between TanStack Solid Router and XMachines state machines. Built on Solid's reactive primitives, it implements the `RouterBridgeBase` pattern for bidirectional synchronization while remaining framework-swappable.
10
10
 
11
11
  Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
12
12
 
@@ -17,26 +17,26 @@ Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md),
17
17
  **Key Benefits:**
18
18
 
19
19
  - **Signals-native:** Zero adaptation layer between Solid signals and TC39 Signals
20
- - **RouterBridge pattern:** ~220 lines, 2 flags (consistent with Vue/Solid Router adapters)
21
- - **Automatic tracking:** `createEffect` handles dependency tracking (no manual Watcher)
20
+ - **Bridge-first:** Extends shared `RouterBridgeBase` policy used by other adapters
21
+ - **Automatic tracking:** `createEffect` handles dependency tracking (no manual Watcher setup needed for the bridge)
22
22
  - **Logic-driven navigation:** Business logic in state machines, not components
23
23
  - **Type-safe parameters:** Route params flow through state machine context
24
24
 
25
25
  **Framework Compatibility:**
26
26
 
27
- - TanStack Solid Router 1.0.0+
27
+ - TanStack Solid Router 1.100.0+
28
28
  - SolidJS 1.8.0+
29
29
  - TC39 Signals polyfill integration
30
30
 
31
31
  ## Installation
32
32
 
33
33
  ```bash
34
- npm install @tanstack/solid-router@^1.0.0 solid-js@^1.8.0 @xmachines/play-tanstack-solid-router @xmachines/play-solid
34
+ npm install @tanstack/solid-router@^1.108.0 solid-js@^1.8.0 @xmachines/play-tanstack-solid-router @xmachines/play-solid
35
35
  ```
36
36
 
37
37
  **Peer dependencies:**
38
38
 
39
- - `@tanstack/solid-router` ^1.0.0 - TanStack Solid Router library
39
+ - `@tanstack/solid-router` ^1.108.0 - TanStack Solid Router library
40
40
  - `solid-js` ^1.8.0 - SolidJS runtime
41
41
  - `@xmachines/play-solid` - Solid renderer (`PlayRenderer`)
42
42
  - `@xmachines/play-actor` - Actor base
@@ -45,377 +45,212 @@ npm install @tanstack/solid-router@^1.0.0 solid-js@^1.8.0 @xmachines/play-tansta
45
45
 
46
46
  ## Quick Start
47
47
 
48
- ```typescript
49
- import { createRouter } from '@tanstack/solid-router';
50
- import { onCleanup } from 'solid-js';
51
- import { PlayRenderer } from '@xmachines/play-solid';
52
- import { definePlayer } from '@xmachines/play-xstate';
53
- import { extractMachineRoutes } from '@xmachines/play-router';
54
- import { SolidRouterBridge, createRouteMapFromTree } from '@xmachines/play-tanstack-solid-router';
48
+ ```tsx
49
+ import { Router, createRouter } from "@tanstack/solid-router";
50
+ import { PlayTanStackRouterProvider, createRouteMap } from "@xmachines/play-tanstack-solid-router";
51
+ import { PlayRenderer } from "@xmachines/play-solid";
52
+ import { definePlayer } from "@xmachines/play-xstate";
53
+ import { routeTree as routerRouteTree } from "./routeTree.gen"; // from TanStack
55
54
 
56
55
  function App() {
57
- // 1. Create actor
58
- const actor = definePlayer({ machine, catalog })();
59
-
60
- // 2. Create router
61
- const router = createRouter({
62
- routes: /* ... TanStack route config ... */
63
- });
64
-
65
- // 3. Extract routes from machine and create RouteMap
66
- const routeTree = extractMachineRoutes(machine);
67
- const routeMap = createRouteMapFromTree(routeTree);
68
-
69
- // 4. Create and connect bridge (inside component with Solid ownership context)
70
- const bridge = new SolidRouterBridge(router, actor, routeMap);
71
- bridge.connect();
72
-
73
- onCleanup(() => bridge.disconnect());
74
-
75
- return (
76
- <Router>
77
- <PlayRenderer actor={actor} components={components} />
78
- </Router>
79
- );
56
+ // 1. Create player with state machine
57
+ const createPlayer = definePlayer({
58
+ machine: authMachine,
59
+ catalog: componentCatalog,
60
+ });
61
+ const actor = createPlayer();
62
+ actor.start();
63
+
64
+ // 2. Create TanStack router instance
65
+ const router = createRouter({ routeTree: routerRouteTree });
66
+
67
+ // 3. Create route mapping from machine routes
68
+ const routeMap = createRouteMap(authMachine);
69
+
70
+ return (
71
+ // 4. Wrap with provider to sync actor and router
72
+ <PlayTanStackRouterProvider
73
+ actor={actor}
74
+ router={router}
75
+ routeMap={routeMap}
76
+ renderer={(currentActor, currentRouter) => (
77
+ <Router router={currentRouter}>
78
+ <PlayRenderer actor={currentActor} components={components} />
79
+ </Router>
80
+ )}
81
+ />
82
+ );
80
83
  }
81
84
  ```
82
85
 
83
- **Important:** The bridge must be created inside a component where Solid hooks (`createEffect`, `onCleanup`) are available. TanStack Solid Router requires Solid's ownership context for reactivity to work correctly.
84
-
85
86
  ## API Reference
86
87
 
87
88
  ### `SolidRouterBridge`
88
89
 
89
90
  Router adapter implementing the `RouterBridge` protocol for TanStack Solid Router.
90
91
 
91
- **Constructor:**
92
+ **Type Signature:**
92
93
 
93
94
  ```typescript
94
- new SolidRouterBridge(
95
- router: Router,
96
- actor: AbstractActor<any>,
97
- routeMap: RouteMap
98
- )
95
+ class SolidRouterBridge {
96
+ constructor(router: Router, actor: AbstractActor<any>, routeMap: RouteMap);
97
+ dispose(): void;
98
+ }
99
99
  ```
100
100
 
101
- **Parameters:**
101
+ **Constructor Parameters:**
102
102
 
103
- - `router`: TanStack Solid Router instance (from `createRouter()`)
104
- - `actor`: XMachines player actor implementing signal protocol
105
- - `routeMap`: Bidirectional state ID ↔ path mapping
103
+ - `router` - TanStack Solid Router instance
104
+ - `actor` - XMachines actor instance
105
+ - `routeMap` - Bidirectional state ID ↔ path mapping
106
106
 
107
107
  **Methods:**
108
108
 
109
- - `connect(): void` - Start bidirectional synchronization (actor ↔ router)
110
- - `disconnect(): void` - Stop synchronization and cleanup subscriptions
109
+ - `connect()` - Start bidirectional synchronization.
110
+ - `disconnect()` - Stop synchronization and cleanup bridge resources.
111
+ - `dispose()` - Alias of `disconnect()`.
111
112
 
112
- **Important:** Must be created inside component where Solid hooks (`createEffect`, `onCleanup`) are available. Creating outside ownership context will cause reactivity to fail.
113
+ **Internal Behavior:**
113
114
 
114
- ### `RouteMap`
115
+ - Uses `RouterBridgeBase` TC39 watcher lifecycle for actor→router synchronization
116
+ - Updates TanStack Router via `router.navigate({ to: path })` when actor state changes
117
+ - Uses `router.subscribe` to watch history navigation events
118
+ - Sends `play.route` events to actor when user navigates
115
119
 
116
- Bidirectional mapping between XMachines state IDs and URL paths with pattern matching support.
120
+ ### `PlayTanStackRouterProvider`
117
121
 
118
- **Constructor:**
122
+ A Solid component that automatically sets up, connects, and tears down the `SolidRouterBridge` using Solid's lifecycle.
119
123
 
120
- ```typescript
121
- new RouteMap(routes: RouteMapping[])
124
+ ```tsx
125
+ interface PlayTanStackRouterProviderProps {
126
+ actor: AbstractActor<any>;
127
+ router: Router;
128
+ routeMap: RouteMap;
129
+ renderer: (actor: AbstractActor<any>, router: Router) => JSX.Element;
130
+ }
122
131
  ```
123
132
 
124
- **Interface:**
133
+ **Props:**
125
134
 
126
- ```typescript
127
- interface RouteMapping {
128
- stateId: string; // XMachines state ID (e.g., '#home', '#profile')
129
- path: string; // URL path pattern (e.g., '/', '/profile/:userId')
130
- }
131
- ```
135
+ - `actor` - The XMachines player actor
136
+ - `router` - The TanStack Solid Router instance
137
+ - `routeMap` - Mapping between paths and state IDs
138
+ - `renderer` - A render prop function that receives the active actor and router
132
139
 
133
- **Methods:**
140
+ **Behavior:**
134
141
 
135
- - `getStateIdByPath(path: string): string | null` - Lookup state ID by path (with URLPattern matching)
136
- - `getPathByStateId(stateId: string): string | null` - Lookup path by state ID
142
+ 1. Instantiates `SolidRouterBridge` on mount
143
+ 2. Calls `bridge.connect()`
144
+ 3. Renders the content returned by the `renderer` function
145
+ 4. Calls `bridge.disconnect()` when the component unmounts via `onCleanup`
137
146
 
138
- ### `createRouteMapFromTree()`
147
+ ### `createRouteMap()`
139
148
 
140
- Helper to create RouteMap from RouteTree extracted from state machine.
149
+ Helper to build a `RouteMap` instance directly from an XState machine.
141
150
 
142
151
  **Signature:**
143
152
 
144
153
  ```typescript
145
- function createRouteMapFromTree(routeTree: RouteTree): RouteMap;
154
+ function createRouteMap(machine: AnyStateMachine): RouteMap;
146
155
  ```
147
156
 
148
157
  **Usage:**
149
158
 
150
159
  ```typescript
151
- import { extractMachineRoutes } from "@xmachines/play-router";
152
- import { createRouteMapFromTree } from "@xmachines/play-tanstack-solid-router";
160
+ import { createRouteMap } from "@xmachines/play-tanstack-solid-router";
153
161
 
154
- const routeTree = extractMachineRoutes(machine);
155
- const routeMap = createRouteMapFromTree(routeTree);
162
+ const routeMap = createRouteMap(machine);
156
163
  ```
157
164
 
158
- This helper traverses the route tree and creates RouteMapping entries for all routable states.
159
-
160
165
  ## Usage Patterns
161
166
 
162
- ### Creating RouteMap from Machine
163
-
164
- **Recommended:** Extract routes from state machine automatically:
165
-
166
- ```typescript
167
- import { extractMachineRoutes } from "@xmachines/play-router";
168
- import { createRouteMapFromTree } from "@xmachines/play-tanstack-solid-router";
169
-
170
- const routeTree = extractMachineRoutes(machine);
171
- const routeMap = createRouteMapFromTree(routeTree);
172
- ```
173
-
174
- **Manual:** Define routes explicitly:
175
-
176
- ```typescript
177
- const routeMap = new RouteMap([
178
- { stateId: "#home", path: "/" },
179
- { stateId: "#profile", path: "/profile/:userId" },
180
- ]);
181
- ```
182
-
183
167
  ### Dynamic Routes with Parameters
184
168
 
185
- **Path parameters:**
169
+ TanStack Router and URLPattern (used internally) support dynamic route matching syntax:
186
170
 
187
171
  ```typescript
188
- const routeMap = new RouteMap([
189
- { stateId: "#home", path: "/" },
190
- { stateId: "#settings", path: "/settings/:section?" }, // Optional param
191
- { stateId: "#profile", path: "/profile/:userId" }, // Required param
192
- ]);
193
- ```
194
-
195
- **Pattern matching examples:**
172
+ // Machine configuration
173
+ const machineConfig = {
174
+ states: {
175
+ profile: {
176
+ meta: {
177
+ route: "/profile/:userId",
178
+ view: { component: "Profile" },
179
+ },
180
+ },
181
+ },
182
+ };
196
183
 
197
- ```typescript
184
+ // Route mapping will natively support URLPattern parameters
198
185
  routeMap.getStateIdByPath("/profile/123"); // → '#profile'
199
- routeMap.getStateIdByPath("/settings"); // → '#settings'
200
- routeMap.getStateIdByPath("/settings/privacy"); // → '#settings'
201
186
  ```
202
187
 
203
- ### Cleanup on Component Unmount
188
+ ### Protected Routes and Guards
204
189
 
205
- **Always cleanup when component unmounts:**
190
+ With XMachines, auth guards are handled entirely inside the state machine, preventing flashes of unauthorized content.
206
191
 
207
192
  ```typescript
208
- function MyApp() {
209
- const bridge = new SolidRouterBridge(router, actor, routeMap);
210
- bridge.connect();
211
-
212
- onCleanup(() => {
213
- bridge.disconnect(); // Stop synchronization
214
- actor.stop(); // Stop actor if needed
215
- });
216
-
217
- return <Router>...</Router>;
193
+ // Machine side
194
+ dashboard: {
195
+ meta: { route: "/dashboard", view: { component: "Dashboard" } },
196
+ always: {
197
+ guard: ({ context }) => !context.isAuthenticated,
198
+ target: "login"
199
+ }
218
200
  }
219
201
  ```
220
202
 
221
- ## Circular Update Prevention
203
+ When a user navigates to `/dashboard`:
222
204
 
223
- SolidRouterBridge uses a **2-flag pattern** to prevent infinite loops:
224
-
225
- 1. **lastSyncedPath** - Skips redundant updates when path unchanged
226
- 2. **isProcessingNavigation** - Prevents actor router → actor loops
227
-
228
- **How it works:**
229
-
230
- ```typescript
231
- // Actor state change → Router navigation
232
- private syncRouterFromActor(route: string | null): void {
233
- if (this.isProcessingNavigation) return; // Skip if processing router event
234
- if (route === this.lastSyncedPath) return; // Skip if path unchanged
235
-
236
- this.lastSyncedPath = route || '/';
237
- this.router.navigate({ to: this.lastSyncedPath });
238
- }
205
+ 1. TanStack Router updates location
206
+ 2. Bridge intercepts and sends `play.route`
207
+ 3. Actor evaluates guard -> denies target, transitions to `login` instead
208
+ 4. Bridge observes new actor state (`/login`)
209
+ 5. Bridge tells TanStack Router to redirect to `/login`
239
210
 
240
- // Router navigation → Actor event
241
- private syncActorFromRouter(location: RouterLocation): void {
242
- this.isProcessingNavigation = true; // Set flag
243
-
244
- // Send event to actor...
211
+ ## Circular Update Prevention
245
212
 
246
- queueMicrotask(() => {
247
- this.isProcessingNavigation = false; // Clear flag in microtask
248
- });
249
- }
250
- ```
213
+ The `RouterBridgeBase` architecture prevents infinite loops between router and actor using two mechanisms:
251
214
 
252
- **Microtask timing:** The `isProcessingNavigation` flag is cleared in a microtask (via `queueMicrotask()`) to ensure the actor has processed the event before allowing router-initiated syncs again.
215
+ 1. **`lastSyncedPath` tracking:** Stores the last synchronized path to prevent redundant navigations back to the identical location.
216
+ 2. **`isProcessingNavigation` flag:** Set during a router event, preventing the router from immediately reacting to the actor's synchronous state update.
253
217
 
254
218
  ## Comparison with @solidjs/router Adapter
255
219
 
256
- | Aspect | @solidjs/router | @tanstack/solid-router |
257
- | -------------- | ----------------- | ---------------------------- |
258
- | **Router API** | router.push() | router.navigate({ to }) |
259
- | **Location** | router.location | router.state.location |
260
- | **Guards** | router.beforeEach | No guards (use createEffect) |
261
- | **Reactivity** | createEffect | createEffect |
262
-
263
- **Key Differences:**
264
-
265
- - **TanStack Solid Router:** Uses `router.navigate({ to })` API (object-based navigation)
266
- - **SolidJS Router:** Uses `navigate(path)` function (string-based navigation)
267
- - **TanStack:** Router instance contains state, no separate hooks needed
268
- - **SolidJS:** Requires hooks (`useNavigate`, `useLocation`) for navigation primitives
269
-
270
- ## Limitations
271
-
272
- ### Solid Ownership Context Required
273
-
274
- The bridge must be created inside a component where Solid hooks are available:
275
-
276
- ```typescript
277
- // ✅ CORRECT: Inside component
278
- function App() {
279
- const bridge = new SolidRouterBridge(router, actor, routeMap);
280
- bridge.connect();
281
- onCleanup(() => bridge.disconnect());
282
- }
283
-
284
- // ❌ WRONG: Outside component (no ownership context)
285
- const bridge = new SolidRouterBridge(router, actor, routeMap);
286
- bridge.connect(); // Will fail - no owner for createEffect
287
- ```
288
-
289
- **Error message:**
290
-
291
- ```
292
- Error: createEffect can only be used within a component
293
- ```
294
-
295
- ### Router Creation
296
-
297
- Router must be created with TanStack's `createRouter()`:
298
-
299
- ```typescript
300
- import { createRouter } from "@tanstack/solid-router";
301
-
302
- const router = createRouter({
303
- routes: [
304
- /* route config */
305
- ],
306
- });
307
- ```
308
-
309
- ### Pattern Matching Requirements
310
-
311
- Route patterns with parameters require URLPattern support:
312
-
313
- - **Modern browsers:** Safari 17.4+, Chrome 95+, Firefox 106+
314
- - **Node.js:** Requires `urlpattern-polyfill` for tests
315
-
316
- **Polyfill setup:**
317
-
318
- ```typescript
319
- // Top of app entry point
320
- if (!globalThis.URLPattern) {
321
- await import("urlpattern-polyfill");
322
- }
323
- ```
220
+ | Aspect | @solidjs/router Adapter | @tanstack/solid-router Adapter |
221
+ | ----------------- | -------------------------------- | ------------------------------- |
222
+ | **Router API** | `navigate(path)` | `router.navigate({ to })` |
223
+ | **Setup Context** | Must be _inside_ Router context | Can wrap Router instance |
224
+ | **State Source** | Uses Solid hooks (`useLocation`) | Subscribes to `router` directly |
225
+ | **Reacting** | `createEffect` on location | `router.subscribe` callback |
324
226
 
325
227
  ## Architecture
326
228
 
327
- SolidRouterBridge follows XMachines Play architectural invariants:
229
+ This package implements standard Play invariants:
328
230
 
329
231
  ### INV-01: Actor Authority
330
232
 
331
- **Principle:** State machine has final authority over all transitions.
332
-
333
- **Implementation:** Router navigation triggers actor events, but actor guards decide validity:
334
-
335
- ```typescript
336
- // Router navigation → Actor event
337
- syncActorFromRouter(location: RouterLocation): void {
338
- const stateId = this.routeMap.getStateIdByPath(location.pathname);
339
- this.actor.send({
340
- type: 'play.route',
341
- to: stateId,
342
- params: extractParams(location)
343
- });
344
- // Actor guards validate - if rejected, URL reverts to actor's current route
345
- }
346
- ```
233
+ State machine has final authority over all transitions. TanStack Router navigation triggers actor events (`play.route`), but the actor's guards and transitions ultimately dictate if the view changes.
347
234
 
348
235
  ### INV-02: Passive Infrastructure
349
236
 
350
- **Principle:** Infrastructure reflects actor state, never decides navigation.
351
-
352
- **Implementation:** Router observes `actor.currentRoute` signal and updates URL:
353
-
354
- ```typescript
355
- // Actor state change → Router navigation
356
- const actorEffect = createEffect(
357
- on(
358
- () => this.actor.currentRoute.get(),
359
- (route) => this.syncRouterFromActor(route),
360
- ),
361
- );
362
- ```
363
-
364
- ### INV-05: Signal-Only Reactivity
365
-
366
- **Principle:** All reactivity through signals, never callbacks or promises.
367
-
368
- **Implementation:** Solid's `createEffect` watches actor signals automatically:
369
-
370
- ```typescript
371
- // Automatic dependency tracking
372
- createEffect(
373
- on(
374
- () => this.actor.currentRoute.get(), // Signal read
375
- (route) => {
376
- // Effect runs when signal changes
377
- },
378
- ),
379
- );
380
- ```
381
-
382
- ## Browser Support
383
-
384
- This package uses the [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) for route pattern matching.
385
-
386
- **Native Support:**
387
-
388
- - Safari 17.4+ (Sept 2024)
389
- - Chrome 95+ (Oct 2021)
390
- - Firefox 106+ (Oct 2022)
391
- - **Baseline 2025** status (newly available)
392
-
393
- **Polyfill Required:**
394
-
395
- - Node.js (all versions) - required for tests
396
- - Safari < 17.4
397
- - Older browsers
237
+ Infrastructure reflects actor state. The router observes `actor.currentRoute` and updates the browser URL, never storing independent business state.
398
238
 
399
- **Polyfill Installation:**
239
+ ### Cleanup Contract
400
240
 
401
- ```bash
402
- npm install urlpattern-polyfill
403
- ```
241
+ The bridge implements an explicit `dispose()`/`disconnect()` method. `PlayTanStackRouterProvider` wires this automatically to Solid's `onCleanup` hook to prevent memory leaks and duplicate bridge subscriptions in hot-reloading scenarios.
404
242
 
405
- **Usage:**
243
+ ## URLPattern Support
406
244
 
407
- ```typescript
408
- // Top of your app entry point (e.g., main.tsx)
409
- if (!globalThis.URLPattern) {
410
- await import("urlpattern-polyfill");
411
- }
412
- ```
245
+ This package uses the [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) for robust route pattern matching via `@xmachines/play-router`.
413
246
 
414
- **Bundle Size:** ~4KB gzipped (zero impact if URLPattern is native)
247
+ URLPattern is available natively on Node.js 24+ and modern browsers (Chrome 95+, Firefox 117+, Safari 16.4+). On older environments, load a polyfill **before** importing this package — see [`@xmachines/play-router` installation](../play-router/README.md#installation) for details.
415
248
 
416
- ## Examples
249
+ ## Related Packages
417
250
 
418
- See `examples/demo/` for the maintained integration example and tests.
251
+ - **[@xmachines/play-solid](../play-solid)** - SolidJS renderer
252
+ - **[@xmachines/play-router](../play-router)** - Core router primitives
253
+ - **[@xmachines/play-solid-router](../play-solid-router)** - Native SolidJS Router equivalent
419
254
 
420
255
  ## License
421
256
 
@@ -1,75 +1,27 @@
1
1
  /**
2
- * RouteMap for bidirectional state ID path mapping
2
+ * Bidirectional route mapper for TanStack Solid Router.
3
3
  *
4
- * Provides efficient lookup between XMachines state IDs and TanStack Router paths.
5
- * Supports pattern matching for dynamic routes with parameters.
4
+ * Extends {@link BaseRouteMap} from `@xmachines/play-router` all matching logic
5
+ * lives there. This class exists to provide a TanStack Solid Router-specific type
6
+ * name and to allow future adapter-specific extensions without breaking the shared base.
7
+ *
8
+ * **Inherited API:**
9
+ * - `getStateIdByPath(path): string | null` — path → state ID
10
+ * - `getPathByStateId(stateId): string | null` — state ID → path pattern
11
+ *
12
+ * @extends BaseRouteMap
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { createRouteMap } from "@xmachines/play-tanstack-solid-router";
17
+ *
18
+ * const routeMap = createRouteMap(machine);
19
+ *
20
+ * routeMap.getStateIdByPath("/profile/123"); // "#profile"
21
+ * routeMap.getPathByStateId("#settings"); // "/settings/:section?"
22
+ * ```
6
23
  */
7
- import type { RouteMapping } from "./types.js";
8
- export declare class RouteMap {
9
- private stateToPath;
10
- private pathToState;
11
- /**
12
- * Create a RouteMap with bidirectional mappings
13
- *
14
- * @param mappings - Array of state ID to path mappings
15
- *
16
- * @example
17
- * ```typescript
18
- * const routeMap = new RouteMap([
19
- * { stateId: '#home', path: '/' },
20
- * { stateId: '#profile', path: '/profile/:userId' },
21
- * { stateId: '#settings', path: '/settings/:section?' }
22
- * ]);
23
- * ```
24
- */
25
- constructor(mappings: RouteMapping[]);
26
- /**
27
- * Get path pattern for a state ID
28
- *
29
- * @param stateId - XMachines state ID (e.g., '#profile')
30
- * @returns Path pattern or null if not found
31
- *
32
- * @example
33
- * ```typescript
34
- * routeMap.getPathByStateId('#profile'); // '/profile/:userId'
35
- * ```
36
- */
37
- getPathByStateId(stateId: string): string | null;
38
- /**
39
- * Get state ID for a path, with pattern matching support
40
- *
41
- * Performs exact match first, then fuzzy pattern matching for dynamic routes.
42
- * Supports both required (:param) and optional (:param?) parameters.
43
- *
44
- * @param path - Actual URL path (e.g., '/profile/123')
45
- * @returns State ID or null if no match found
46
- *
47
- * @example
48
- * ```typescript
49
- * routeMap.getStateIdByPath('/profile/123'); // '#profile'
50
- * routeMap.getStateIdByPath('/settings'); // '#settings'
51
- * routeMap.getStateIdByPath('/settings/account'); // '#settings'
52
- * ```
53
- */
54
- getStateIdByPath(path: string): string | null;
55
- /**
56
- * Check if a path matches a pattern
57
- *
58
- * Supports:
59
- * - Required parameters: :param
60
- * - Optional parameters: :param?
61
- *
62
- * @param path - Actual URL path
63
- * @param pattern - Route pattern with :param syntax
64
- * @returns true if path matches pattern
65
- *
66
- * @example
67
- * ```typescript
68
- * matchesPattern('/profile/123', '/profile/:userId'); // true
69
- * matchesPattern('/settings', '/settings/:section?'); // true
70
- * matchesPattern('/settings/account', '/settings/:section?'); // true
71
- * ```
72
- */
73
- private matchesPattern;
24
+ import { BaseRouteMap } from "@xmachines/play-router";
25
+ export declare class RouteMap extends BaseRouteMap {
74
26
  }
75
27
  //# sourceMappingURL=route-map.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-map.d.ts","sourceRoot":"","sources":["../src/route-map.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,qBAAa,QAAQ;IACpB,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;;;;;;;;;;OAaG;gBACS,QAAQ,EAAE,YAAY,EAAE;IAOpC;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIhD;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAmB7C;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,cAAc;CAetB"}
1
+ {"version":3,"file":"route-map.d.ts","sourceRoot":"","sources":["../src/route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,qBAAa,QAAS,SAAQ,YAAY;CAAG"}
package/dist/route-map.js CHANGED
@@ -1,107 +1,27 @@
1
1
  /**
2
- * RouteMap for bidirectional state ID path mapping
2
+ * Bidirectional route mapper for TanStack Solid Router.
3
3
  *
4
- * Provides efficient lookup between XMachines state IDs and TanStack Router paths.
5
- * Supports pattern matching for dynamic routes with parameters.
4
+ * Extends {@link BaseRouteMap} from `@xmachines/play-router` all matching logic
5
+ * lives there. This class exists to provide a TanStack Solid Router-specific type
6
+ * name and to allow future adapter-specific extensions without breaking the shared base.
7
+ *
8
+ * **Inherited API:**
9
+ * - `getStateIdByPath(path): string | null` — path → state ID
10
+ * - `getPathByStateId(stateId): string | null` — state ID → path pattern
11
+ *
12
+ * @extends BaseRouteMap
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { createRouteMap } from "@xmachines/play-tanstack-solid-router";
17
+ *
18
+ * const routeMap = createRouteMap(machine);
19
+ *
20
+ * routeMap.getStateIdByPath("/profile/123"); // "#profile"
21
+ * routeMap.getPathByStateId("#settings"); // "/settings/:section?"
22
+ * ```
6
23
  */
7
- export class RouteMap {
8
- stateToPath = new Map();
9
- pathToState = new Map();
10
- /**
11
- * Create a RouteMap with bidirectional mappings
12
- *
13
- * @param mappings - Array of state ID to path mappings
14
- *
15
- * @example
16
- * ```typescript
17
- * const routeMap = new RouteMap([
18
- * { stateId: '#home', path: '/' },
19
- * { stateId: '#profile', path: '/profile/:userId' },
20
- * { stateId: '#settings', path: '/settings/:section?' }
21
- * ]);
22
- * ```
23
- */
24
- constructor(mappings) {
25
- mappings.forEach(({ stateId, path }) => {
26
- this.stateToPath.set(stateId, path);
27
- this.pathToState.set(path, stateId);
28
- });
29
- }
30
- /**
31
- * Get path pattern for a state ID
32
- *
33
- * @param stateId - XMachines state ID (e.g., '#profile')
34
- * @returns Path pattern or null if not found
35
- *
36
- * @example
37
- * ```typescript
38
- * routeMap.getPathByStateId('#profile'); // '/profile/:userId'
39
- * ```
40
- */
41
- getPathByStateId(stateId) {
42
- return this.stateToPath.get(stateId) ?? null;
43
- }
44
- /**
45
- * Get state ID for a path, with pattern matching support
46
- *
47
- * Performs exact match first, then fuzzy pattern matching for dynamic routes.
48
- * Supports both required (:param) and optional (:param?) parameters.
49
- *
50
- * @param path - Actual URL path (e.g., '/profile/123')
51
- * @returns State ID or null if no match found
52
- *
53
- * @example
54
- * ```typescript
55
- * routeMap.getStateIdByPath('/profile/123'); // '#profile'
56
- * routeMap.getStateIdByPath('/settings'); // '#settings'
57
- * routeMap.getStateIdByPath('/settings/account'); // '#settings'
58
- * ```
59
- */
60
- getStateIdByPath(path) {
61
- // Strip query string and hash fragment for matching
62
- const cleanPath = path.split("?")[0].split("#")[0];
63
- // Direct lookup first (exact match)
64
- if (this.pathToState.has(cleanPath)) {
65
- return this.pathToState.get(cleanPath);
66
- }
67
- // Fuzzy match for dynamic routes
68
- for (const [pattern, stateId] of this.pathToState) {
69
- if (this.matchesPattern(cleanPath, pattern)) {
70
- return stateId;
71
- }
72
- }
73
- return null;
74
- }
75
- /**
76
- * Check if a path matches a pattern
77
- *
78
- * Supports:
79
- * - Required parameters: :param
80
- * - Optional parameters: :param?
81
- *
82
- * @param path - Actual URL path
83
- * @param pattern - Route pattern with :param syntax
84
- * @returns true if path matches pattern
85
- *
86
- * @example
87
- * ```typescript
88
- * matchesPattern('/profile/123', '/profile/:userId'); // true
89
- * matchesPattern('/settings', '/settings/:section?'); // true
90
- * matchesPattern('/settings/account', '/settings/:section?'); // true
91
- * ```
92
- */
93
- matchesPattern(path, pattern) {
94
- // Convert route pattern to regex
95
- // Process replacements in correct order: optional params first, then required
96
- let regexPattern = pattern
97
- // Replace /:param? with optional segment (matches both /value and nothing)
98
- .replace(/\/:(\w+)\?/g, "(?:/([^/]+))?")
99
- // Replace /:param with required segment
100
- .replace(/\/:(\w+)/g, "/([^/]+)");
101
- // Add anchors for exact match
102
- regexPattern = "^" + regexPattern + "$";
103
- const regex = new RegExp(regexPattern);
104
- return regex.test(path);
105
- }
24
+ import { BaseRouteMap } from "@xmachines/play-router";
25
+ export class RouteMap extends BaseRouteMap {
106
26
  }
107
27
  //# sourceMappingURL=route-map.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-map.js","sourceRoot":"","sources":["../src/route-map.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,OAAO,QAAQ;IACZ,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEhD;;;;;;;;;;;;;OAaG;IACH,YAAY,QAAwB;QACnC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YACtC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,OAAe;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IAC9C,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAY;QAC5B,oDAAoD;QACpD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnD,oCAAoC;QACpC,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC;QACzC,CAAC;QAED,iCAAiC;QACjC,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnD,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC7C,OAAO,OAAO,CAAC;YAChB,CAAC;QACF,CAAC;QAED,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACK,cAAc,CAAC,IAAY,EAAE,OAAe;QACnD,iCAAiC;QACjC,8EAA8E;QAC9E,IAAI,YAAY,GAAG,OAAO;YACzB,2EAA2E;aAC1E,OAAO,CAAC,aAAa,EAAE,eAAe,CAAC;YACxC,wCAAwC;aACvC,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAEnC,8BAA8B;QAC9B,YAAY,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,CAAC;QAExC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;CACD"}
1
+ {"version":3,"file":"route-map.js","sourceRoot":"","sources":["../src/route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,MAAM,OAAO,QAAS,SAAQ,YAAY;CAAG"}
package/dist/types.d.ts CHANGED
@@ -3,8 +3,21 @@
3
3
  */
4
4
  export type { RouterBridge, PlayRouteEvent } from "@xmachines/play-router";
5
5
  export type { AbstractActor } from "@xmachines/play-actor";
6
+ /**
7
+ * A single state ID ↔ path mapping entry for the TanStack Solid Router adapter.
8
+ *
9
+ * Structurally compatible with `BaseRouteMapping` from `@xmachines/play-router`.
10
+ * Fields are `readonly` — entries are immutable once passed to `RouteMap`.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const mapping: RouteMapping = { stateId: "#profile", path: "/profile/:userId" };
15
+ * ```
16
+ */
6
17
  export interface RouteMapping {
7
- stateId: string;
8
- path: string;
18
+ /** XMachines state ID (e.g., `"#home"`, `"#profile"`) */
19
+ readonly stateId: string;
20
+ /** TanStack Router path pattern (e.g., `"/"`, `"/profile/:userId"`, `"/settings/:section?"`) */
21
+ readonly path: string;
9
22
  }
10
23
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACb"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmachines/play-tanstack-solid-router",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.9",
4
4
  "description": "TanStack Solid Router adapter for XMachines Universal Player Architecture",
5
5
  "license": "MIT",
6
6
  "author": "XMachines Contributors",
@@ -28,27 +28,29 @@
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsc --build",
31
- "test": "vitest run",
31
+ "test": "vitest",
32
32
  "test:watch": "vitest",
33
33
  "typecheck": "tsc --noEmit",
34
- "clean": "rm -rf dist *.tsbuildinfo",
34
+ "clean": "rm -rf dist *.tsbuildinfo node_modules/.vite node_modules/.vite-temp",
35
35
  "prepublishOnly": "npm run build"
36
36
  },
37
37
  "dependencies": {
38
- "@xmachines/play": "1.0.0-beta.7",
39
- "@xmachines/play-actor": "1.0.0-beta.7",
40
- "@xmachines/play-router": "1.0.0-beta.7",
41
- "@xmachines/play-signals": "1.0.0-beta.7"
38
+ "@xmachines/play": "1.0.0-beta.9",
39
+ "@xmachines/play-actor": "1.0.0-beta.9",
40
+ "@xmachines/play-router": "1.0.0-beta.9",
41
+ "@xmachines/play-signals": "1.0.0-beta.9"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@solidjs/testing-library": "^0.8.10",
45
45
  "@tanstack/solid-router": "^1.167.5",
46
- "@xmachines/shared": "1.0.0-beta.7",
46
+ "@xmachines/shared": "1.0.0-beta.9",
47
47
  "solid-js": "^1.9.11",
48
- "vite-plugin-solid": "^2.11.11"
48
+ "vite-plugin-solid": "^2.11.11",
49
+ "vitest": "^4.1.0",
50
+ "xstate": "^5.28.0"
49
51
  },
50
52
  "peerDependencies": {
51
- "@tanstack/solid-router": "^1.166.7",
53
+ "@tanstack/solid-router": "^1.108.0",
52
54
  "solid-js": "^1.8.0"
53
55
  }
54
56
  }