@xmachines/play-tanstack-solid-router 1.0.0-beta.1
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 +428 -0
- package/dist/create-route-map.d.ts +25 -0
- package/dist/create-route-map.d.ts.map +1 -0
- package/dist/create-route-map.js +36 -0
- package/dist/create-route-map.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/play-router-provider.d.ts +15 -0
- package/dist/play-router-provider.d.ts.map +1 -0
- package/dist/play-router-provider.jsx +11 -0
- package/dist/play-router-provider.jsx.map +1 -0
- package/dist/route-map.d.ts +75 -0
- package/dist/route-map.d.ts.map +1 -0
- package/dist/route-map.js +107 -0
- package/dist/route-map.js.map +1 -0
- package/dist/solid-router-bridge.d.ts +97 -0
- package/dist/solid-router-bridge.d.ts.map +1 -0
- package/dist/solid-router-bridge.js +98 -0
- package/dist/solid-router-bridge.js.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# @xmachines/play-tanstack-solid-router
|
|
2
|
+
|
|
3
|
+
**TanStack Solid Router adapter for XMachines Universal Player Architecture**
|
|
4
|
+
|
|
5
|
+
Signals-native integration with TanStack Solid Router enabling logic-driven navigation through Solid.js reactivity.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
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.
|
|
10
|
+
|
|
11
|
+
Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
|
|
12
|
+
|
|
13
|
+
- **Actor Authority (INV-01):** State machine controls navigation, router reflects decisions
|
|
14
|
+
- **Passive Infrastructure (INV-04):** Router observes `actor.currentRoute` signal
|
|
15
|
+
- **Signal-Only Reactivity (INV-05):** `createEffect` synchronizes URL with actor state
|
|
16
|
+
|
|
17
|
+
**Key Benefits:**
|
|
18
|
+
|
|
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)
|
|
22
|
+
- **Logic-driven navigation:** Business logic in state machines, not components
|
|
23
|
+
- **Type-safe parameters:** Route params flow through state machine context
|
|
24
|
+
|
|
25
|
+
**Framework Compatibility:**
|
|
26
|
+
|
|
27
|
+
- TanStack Solid Router 1.0.0+
|
|
28
|
+
- SolidJS 1.8.0+
|
|
29
|
+
- TC39 Signals polyfill integration
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @tanstack/solid-router@^1.0.0 solid-js@^1.8.0 @xmachines/play-tanstack-solid-router @xmachines/play-solid
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Peer dependencies:**
|
|
38
|
+
|
|
39
|
+
- `@tanstack/solid-router` ^1.0.0 - TanStack Solid Router library
|
|
40
|
+
- `solid-js` ^1.8.0 - SolidJS runtime
|
|
41
|
+
- `@xmachines/play-solid` - Solid renderer (`PlayRenderer`)
|
|
42
|
+
- `@xmachines/play-actor` - Actor base
|
|
43
|
+
- `@xmachines/play-router` - Route extraction
|
|
44
|
+
- `@xmachines/play-signals` - TC39 Signals polyfill
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
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';
|
|
55
|
+
|
|
56
|
+
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
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
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
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### `SolidRouterBridge`
|
|
88
|
+
|
|
89
|
+
Router adapter implementing the `RouterBridge` protocol for TanStack Solid Router.
|
|
90
|
+
|
|
91
|
+
**Constructor:**
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
new SolidRouterBridge(
|
|
95
|
+
router: Router,
|
|
96
|
+
actor: AbstractActor<any>,
|
|
97
|
+
routeMap: RouteMap
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Parameters:**
|
|
102
|
+
|
|
103
|
+
- `router`: TanStack Solid Router instance (from `createRouter()`)
|
|
104
|
+
- `actor`: XMachines player actor implementing signal protocol
|
|
105
|
+
- `routeMap`: Bidirectional state ID ↔ path mapping
|
|
106
|
+
|
|
107
|
+
**Methods:**
|
|
108
|
+
|
|
109
|
+
- `connect(): void` - Start bidirectional synchronization (actor ↔ router)
|
|
110
|
+
- `disconnect(): void` - Stop synchronization and cleanup subscriptions
|
|
111
|
+
|
|
112
|
+
**Important:** Must be created inside component where Solid hooks (`createEffect`, `onCleanup`) are available. Creating outside ownership context will cause reactivity to fail.
|
|
113
|
+
|
|
114
|
+
### `RouteMap`
|
|
115
|
+
|
|
116
|
+
Bidirectional mapping between XMachines state IDs and URL paths with pattern matching support.
|
|
117
|
+
|
|
118
|
+
**Constructor:**
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
new RouteMap(routes: RouteMapping[])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Interface:**
|
|
125
|
+
|
|
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
|
+
```
|
|
132
|
+
|
|
133
|
+
**Methods:**
|
|
134
|
+
|
|
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
|
|
137
|
+
|
|
138
|
+
### `createRouteMapFromTree()`
|
|
139
|
+
|
|
140
|
+
Helper to create RouteMap from RouteTree extracted from state machine.
|
|
141
|
+
|
|
142
|
+
**Signature:**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
function createRouteMapFromTree(routeTree: RouteTree): RouteMap;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Usage:**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { extractMachineRoutes } from "@xmachines/play-router";
|
|
152
|
+
import { createRouteMapFromTree } from "@xmachines/play-tanstack-solid-router";
|
|
153
|
+
|
|
154
|
+
const routeTree = extractMachineRoutes(machine);
|
|
155
|
+
const routeMap = createRouteMapFromTree(routeTree);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This helper traverses the route tree and creates RouteMapping entries for all routable states.
|
|
159
|
+
|
|
160
|
+
## Usage Patterns
|
|
161
|
+
|
|
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
|
+
### Dynamic Routes with Parameters
|
|
184
|
+
|
|
185
|
+
**Path parameters:**
|
|
186
|
+
|
|
187
|
+
```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:**
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
routeMap.getStateIdByPath("/profile/123"); // → '#profile'
|
|
199
|
+
routeMap.getStateIdByPath("/settings"); // → '#settings'
|
|
200
|
+
routeMap.getStateIdByPath("/settings/privacy"); // → '#settings'
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Cleanup on Component Unmount
|
|
204
|
+
|
|
205
|
+
**Always cleanup when component unmounts:**
|
|
206
|
+
|
|
207
|
+
```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>;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Circular Update Prevention
|
|
222
|
+
|
|
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
|
+
}
|
|
239
|
+
|
|
240
|
+
// Router navigation → Actor event
|
|
241
|
+
private syncActorFromRouter(location: RouterLocation): void {
|
|
242
|
+
this.isProcessingNavigation = true; // Set flag
|
|
243
|
+
|
|
244
|
+
// Send event to actor...
|
|
245
|
+
|
|
246
|
+
queueMicrotask(() => {
|
|
247
|
+
this.isProcessingNavigation = false; // Clear flag in microtask
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
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.
|
|
253
|
+
|
|
254
|
+
## Comparison with @solidjs/router Adapter
|
|
255
|
+
|
|
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
|
+
```
|
|
324
|
+
|
|
325
|
+
## Architecture
|
|
326
|
+
|
|
327
|
+
SolidRouterBridge follows XMachines Play architectural invariants:
|
|
328
|
+
|
|
329
|
+
### INV-01: Actor Authority
|
|
330
|
+
|
|
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
|
+
```
|
|
347
|
+
|
|
348
|
+
### INV-02: Passive Infrastructure
|
|
349
|
+
|
|
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
|
|
398
|
+
|
|
399
|
+
**Polyfill Installation:**
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
npm install urlpattern-polyfill
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Usage:**
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Top of your app entry point (e.g., main.tsx)
|
|
409
|
+
if (!globalThis.URLPattern) {
|
|
410
|
+
await import("urlpattern-polyfill");
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Bundle Size:** ~4KB gzipped (zero impact if URLPattern is native)
|
|
415
|
+
|
|
416
|
+
## Examples
|
|
417
|
+
|
|
418
|
+
See `examples/demo/` for the maintained integration example and tests.
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
**Package:** @xmachines/play-tanstack-solid-router
|
|
427
|
+
**Pattern:** RouterBridge (~220 lines, 2 flags)
|
|
428
|
+
**Framework:** TanStack Solid Router 1.0+ with SolidJS 1.8+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create RouteMap helper for TanStack Solid Router adapter
|
|
3
|
+
*
|
|
4
|
+
* Simplifies demo code by hiding route extraction complexity.
|
|
5
|
+
* Internally calls extractMachineRoutes() and getRoutableRoutes(),
|
|
6
|
+
* then constructs RouteMap with proper state ID ↔ path mappings.
|
|
7
|
+
*/
|
|
8
|
+
import type { AnyStateMachine } from "xstate";
|
|
9
|
+
import { RouteMap } from "./route-map.js";
|
|
10
|
+
/**
|
|
11
|
+
* Create a RouteMap from an XState machine
|
|
12
|
+
*
|
|
13
|
+
* @param machine - XState machine with route: {} config on states
|
|
14
|
+
* @returns RouteMap for bidirectional state ID ↔ path resolution
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createRouteMap } from '@xmachines/play-tanstack-solid-router';
|
|
19
|
+
* import { authMachine } from './machine.js';
|
|
20
|
+
*
|
|
21
|
+
* const routeMap = createRouteMap(authMachine);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function createRouteMap(machine: AnyStateMachine): RouteMap;
|
|
25
|
+
//# sourceMappingURL=create-route-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAcjE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create RouteMap helper for TanStack Solid Router adapter
|
|
3
|
+
*
|
|
4
|
+
* Simplifies demo code by hiding route extraction complexity.
|
|
5
|
+
* Internally calls extractMachineRoutes() and getRoutableRoutes(),
|
|
6
|
+
* then constructs RouteMap with proper state ID ↔ path mappings.
|
|
7
|
+
*/
|
|
8
|
+
import { extractMachineRoutes, getRoutableRoutes } from "@xmachines/play-router";
|
|
9
|
+
import { RouteMap } from "./route-map.js";
|
|
10
|
+
/**
|
|
11
|
+
* Create a RouteMap from an XState machine
|
|
12
|
+
*
|
|
13
|
+
* @param machine - XState machine with route: {} config on states
|
|
14
|
+
* @returns RouteMap for bidirectional state ID ↔ path resolution
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createRouteMap } from '@xmachines/play-tanstack-solid-router';
|
|
19
|
+
* import { authMachine } from './machine.js';
|
|
20
|
+
*
|
|
21
|
+
* const routeMap = createRouteMap(authMachine);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function createRouteMap(machine) {
|
|
25
|
+
// Extract route tree from machine
|
|
26
|
+
const routeTree = extractMachineRoutes(machine);
|
|
27
|
+
// Get only routable nodes (states with route: {} config)
|
|
28
|
+
const routes = getRoutableRoutes(routeTree);
|
|
29
|
+
// Map to RouteMapping format expected by RouteMap constructor
|
|
30
|
+
const mappings = routes.map((node) => ({
|
|
31
|
+
stateId: node.stateId,
|
|
32
|
+
path: node.fullPath,
|
|
33
|
+
}));
|
|
34
|
+
return new RouteMap(mappings);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=create-route-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-route-map.js","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,OAAwB;IACtD,kCAAkC;IAClC,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAEhD,yDAAyD;IACzD,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAE5C,8DAA8D;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,QAAQ;KACnB,CAAC,CAAC,CAAC;IAEJ,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xmachines/play-tanstack-solid-router
|
|
3
|
+
*
|
|
4
|
+
* TanStack Solid Router adapter for XMachines Universal Player Architecture
|
|
5
|
+
*/
|
|
6
|
+
export { SolidRouterBridge } from "./solid-router-bridge.js";
|
|
7
|
+
export { PlayRouterProvider } from "./play-router-provider.js";
|
|
8
|
+
export type { PlayRouterProviderProps, RoutableActor, TanStackRouterInstance, } from "./play-router-provider.js";
|
|
9
|
+
export { RouteMap } from "./route-map.js";
|
|
10
|
+
export { createRouteMap } from "./create-route-map.js";
|
|
11
|
+
export type { TanStackRouterLike } from "./solid-router-bridge.js";
|
|
12
|
+
export type { RouteMapping, RouterBridge, PlayRouteEvent } from "./types.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EACX,uBAAuB,EACvB,aAAa,EACb,sBAAsB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xmachines/play-tanstack-solid-router
|
|
3
|
+
*
|
|
4
|
+
* TanStack Solid Router adapter for XMachines Universal Player Architecture
|
|
5
|
+
*/
|
|
6
|
+
export { SolidRouterBridge } from "./solid-router-bridge.js";
|
|
7
|
+
export { PlayRouterProvider } from "./play-router-provider.js";
|
|
8
|
+
export { RouteMap } from "./route-map.js";
|
|
9
|
+
export { createRouteMap } from "./create-route-map.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAM/D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type JSX } from "solid-js";
|
|
2
|
+
import type { AbstractActor, Routable, Viewable } from "@xmachines/play-actor";
|
|
3
|
+
import type { AnyActorLogic } from "xstate";
|
|
4
|
+
import { SolidRouterBridge } from "./solid-router-bridge.js";
|
|
5
|
+
import type { RouteMap } from "./route-map.js";
|
|
6
|
+
export type RoutableActor = AbstractActor<AnyActorLogic> & Routable & Viewable;
|
|
7
|
+
export type TanStackRouterInstance = ConstructorParameters<typeof SolidRouterBridge>[0];
|
|
8
|
+
export interface PlayRouterProviderProps {
|
|
9
|
+
actor: RoutableActor;
|
|
10
|
+
router: TanStackRouterInstance;
|
|
11
|
+
routeMap: RouteMap;
|
|
12
|
+
renderer: (actor: RoutableActor, router: TanStackRouterInstance) => JSX.Element;
|
|
13
|
+
}
|
|
14
|
+
export declare function PlayRouterProvider(props: PlayRouterProviderProps): any;
|
|
15
|
+
//# sourceMappingURL=play-router-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play-router-provider.d.ts","sourceRoot":"","sources":["../src/play-router-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC/E,MAAM,MAAM,sBAAsB,GAAG,qBAAqB,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAExF,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,sBAAsB,KAAK,GAAG,CAAC,OAAO,CAAC;CAChF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,OAShE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { onCleanup } from "solid-js";
|
|
2
|
+
import { SolidRouterBridge } from "./solid-router-bridge.js";
|
|
3
|
+
export function PlayRouterProvider(props) {
|
|
4
|
+
const bridge = new SolidRouterBridge(props.router, props.actor, props.routeMap);
|
|
5
|
+
bridge.connect();
|
|
6
|
+
onCleanup(() => {
|
|
7
|
+
bridge.disconnect();
|
|
8
|
+
});
|
|
9
|
+
return <>{props.renderer(props.actor, props.router)}</>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=play-router-provider.jsx.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play-router-provider.jsx","sourceRoot":"","sources":["../src/play-router-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,UAAU,CAAC;AAG/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAa7D,MAAM,UAAU,kBAAkB,CAAC,KAA8B;IAChE,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChF,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjB,SAAS,CAAC,GAAG,EAAE;QACd,MAAM,CAAC,UAAU,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RouteMap for bidirectional state ID ↔ path mapping
|
|
3
|
+
*
|
|
4
|
+
* Provides efficient lookup between XMachines state IDs and TanStack Router paths.
|
|
5
|
+
* Supports pattern matching for dynamic routes with parameters.
|
|
6
|
+
*/
|
|
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;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=route-map.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RouteMap for bidirectional state ID ↔ path mapping
|
|
3
|
+
*
|
|
4
|
+
* Provides efficient lookup between XMachines state IDs and TanStack Router paths.
|
|
5
|
+
* Supports pattern matching for dynamic routes with parameters.
|
|
6
|
+
*/
|
|
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
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=route-map.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack Solid Router bridge implementing RouterBridge protocol via RouterBridgeBase
|
|
3
|
+
*
|
|
4
|
+
* Extends RouterBridgeBase to handle all common lifecycle and sync logic.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* \`\`\`tsx
|
|
8
|
+
* import { createRouter } from '@tanstack/solid-router';
|
|
9
|
+
* import { onCleanup } from 'solid-js';
|
|
10
|
+
* import { SolidRouterBridge, RouteMap } from '@xmachines/play-tanstack-solid-router';
|
|
11
|
+
*
|
|
12
|
+
* function App() {
|
|
13
|
+
* const routeMap = new RouteMap([...]);
|
|
14
|
+
* const bridge = new SolidRouterBridge(router, actor, routeMap);
|
|
15
|
+
* bridge.connect();
|
|
16
|
+
* onCleanup(() => bridge.disconnect());
|
|
17
|
+
* return <div>...</div>;
|
|
18
|
+
* }
|
|
19
|
+
* \`\`\`
|
|
20
|
+
*/
|
|
21
|
+
import { RouterBridgeBase } from "@xmachines/play-router";
|
|
22
|
+
import type { AbstractActor, Routable } from "@xmachines/play-actor";
|
|
23
|
+
import type { AnyActorLogic } from "xstate";
|
|
24
|
+
import type { RouteMap } from "./route-map.js";
|
|
25
|
+
export type TanStackRouterLike = {
|
|
26
|
+
navigate(args: {
|
|
27
|
+
to: string;
|
|
28
|
+
}): void;
|
|
29
|
+
state?: {
|
|
30
|
+
location?: {
|
|
31
|
+
pathname?: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
history: {
|
|
35
|
+
subscribe(handler: (event: {
|
|
36
|
+
location: {
|
|
37
|
+
pathname: string;
|
|
38
|
+
search?: string;
|
|
39
|
+
};
|
|
40
|
+
}) => void): () => void;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* TanStack Solid Router integration bridge extending RouterBridgeBase
|
|
45
|
+
*
|
|
46
|
+
* Uses router.subscribe("onBeforeLoad", fn) for router→actor sync.
|
|
47
|
+
* This mirrors the TanStack React Router bridge pattern and uses the
|
|
48
|
+
* public router events API instead of the internal __store.
|
|
49
|
+
*/
|
|
50
|
+
export declare class SolidRouterBridge extends RouterBridgeBase {
|
|
51
|
+
private readonly router;
|
|
52
|
+
private _unsubscribeRouter;
|
|
53
|
+
/**
|
|
54
|
+
* Create a TanStack Solid Router bridge
|
|
55
|
+
*
|
|
56
|
+
* @param router - TanStack Router instance (from createRouter)
|
|
57
|
+
* @param actor - XMachines actor instance
|
|
58
|
+
* @param routeMap - Bidirectional state ID ↔ path mapping
|
|
59
|
+
*/
|
|
60
|
+
constructor(router: TanStackRouterLike, actor: AbstractActor<AnyActorLogic> & Routable, routeMap: RouteMap);
|
|
61
|
+
/**
|
|
62
|
+
* Navigate TanStack Solid Router to the given path.
|
|
63
|
+
*/
|
|
64
|
+
protected navigateRouter(path: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Get the router's current path at connect() time for initial sync.
|
|
67
|
+
*
|
|
68
|
+
* TanStack Router exposes router.state.location.pathname synchronously,
|
|
69
|
+
* so we can read it here to drive the actor to the correct initial state
|
|
70
|
+
* when the user cold-loads on a deep-link URL.
|
|
71
|
+
*/
|
|
72
|
+
protected getInitialRouterPath(): string | null;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to ALL navigation events via router.history.
|
|
75
|
+
*
|
|
76
|
+
* router.history.subscribe fires for PUSH, POP, BACK, FORWARD, REPLACE, and GO —
|
|
77
|
+
* covering both link clicks and browser back/forward button presses.
|
|
78
|
+
*
|
|
79
|
+
* The subscriber callback receives { location, action } where location is the
|
|
80
|
+
* new history location with pathname and search already updated.
|
|
81
|
+
*
|
|
82
|
+
* Using router.history rather than router.subscribe("onBeforeLoad") ensures
|
|
83
|
+
* back/forward works regardless of whether <RouterProvider> is mounted.
|
|
84
|
+
*
|
|
85
|
+
* Also registers onCleanup() so Solid automatically disconnects on unmount.
|
|
86
|
+
*/
|
|
87
|
+
protected watchRouterChanges(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Stop watching TanStack Router changes.
|
|
90
|
+
*/
|
|
91
|
+
protected unwatchRouterChanges(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Dispose the bridge (alias for disconnect).
|
|
94
|
+
*/
|
|
95
|
+
dispose(): void;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=solid-router-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solid-router-bridge.d.ts","sourceRoot":"","sources":["../src/solid-router-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAC7C,OAAO,EAAE;QACR,SAAS,CACR,OAAO,EAAE,CAAC,KAAK,EAAE;YAAE,QAAQ,EAAE;gBAAE,QAAQ,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,KAAK,IAAI,GAC3E,MAAM,IAAI,CAAC;KACd,CAAC;CACF,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,gBAAgB;IAWrD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVxB,OAAO,CAAC,kBAAkB,CAA6B;IAEvD;;;;;;OAMG;gBAEe,MAAM,EAAE,kBAAkB,EAC3C,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,EAC9C,QAAQ,EAAE,QAAQ;IAQnB;;OAEG;IACH,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI5C;;;;;;OAMG;cACgB,oBAAoB,IAAI,MAAM,GAAG,IAAI;IAIxD;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAWpC;;OAEG;IACH,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAKtC;;OAEG;IACH,OAAO,IAAI,IAAI;CAGf"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack Solid Router bridge implementing RouterBridge protocol via RouterBridgeBase
|
|
3
|
+
*
|
|
4
|
+
* Extends RouterBridgeBase to handle all common lifecycle and sync logic.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* \`\`\`tsx
|
|
8
|
+
* import { createRouter } from '@tanstack/solid-router';
|
|
9
|
+
* import { onCleanup } from 'solid-js';
|
|
10
|
+
* import { SolidRouterBridge, RouteMap } from '@xmachines/play-tanstack-solid-router';
|
|
11
|
+
*
|
|
12
|
+
* function App() {
|
|
13
|
+
* const routeMap = new RouteMap([...]);
|
|
14
|
+
* const bridge = new SolidRouterBridge(router, actor, routeMap);
|
|
15
|
+
* bridge.connect();
|
|
16
|
+
* onCleanup(() => bridge.disconnect());
|
|
17
|
+
* return <div>...</div>;
|
|
18
|
+
* }
|
|
19
|
+
* \`\`\`
|
|
20
|
+
*/
|
|
21
|
+
import { onCleanup } from "solid-js";
|
|
22
|
+
import { RouterBridgeBase } from "@xmachines/play-router";
|
|
23
|
+
/**
|
|
24
|
+
* TanStack Solid Router integration bridge extending RouterBridgeBase
|
|
25
|
+
*
|
|
26
|
+
* Uses router.subscribe("onBeforeLoad", fn) for router→actor sync.
|
|
27
|
+
* This mirrors the TanStack React Router bridge pattern and uses the
|
|
28
|
+
* public router events API instead of the internal __store.
|
|
29
|
+
*/
|
|
30
|
+
export class SolidRouterBridge extends RouterBridgeBase {
|
|
31
|
+
router;
|
|
32
|
+
_unsubscribeRouter = null;
|
|
33
|
+
/**
|
|
34
|
+
* Create a TanStack Solid Router bridge
|
|
35
|
+
*
|
|
36
|
+
* @param router - TanStack Router instance (from createRouter)
|
|
37
|
+
* @param actor - XMachines actor instance
|
|
38
|
+
* @param routeMap - Bidirectional state ID ↔ path mapping
|
|
39
|
+
*/
|
|
40
|
+
constructor(router, actor, routeMap) {
|
|
41
|
+
super(actor, {
|
|
42
|
+
getStateIdByPath: (path) => routeMap.getStateIdByPath(path) ?? undefined,
|
|
43
|
+
getPathByStateId: (id) => routeMap.getPathByStateId(id) ?? undefined,
|
|
44
|
+
});
|
|
45
|
+
this.router = router;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Navigate TanStack Solid Router to the given path.
|
|
49
|
+
*/
|
|
50
|
+
navigateRouter(path) {
|
|
51
|
+
this.router.navigate({ to: path });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the router's current path at connect() time for initial sync.
|
|
55
|
+
*
|
|
56
|
+
* TanStack Router exposes router.state.location.pathname synchronously,
|
|
57
|
+
* so we can read it here to drive the actor to the correct initial state
|
|
58
|
+
* when the user cold-loads on a deep-link URL.
|
|
59
|
+
*/
|
|
60
|
+
getInitialRouterPath() {
|
|
61
|
+
return this.router.state?.location?.pathname ?? null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Subscribe to ALL navigation events via router.history.
|
|
65
|
+
*
|
|
66
|
+
* router.history.subscribe fires for PUSH, POP, BACK, FORWARD, REPLACE, and GO —
|
|
67
|
+
* covering both link clicks and browser back/forward button presses.
|
|
68
|
+
*
|
|
69
|
+
* The subscriber callback receives { location, action } where location is the
|
|
70
|
+
* new history location with pathname and search already updated.
|
|
71
|
+
*
|
|
72
|
+
* Using router.history rather than router.subscribe("onBeforeLoad") ensures
|
|
73
|
+
* back/forward works regardless of whether <RouterProvider> is mounted.
|
|
74
|
+
*
|
|
75
|
+
* Also registers onCleanup() so Solid automatically disconnects on unmount.
|
|
76
|
+
*/
|
|
77
|
+
watchRouterChanges() {
|
|
78
|
+
this._unsubscribeRouter = this.router.history.subscribe(({ location }) => {
|
|
79
|
+
this.syncActorFromRouter(location.pathname, location.search ?? "");
|
|
80
|
+
});
|
|
81
|
+
// Solid's onCleanup — automatically disconnects when component unmounts
|
|
82
|
+
onCleanup(() => this.disconnect());
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Stop watching TanStack Router changes.
|
|
86
|
+
*/
|
|
87
|
+
unwatchRouterChanges() {
|
|
88
|
+
this._unsubscribeRouter?.();
|
|
89
|
+
this._unsubscribeRouter = null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Dispose the bridge (alias for disconnect).
|
|
93
|
+
*/
|
|
94
|
+
dispose() {
|
|
95
|
+
this.disconnect();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=solid-router-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solid-router-bridge.js","sourceRoot":"","sources":["../src/solid-router-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAe1D;;;;;;GAMG;AACH,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;IAWpC;IAVV,kBAAkB,GAAwB,IAAI,CAAC;IAEvD;;;;;;OAMG;IACH,YACkB,MAA0B,EAC3C,KAA8C,EAC9C,QAAkB;QAElB,KAAK,CAAC,KAAK,EAAE;YACZ,gBAAgB,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,SAAS;YAChF,gBAAgB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,SAAS;SAC5E,CAAC,CAAC;QAPc,WAAM,GAAN,MAAM,CAAoB;IAQ5C,CAAC;IAED;;OAEG;IACO,cAAc,CAAC,IAAY;QACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACgB,oBAAoB;QACtC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI,CAAC;IACtD,CAAC;IAED;;;;;;;;;;;;;OAaG;IACO,kBAAkB;QAC3B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CACtD,CAAC,EAAE,QAAQ,EAAuD,EAAE,EAAE;YACrE,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QACpE,CAAC,CACD,CAAC;QAEF,wEAAwE;QACxE,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACO,oBAAoB;QAC7B,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,OAAO;QACN,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;CACD"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for TanStack Solid Router adapter
|
|
3
|
+
*/
|
|
4
|
+
export type { RouterBridge, PlayRouteEvent } from "@xmachines/play-router";
|
|
5
|
+
export type { AbstractActor } from "@xmachines/play-actor";
|
|
6
|
+
export interface RouteMapping {
|
|
7
|
+
stateId: string;
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xmachines/play-tanstack-solid-router",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "TanStack Solid Router adapter for XMachines Universal Player Architecture",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "XMachines Contributors",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git@gitlab.com:xmachin-es/xmachines-js.git",
|
|
10
|
+
"directory": "packages/play-tanstack-solid-router"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc --build",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@xmachines/play": "1.0.0-beta.1",
|
|
35
|
+
"@xmachines/play-actor": "1.0.0-beta.1",
|
|
36
|
+
"@xmachines/play-router": "1.0.0-beta.1",
|
|
37
|
+
"@xmachines/play-signals": "1.0.0-beta.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@solidjs/testing-library": "^0.8.0",
|
|
41
|
+
"@tanstack/solid-router": "^1.166.7",
|
|
42
|
+
"solid-js": "^1.8.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@tanstack/solid-router": "^1.166.7",
|
|
46
|
+
"solid-js": "^1.8.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|