@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 +637 -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 +12 -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 +21 -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 +90 -0
- package/dist/solid-router-bridge.d.ts.map +1 -0
- package/dist/solid-router-bridge.js +107 -0
- package/dist/solid-router-bridge.js.map +1 -0
- package/dist/types.d.ts +21 -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,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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|