@xmachines/play-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 ADDED
@@ -0,0 +1,637 @@
1
+ # @xmachines/play-solid-router
2
+
3
+ **SolidJS Router adapter for XMachines Universal Player Architecture**
4
+
5
+ SolidJS Router adapter using `RouterBridgeBase` for consistent actor↔router sync.
6
+
7
+ ## Overview
8
+
9
+ `@xmachines/play-solid-router` provides seamless integration between SolidJS Router and XMachines state machines. Built on Solid's reactive primitives, it enables zero-adaptation signals 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):** TC39 watcher lifecycle + Solid reactive owner integration
16
+
17
+ **Key Benefits:**
18
+
19
+ - **Bridge-first:** Extends shared `RouterBridgeBase` policy used by other adapters
20
+ - **Automatic tracking:** Uses Solid reactivity for router→actor while base class handles actor→router watcher lifecycle
21
+ - **Fine-grained reactivity:** Updates only affected components
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
+ - SolidJS 1.8.4+ (signals-native architecture)
28
+ - @solidjs/router 0.13.0+ (modern routing primitives)
29
+ - TC39 Signals polyfill integration
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install @solidjs/router@^0.13.0 solid-js@^1.8.0 @xmachines/play-solid-router @xmachines/play-solid
35
+ ```
36
+
37
+ **Peer dependencies:**
38
+
39
+ - `@solidjs/router` ^0.13.0 - SolidJS 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 { Router, Route, useNavigate, useLocation, useParams } from '@solidjs/router';
50
+ import { onCleanup } from 'solid-js';
51
+ import { SolidRouterBridge, createRouteMap } from '@xmachines/play-solid-router';
52
+ import { definePlayer } from '@xmachines/play-xstate';
53
+
54
+ function App() {
55
+ // 1. Get SolidJS Router hooks (MUST be inside component)
56
+ const navigate = useNavigate();
57
+ const location = useLocation();
58
+ const params = useParams();
59
+
60
+ // 2. Create route mapping from machine routes
61
+ const routeMap = createRouteMap(authMachine);
62
+
63
+ // 3. Create player with state machine
64
+ const createPlayer = definePlayer({
65
+ machine: authMachine,
66
+ catalog: componentCatalog
67
+ });
68
+ const actor = createPlayer();
69
+ actor.start();
70
+
71
+ // 4. Create bridge to sync actor and router
72
+ const bridge = new SolidRouterBridge(
73
+ navigate,
74
+ location,
75
+ params,
76
+ actor,
77
+ routeMap
78
+ );
79
+
80
+ // 5. Start synchronization
81
+ bridge.connect();
82
+
83
+ // 6. Cleanup on component disposal
84
+ onCleanup(() => {
85
+ bridge.disconnect();
86
+ });
87
+
88
+ return (
89
+ <Router>
90
+ <Route path="/" component={HomeView} />
91
+ <Route path="/profile/:userId" component={ProfileView} />
92
+ <Route path="/settings/:section?" component={SettingsView} />
93
+ </Router>
94
+ );
95
+ }
96
+ ```
97
+
98
+ ## API Reference
99
+
100
+ ### `SolidRouterBridge`
101
+
102
+ Router adapter implementing the `RouterBridge` protocol for SolidJS Router.
103
+
104
+ **Type Signature:**
105
+
106
+ ```typescript
107
+ class SolidRouterBridge {
108
+ constructor(
109
+ navigate: ReturnType<typeof useNavigate>,
110
+ location: ReturnType<typeof useLocation>,
111
+ params: ReturnType<typeof useParams>,
112
+ actor: AbstractActor<any>,
113
+ routeMap: RouteMap,
114
+ );
115
+ dispose(): void;
116
+ }
117
+ ```
118
+
119
+ **Constructor Parameters:**
120
+
121
+ - `navigate` - Function from `useNavigate()` hook (signals-aware navigation)
122
+ - `location` - Object from `useLocation()` hook (reactive pathname, search, hash)
123
+ - `params` - Object from `useParams()` hook (reactive route parameters)
124
+ - `actor` - XMachines actor instance (from `definePlayer().actor`)
125
+ - `routeMap` - Bidirectional state ID ↔ path mapping
126
+
127
+ **Methods:**
128
+
129
+ - `connect()` - Start bidirectional synchronization.
130
+ - `disconnect()` - Stop synchronization and cleanup bridge resources.
131
+ - `dispose()` - Alias of `disconnect()`.
132
+
133
+ **Internal Behavior:**
134
+
135
+ - Uses `RouterBridgeBase` TC39 watcher lifecycle for actor→router synchronization
136
+ - Updates SolidJS Router via `navigate(path)` when actor state changes
137
+ - Uses `createEffect(on(...))` to watch `location.pathname` signal
138
+ - Sends `play.route` events to actor when user navigates
139
+ - Prevents circular updates with path tracking and processing flags
140
+
141
+ ### `RouteMap`
142
+
143
+ Bidirectional mapping between XMachines state IDs and SolidJS Router paths with pattern matching support.
144
+
145
+ **Type Signature:**
146
+
147
+ ```typescript
148
+ interface RouteMapping {
149
+ stateId: string;
150
+ path: string;
151
+ }
152
+
153
+ class RouteMap {
154
+ constructor(mappings: RouteMapping[]);
155
+ getPath(stateId: string, params?: Record<string, string>): string | undefined;
156
+ getStateIdByPath(path: string): string | undefined;
157
+ }
158
+ ```
159
+
160
+ **Constructor Parameters:**
161
+
162
+ - `mappings` - Array of mapping objects with:
163
+ - `stateId` - State machine state ID (e.g., `'#profile'`)
164
+ - `path` - SolidJS Router path pattern (e.g., `'/profile/:userId'`)
165
+
166
+ **Methods:**
167
+
168
+ - `getPath(stateId, params?)` - Find path from state ID, optionally substitute params
169
+ - `getStateIdByPath(path)` - Find state ID from path with pattern matching (supports `:param` and `:param?` syntax)
170
+
171
+ **Pattern Matching:**
172
+
173
+ Uses URLPattern API for robust dynamic route matching:
174
+
175
+ ```typescript
176
+ const routeMap = new RouteMap([{ stateId: "#settings", path: "/settings/:section?" }]);
177
+
178
+ routeMap.getStateIdByPath("/settings"); // '#settings'
179
+ routeMap.getStateIdByPath("/settings/account"); // '#settings'
180
+ routeMap.getStateIdByPath("/settings/privacy"); // '#settings'
181
+ ```
182
+
183
+ ## Examples
184
+
185
+ ### Basic Usage: Simple 2-3 Route Setup
186
+
187
+ ```typescript
188
+ import { Router, Route } from '@solidjs/router';
189
+ import { createSignal } from 'solid-js';
190
+ import { defineCatalog } from '@xmachines/play-catalog';
191
+
192
+ // State machine with 3 states
193
+ const appMachine = setup({
194
+ types: {
195
+ events: {} as PlayRouteEvent
196
+ }
197
+ }).createMachine({
198
+ id: 'app',
199
+ initial: 'home',
200
+ states: {
201
+ home: {
202
+ meta: { route: '/', view: { component: 'Home' } }
203
+ },
204
+ about: {
205
+ meta: { route: '/about', view: { component: 'About' } }
206
+ },
207
+ contact: {
208
+ meta: { route: '/contact', view: { component: 'Contact' } }
209
+ }
210
+ }
211
+ });
212
+
213
+ const catalog = defineCatalog({
214
+ Home,
215
+ About,
216
+ Contact,
217
+ });
218
+
219
+ // Component setup
220
+ function App() {
221
+ const navigate = useNavigate();
222
+ const location = useLocation();
223
+ const params = useParams();
224
+
225
+ const routeMap = createRouteMap(appMachine);
226
+
227
+ const createPlayer = definePlayer({ machine: appMachine, catalog });
228
+ const actor = createPlayer();
229
+ actor.start();
230
+ const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
231
+
232
+ onCleanup(() => bridge.dispose());
233
+
234
+ return (
235
+ <Router>
236
+ <Route path="/" component={Home} />
237
+ <Route path="/about" component={About} />
238
+ <Route path="/contact" component={Contact} />
239
+ </Router>
240
+ );
241
+ }
242
+ ```
243
+
244
+ ### Parameter Handling: Dynamic Routes with `:param` Syntax
245
+
246
+ ```typescript
247
+ // State machine with parameter routes
248
+ import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
249
+ import { defineCatalog } from "@xmachines/play-catalog";
250
+
251
+ const machineConfig = {
252
+ id: 'app',
253
+ context: {},
254
+ states: {
255
+ profile: {
256
+ meta: {
257
+ route: '/profile/:userId',
258
+ view: { component: 'Profile' },
259
+ },
260
+ },
261
+ settings: {
262
+ meta: {
263
+ route: '/settings/:section?',
264
+ view: { component: 'Settings' },
265
+ },
266
+ }
267
+ }
268
+ };
269
+
270
+ const appMachine = setup({
271
+ types: {
272
+ context: {} as { userId?: string; section?: string },
273
+ events: {} as PlayRouteEvent
274
+ }
275
+ }).createMachine(formatPlayRouteTransitions(machineConfig));
276
+
277
+ const catalog = defineCatalog({
278
+ Profile,
279
+ Settings,
280
+ });
281
+
282
+ // Router with dynamic routes
283
+ function App() {
284
+ const navigate = useNavigate();
285
+ const location = useLocation();
286
+ const params = useParams();
287
+
288
+ const routeMap = createRouteMap(appMachine);
289
+
290
+ const createPlayer = definePlayer({ machine: appMachine, catalog });
291
+ const actor = createPlayer();
292
+ actor.start();
293
+ const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
294
+
295
+ onCleanup(() => bridge.dispose());
296
+
297
+ return (
298
+ <Router>
299
+ <Route path="/profile/:userId" component={Profile} />
300
+ <Route path="/settings/:section?" component={Settings} />
301
+ </Router>
302
+ );
303
+ }
304
+ ```
305
+
306
+ **Usage in component:**
307
+
308
+ ```tsx
309
+ function ProfileButton(props: { userId: string }) {
310
+ return (
311
+ <button
312
+ onClick={() =>
313
+ props.actor.send({
314
+ type: "play.route",
315
+ to: "#profile",
316
+ params: { userId: props.userId },
317
+ })
318
+ }
319
+ >
320
+ View Profile
321
+ </button>
322
+ );
323
+ }
324
+ ```
325
+
326
+ ### Query Parameters: Search/Filters via Query Strings
327
+
328
+ ```typescript
329
+ // State machine with query param handling
330
+ import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
331
+ import { defineCatalog } from "@xmachines/play-catalog";
332
+
333
+ const machineConfig = {
334
+ context: { query: '', filters: {} },
335
+ states: {
336
+ search: {
337
+ meta: {
338
+ route: '/search',
339
+ view: { component: 'Search' },
340
+ },
341
+ }
342
+ }
343
+ };
344
+
345
+ const searchMachine = setup({
346
+ types: {
347
+ context: {} as { query: string; filters: Record<string, string> },
348
+ events: {} as PlayRouteEvent
349
+ }
350
+ }).createMachine(formatPlayRouteTransitions(machineConfig));
351
+
352
+ const catalog = defineCatalog({
353
+ Search,
354
+ });
355
+
356
+ const player = definePlayer({ machine: searchMachine, catalog });
357
+
358
+ // Component sends query params
359
+ function SearchBar(props) {
360
+ const [searchTerm, setSearchTerm] = createSignal('');
361
+
362
+ function handleSearch() {
363
+ props.actor.send({
364
+ type: 'play.route',
365
+ to: '#search',
366
+ query: { q: searchTerm(), tag: 'typescript' }
367
+ });
368
+ }
369
+
370
+ return (
371
+ <div>
372
+ <input
373
+ value={searchTerm()}
374
+ onInput={(e) => setSearchTerm(e.target.value)}
375
+ />
376
+ <button onClick={handleSearch}>Search</button>
377
+ </div>
378
+ );
379
+ }
380
+ ```
381
+
382
+ **SolidJS Router automatically reflects query params in URL:**
383
+
384
+ - `/search?q=xmachines&tag=typescript`
385
+
386
+ ### Protected Routes: Authentication Guards
387
+
388
+ ```typescript
389
+ // State machine with auth guards
390
+ import { defineCatalog } from "@xmachines/play-catalog";
391
+
392
+ const authMachine = setup({
393
+ types: {
394
+ context: {} as { isAuthenticated: boolean },
395
+ events: {} as PlayRouteEvent | { type: "login" } | { type: "logout" },
396
+ },
397
+ }).createMachine({
398
+ context: { isAuthenticated: false },
399
+ initial: "home",
400
+ states: {
401
+ home: {
402
+ meta: { route: "/", view: { component: "Home" } },
403
+ },
404
+ login: {
405
+ meta: { route: "/login", view: { component: "Login" } },
406
+ on: {
407
+ login: {
408
+ target: "dashboard",
409
+ actions: assign({ isAuthenticated: true }),
410
+ },
411
+ },
412
+ },
413
+ dashboard: {
414
+ meta: { route: "/dashboard", view: { component: "Dashboard" } },
415
+ always: {
416
+ guard: ({ context }) => !context.isAuthenticated,
417
+ target: "login",
418
+ },
419
+ },
420
+ },
421
+ });
422
+
423
+ const catalog = defineCatalog({
424
+ Home,
425
+ Login,
426
+ Dashboard,
427
+ });
428
+
429
+ const player = definePlayer({ machine: authMachine, catalog });
430
+ ```
431
+
432
+ **Guard behavior:**
433
+
434
+ - User navigates to `/dashboard`
435
+ - Bridge sends `play.route` event to actor
436
+ - Actor's `always` guard checks `isAuthenticated`
437
+ - If `false`, actor transitions to `login` state
438
+ - Bridge detects state change via `createEffect`, redirects to `/login`
439
+ - Actor Authority principle enforced
440
+
441
+ ### Cleanup: Proper Disposal on Component Unmount
442
+
443
+ ```tsx
444
+ import { onCleanup } from "solid-js";
445
+ import { SolidRouterBridge } from "@xmachines/play-solid-router";
446
+
447
+ function App() {
448
+ const navigate = useNavigate();
449
+ const location = useLocation();
450
+ const params = useParams();
451
+ const actor = useContext(ActorContext);
452
+ const routeMap = useContext(RouteMapContext);
453
+
454
+ const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
455
+
456
+ // CRITICAL: Cleanup effects
457
+ onCleanup(() => {
458
+ bridge.dispose();
459
+ });
460
+
461
+ return <Router>...</Router>;
462
+ }
463
+ ```
464
+
465
+ **Why cleanup matters:**
466
+
467
+ - `createEffect` subscriptions continue after disposal (memory leak)
468
+ - Multiple bridge instances send duplicate events
469
+ - Tests fail with "Cannot send to stopped actor" errors
470
+ - Solid's fine-grained reactivity tracks disposed components
471
+
472
+ ## Architecture
473
+
474
+ ### Bidirectional Sync (Actor ↔ Router)
475
+
476
+ **Actor → Router (Signal-driven via createEffect):**
477
+
478
+ 1. Actor transitions to new state with `meta.route`
479
+ 2. `actor.currentRoute` signal updates
480
+ 3. `createEffect(on(...))` fires with new route value
481
+ 4. Bridge extracts state ID from signal
482
+ 5. Bridge looks up path via `routeMap.getPath(stateId, params)`
483
+ 6. Bridge calls `navigate(path)`
484
+ 7. SolidJS Router updates URL and renders component
485
+
486
+ **Router → Actor (Location tracking via createEffect):**
487
+
488
+ 1. User clicks link or browser back button
489
+ 2. `location.pathname` signal updates
490
+ 3. `createEffect(on(...))` fires with new pathname
491
+ 4. Bridge looks up state ID via `routeMap.getStateIdByPath(pathname)`
492
+ 5. Bridge extracts params from `useParams()` reactive object
493
+ 6. Bridge sends `play.route` event to actor
494
+ 7. Actor validates navigation (guards, transitions)
495
+ 8. If accepted: Actor transitions, signal updates, URL stays
496
+ 9. If rejected: Actor redirects, bridge corrects URL via `navigate()`
497
+
498
+ ### Circular Update Prevention
499
+
500
+ **Multi-layer guards prevent infinite loops:**
501
+
502
+ 1. **`lastSyncedPath` tracking:** Stores last synchronized path, skips if unchanged
503
+ 2. **`isProcessingNavigation` flag:** Set during navigation processing, prevents concurrent syncs
504
+ 3. **Effect timing:** Solid's batched updates and `defer: true` option prevent rapid cycles
505
+
506
+ **Signals-native pattern:**
507
+
508
+ ```typescript
509
+ // Actor → Router
510
+ createEffect(
511
+ on(
512
+ () => this.actor.currentRoute.get(),
513
+ (route) => {
514
+ if (!route || route === this.lastSyncedPath || this.isProcessingNavigation) {
515
+ return;
516
+ }
517
+ this.lastSyncedPath = route;
518
+ this.navigate(route);
519
+ },
520
+ { defer: true },
521
+ ),
522
+ );
523
+
524
+ // Router → Actor
525
+ createEffect(
526
+ on(
527
+ () => this.location.pathname,
528
+ (pathname) => {
529
+ if (pathname === this.lastSyncedPath || this.isProcessingNavigation) {
530
+ return;
531
+ }
532
+ this.isProcessingNavigation = true;
533
+ this.actor.send({ type: "play.route", to: stateId, params });
534
+ this.isProcessingNavigation = false;
535
+ },
536
+ { defer: true },
537
+ ),
538
+ );
539
+ ```
540
+
541
+ ### Relationship to Other Packages
542
+
543
+ **Package Dependencies:**
544
+
545
+ - `@xmachines/play` - Protocol interfaces (`PlayRouteEvent`, `RouterBridge`)
546
+ - `@xmachines/play-actor` - Actor base class with signal protocol
547
+ - `@xmachines/play-router` - Route extraction and pattern matching
548
+ - `@xmachines/play-signals` - TC39 Signals polyfill for reactivity
549
+ - `@xmachines/play-xstate` - XState integration via `definePlayer()`
550
+
551
+ **Architecture Layers:**
552
+
553
+ ```
554
+ ┌─────────────────────────────────────┐
555
+ │ Solid Components (View Layer) │
556
+ │ - Props include actor reference │
557
+ │ - Sends play.route events │
558
+ └─────────────────────────────────────┘
559
+
560
+ ┌─────────────────────────────────────┐
561
+ │ SolidRouterBridge (Adapter) │
562
+ │ - createEffect(actor.currentRoute) │
563
+ │ - createEffect(location.pathname) │
564
+ └─────────────────────────────────────┘
565
+ ↕ ↕
566
+ ┌─────────────┐ ┌──────────────────┐
567
+ │ SolidJS │ │ XMachines Actor │
568
+ │ Router │ │ (Business Logic) │
569
+ │ (Infra) │ │ │
570
+ └─────────────┘ └──────────────────┘
571
+ ```
572
+
573
+ ### Signals Integration (SolidJS-Specific)
574
+
575
+ **Why signals-native matters:**
576
+
577
+ - **Zero adaptation:** Solid signals and TC39 Signals share reactive primitives
578
+ - **Automatic tracking:** `createEffect(on(...))` tracks dependencies without manual Watcher setup
579
+ - **Fine-grained updates:** Only affected components re-render (not full tree)
580
+ - **Batched updates:** Solid batches multiple signal changes in single render cycle
581
+
582
+ **Hook context requirement (Pitfall 2):**
583
+
584
+ SolidJS hooks (`useNavigate`, `useLocation`, `useParams`) **MUST** be called inside component tree:
585
+
586
+ ```tsx
587
+ // ❌ WRONG: Bridge created outside component
588
+ const navigate = useNavigate(); // ERROR: No reactive context
589
+ const bridge = new SolidRouterBridge(navigate, ...);
590
+
591
+ // ✅ CORRECT: Bridge created inside component
592
+ function App() {
593
+ const navigate = useNavigate();
594
+ const location = useLocation();
595
+ const params = useParams();
596
+ const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
597
+ onCleanup(() => bridge.dispose());
598
+ return <Router>...</Router>;
599
+ }
600
+ ```
601
+
602
+ **Why:** Solid's reactivity system requires reactive ownership context. Hooks create tracked scopes that exist only within component lifecycle.
603
+
604
+ ### Pattern Matching for Dynamic Routes
605
+
606
+ **URLPattern API integration:**
607
+
608
+ ```typescript
609
+ private matchesPattern(path: string, pattern: string): boolean {
610
+ // Use URLPattern for robust matching
611
+ const urlPattern = new URLPattern({ pathname: pattern });
612
+ return urlPattern.test({ pathname: path });
613
+ }
614
+ ```
615
+
616
+ **Supported syntax:**
617
+
618
+ - `:param` - Required parameter (e.g., `/profile/:userId` matches `/profile/123`)
619
+ - `:param?` - Optional parameter (e.g., `/settings/:section?` matches `/settings` and `/settings/account`)
620
+ - Wildcards via `*` (future enhancement)
621
+
622
+ **Example:**
623
+
624
+ ```typescript
625
+ const routeMap = new RouteMap([
626
+ { stateId: "#profile", path: "/profile/:userId" },
627
+ { stateId: "#settings", path: "/settings/:section?" },
628
+ ]);
629
+
630
+ routeMap.getStateIdByPath("/profile/123"); // '#profile'
631
+ routeMap.getStateIdByPath("/settings"); // '#settings'
632
+ routeMap.getStateIdByPath("/settings/privacy"); // '#settings'
633
+ ```
634
+
635
+ ## License
636
+
637
+ MIT
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Create RouteMap helper for SolidJS 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-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 SolidJS 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-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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @xmachines/play-solid-router
3
+ *
4
+ * SolidJS 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, SolidRouterHooks, } from "./play-router-provider.js";
9
+ export { RouteMap } from "./route-map.js";
10
+ export { createRouteMap } from "./create-route-map.js";
11
+ export type { RouteMapping, PlayRouteEvent, RouterBridge, AbstractActor } from "./types.js";
12
+ //# 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,gBAAgB,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @xmachines/play-solid-router
3
+ *
4
+ * SolidJS 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,21 @@
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 type { RouteMap } from "./route-map.js";
5
+ export type RoutableActor = AbstractActor<AnyActorLogic> & Routable & Viewable;
6
+ export type SolidRouterHooks = {
7
+ navigate: ((path: string) => void) | ((path: string, ...args: unknown[]) => unknown);
8
+ location: {
9
+ pathname: string;
10
+ search: string;
11
+ };
12
+ params: Record<string, string | undefined>;
13
+ };
14
+ export interface PlayRouterProviderProps {
15
+ actor: RoutableActor;
16
+ routeMap: RouteMap;
17
+ router: SolidRouterHooks;
18
+ renderer: (actor: RoutableActor, router: SolidRouterHooks) => JSX.Element;
19
+ }
20
+ export declare function PlayRouterProvider(props: PlayRouterProviderProps): any;
21
+ //# 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;AAE5C,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,gBAAgB,GAAG;IAC9B,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;IACrF,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,KAAK,GAAG,CAAC,OAAO,CAAC;CAC1E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,OAehE"}
@@ -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.navigate, props.router.location, props.router.params, 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;AAiB7D,MAAM,UAAU,kBAAkB,CAAC,KAA8B;IAChE,MAAM,MAAM,GAAG,IAAI,iBAAiB,CACnC,KAAK,CAAC,MAAM,CAAC,QAAQ,EACrB,KAAK,CAAC,MAAM,CAAC,QAAQ,EACrB,KAAK,CAAC,MAAM,CAAC,MAAM,EACnB,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,QAAQ,CACd,CAAC;IACF,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 SolidJS 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 undefined if not found
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * routeMap.getPath('#profile'); // '/profile/:userId'
35
+ * ```
36
+ */
37
+ getPath(stateId: string): string | undefined;
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 undefined 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 | undefined;
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,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5C;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAmBlD;;;;;;;;;;;;;;;;;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 SolidJS 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 undefined if not found
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * routeMap.getPath('#profile'); // '/profile/:userId'
39
+ * ```
40
+ */
41
+ getPath(stateId) {
42
+ return this.stateToPath.get(stateId);
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 undefined 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 undefined;
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,OAAO,CAAC,OAAe;QACtB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACtC,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,CAAC,CAAC;QACxC,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,SAAS,CAAC;IAClB,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,90 @@
1
+ /**
2
+ * SolidJS Router bridge implementing RouterBridge protocol via RouterBridgeBase
3
+ *
4
+ * Extends RouterBridgeBase to handle all common lifecycle and sync logic.
5
+ * Uses Solid's native reactive primitives (createEffect) for router→actor direction.
6
+ *
7
+ * **IMPORTANT:** `connect()` MUST be called inside a Solid reactive owner (component
8
+ * or createRoot). The createEffect() in watchRouterChanges() relies on Solid's
9
+ * reactive system for automatic cleanup.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { useNavigate, useLocation, useParams } from '@solidjs/router';
14
+ * import { onCleanup } from 'solid-js';
15
+ * import { SolidRouterBridge, RouteMap } from '@xmachines/play-solid-router';
16
+ *
17
+ * function App() {
18
+ * const navigate = useNavigate();
19
+ * const location = useLocation();
20
+ * const params = useParams();
21
+ *
22
+ * const routeMap = new RouteMap([...]);
23
+ * const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
24
+ *
25
+ * // connect() MUST be called inside a Solid reactive owner
26
+ * bridge.connect();
27
+ * onCleanup(() => bridge.disconnect());
28
+ *
29
+ * return <div>...</div>;
30
+ * }
31
+ * ```
32
+ */
33
+ import { RouterBridgeBase } from "@xmachines/play-router";
34
+ import type { AbstractActor, Routable } from "@xmachines/play-actor";
35
+ import type { AnyActorLogic } from "xstate";
36
+ import type { RouteMap } from "./route-map.js";
37
+ /**
38
+ * SolidJS Router integration bridge extending RouterBridgeBase
39
+ *
40
+ * Implements RouterBridge protocol for SolidJS Router using Solid's reactive
41
+ * primitives. The actor→router direction uses TC39 Signal watcher (from base class).
42
+ * The router→actor direction uses Solid's createEffect for native reactivity.
43
+ */
44
+ export declare class SolidRouterBridge extends RouterBridgeBase {
45
+ private readonly solidNavigate;
46
+ private readonly location;
47
+ /**
48
+ * Create a SolidJS Router bridge
49
+ *
50
+ * **CRITICAL:** `connect()` must be called inside a Solid component where hooks are available.
51
+ *
52
+ * @param solidNavigate - Result of useNavigate() hook
53
+ * @param location - Result of useLocation() hook
54
+ * @param _params - Result of useParams() hook
55
+ * @param actor - XMachines actor instance
56
+ * @param routeMap - Bidirectional state ID ↔ path mapping
57
+ */
58
+ constructor(solidNavigate: ((path: string) => void) | ((path: string, ...args: unknown[]) => unknown), location: {
59
+ pathname: string;
60
+ search: string;
61
+ }, _params: Record<string, string | undefined>, actor: AbstractActor<AnyActorLogic> & Routable, routeMap: RouteMap);
62
+ /**
63
+ * Navigate SolidJS Router to the given path.
64
+ */
65
+ protected navigateRouter(path: string): void;
66
+ /**
67
+ * Subscribe to SolidJS Router location changes using createEffect.
68
+ *
69
+ * MUST be called inside a Solid reactive owner (component/createRoot).
70
+ * Solid auto-cleans createEffect subscriptions on component unmount.
71
+ */
72
+ protected watchRouterChanges(): void;
73
+ /**
74
+ * Stop watching SolidJS Router changes.
75
+ *
76
+ * Solid auto-cleans createEffect subscriptions when component unmounts.
77
+ * This sets the processing flag to prevent pending operations.
78
+ */
79
+ protected unwatchRouterChanges(): void;
80
+ /**
81
+ * Dispose the bridge (alias for disconnect).
82
+ *
83
+ * @example
84
+ * ```tsx
85
+ * onCleanup(() => bridge.dispose());
86
+ * ```
87
+ */
88
+ dispose(): void;
89
+ }
90
+ //# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,gBAAgB;IAarD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAG9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAf1B;;;;;;;;;;OAUG;gBAEe,aAAa,EAC3B,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GACxB,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,EACjC,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAC/D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,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;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAcpC;;;;;OAKG;IACH,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAMtC;;;;;;;OAOG;IACH,OAAO,IAAI,IAAI;CAGf"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * SolidJS Router bridge implementing RouterBridge protocol via RouterBridgeBase
3
+ *
4
+ * Extends RouterBridgeBase to handle all common lifecycle and sync logic.
5
+ * Uses Solid's native reactive primitives (createEffect) for router→actor direction.
6
+ *
7
+ * **IMPORTANT:** `connect()` MUST be called inside a Solid reactive owner (component
8
+ * or createRoot). The createEffect() in watchRouterChanges() relies on Solid's
9
+ * reactive system for automatic cleanup.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { useNavigate, useLocation, useParams } from '@solidjs/router';
14
+ * import { onCleanup } from 'solid-js';
15
+ * import { SolidRouterBridge, RouteMap } from '@xmachines/play-solid-router';
16
+ *
17
+ * function App() {
18
+ * const navigate = useNavigate();
19
+ * const location = useLocation();
20
+ * const params = useParams();
21
+ *
22
+ * const routeMap = new RouteMap([...]);
23
+ * const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
24
+ *
25
+ * // connect() MUST be called inside a Solid reactive owner
26
+ * bridge.connect();
27
+ * onCleanup(() => bridge.disconnect());
28
+ *
29
+ * return <div>...</div>;
30
+ * }
31
+ * ```
32
+ */
33
+ import { createEffect, on } from "solid-js";
34
+ import { RouterBridgeBase } from "@xmachines/play-router";
35
+ /**
36
+ * SolidJS Router integration bridge extending RouterBridgeBase
37
+ *
38
+ * Implements RouterBridge protocol for SolidJS Router using Solid's reactive
39
+ * primitives. The actor→router direction uses TC39 Signal watcher (from base class).
40
+ * The router→actor direction uses Solid's createEffect for native reactivity.
41
+ */
42
+ export class SolidRouterBridge extends RouterBridgeBase {
43
+ solidNavigate;
44
+ location;
45
+ /**
46
+ * Create a SolidJS Router bridge
47
+ *
48
+ * **CRITICAL:** `connect()` must be called inside a Solid component where hooks are available.
49
+ *
50
+ * @param solidNavigate - Result of useNavigate() hook
51
+ * @param location - Result of useLocation() hook
52
+ * @param _params - Result of useParams() hook
53
+ * @param actor - XMachines actor instance
54
+ * @param routeMap - Bidirectional state ID ↔ path mapping
55
+ */
56
+ constructor(solidNavigate, location, _params, actor, routeMap) {
57
+ super(actor, {
58
+ getStateIdByPath: (path) => routeMap.getStateIdByPath(path),
59
+ getPathByStateId: (id) => routeMap.getPath(id),
60
+ });
61
+ this.solidNavigate = solidNavigate;
62
+ this.location = location;
63
+ }
64
+ /**
65
+ * Navigate SolidJS Router to the given path.
66
+ */
67
+ navigateRouter(path) {
68
+ this.solidNavigate(path);
69
+ }
70
+ /**
71
+ * Subscribe to SolidJS Router location changes using createEffect.
72
+ *
73
+ * MUST be called inside a Solid reactive owner (component/createRoot).
74
+ * Solid auto-cleans createEffect subscriptions on component unmount.
75
+ */
76
+ watchRouterChanges() {
77
+ // MUST be called inside Solid reactive owner (component or createRoot)
78
+ // createEffect in watchRouterChanges() relies on Solid's reactive system
79
+ createEffect(on(() => this.location.pathname, (pathname) => {
80
+ const search = this.location.search ?? "";
81
+ this.syncActorFromRouter(pathname, search);
82
+ }));
83
+ }
84
+ /**
85
+ * Stop watching SolidJS Router changes.
86
+ *
87
+ * Solid auto-cleans createEffect subscriptions when component unmounts.
88
+ * This sets the processing flag to prevent pending operations.
89
+ */
90
+ unwatchRouterChanges() {
91
+ // Solid auto-cleans createEffect on component unmount
92
+ // Set flag to prevent any pending operations
93
+ this.isProcessingNavigation = true;
94
+ }
95
+ /**
96
+ * Dispose the bridge (alias for disconnect).
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * onCleanup(() => bridge.dispose());
101
+ * ```
102
+ */
103
+ dispose() {
104
+ this.disconnect();
105
+ }
106
+ }
107
+ //# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D;;;;;;GAMG;AACH,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;IAapC;IAGA;IAflB;;;;;;;;;;OAUG;IACH,YACkB,aAEiC,EACjC,QAA8C,EAC/D,OAA2C,EAC3C,KAA8C,EAC9C,QAAkB;QAElB,KAAK,CAAC,KAAK,EAAE;YACZ,gBAAgB,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC;YACnE,gBAAgB,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;SACtD,CAAC,CAAC;QAXc,kBAAa,GAAb,aAAa,CAEoB;QACjC,aAAQ,GAAR,QAAQ,CAAsC;IAShE,CAAC;IAED;;OAEG;IACO,cAAc,CAAC,IAAY;QACpC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED;;;;;OAKG;IACO,kBAAkB;QAC3B,uEAAuE;QACvE,yEAAyE;QACzE,YAAY,CACX,EAAE,CACD,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAC5B,CAAC,QAAgB,EAAE,EAAE;YACpB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;YAC1C,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC,CACD,CACD,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,oBAAoB;QAC7B,sDAAsD;QACtD,6CAA6C;QAC7C,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;IACpC,CAAC;IAED;;;;;;;OAOG;IACH,OAAO;QACN,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;CACD"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Type definitions for @xmachines/play-solid-router
3
+ */
4
+ /**
5
+ * Mapping between state ID and URL path for SolidJS Router
6
+ */
7
+ export interface RouteMapping {
8
+ /**
9
+ * XMachines state ID with # prefix
10
+ * @example '#home', '#profile'
11
+ */
12
+ readonly stateId: string;
13
+ /**
14
+ * SolidJS Router path pattern
15
+ * @example '/', '/profile/:userId', '/settings/:section?'
16
+ */
17
+ readonly path: string;
18
+ }
19
+ export type { PlayRouteEvent, RouterBridge } from "@xmachines/play-router";
20
+ export type { AbstractActor } from "@xmachines/play-actor";
21
+ //# 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;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAGD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type definitions for @xmachines/play-solid-router
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -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-solid-router",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "SolidJS 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-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/router": "^0.15.4",
41
+ "@solidjs/testing-library": "^0.8.0",
42
+ "solid-js": "^1.8.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@solidjs/router": "^0.15.4",
46
+ "solid-js": "^1.8.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }