@xmachines/play-solid-router 1.0.0-beta.4 → 1.0.0-beta.40

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mikael Karon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -8,7 +8,7 @@ SolidJS Router adapter using `RouterBridgeBase` for consistent actor↔router sy
8
8
 
9
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
10
 
11
- Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
11
+ Per [Play RFC](../docs/rfc/play.md), this package implements:
12
12
 
13
13
  - **Actor Authority (INV-01):** State machine controls navigation, router reflects decisions
14
14
  - **Passive Infrastructure (INV-04):** Router observes `actor.currentRoute` signal
@@ -36,12 +36,12 @@ npm install @solidjs/router@^0.13.0 solid-js@^1.8.0 @xmachines/play-solid-router
36
36
 
37
37
  **Peer dependencies:**
38
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
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 primitives
45
45
 
46
46
  ## Quick Start
47
47
 
@@ -126,51 +126,57 @@ class SolidRouterBridge {
126
126
 
127
127
  **Methods:**
128
128
 
129
- - `connect()` - Start bidirectional synchronization.
129
+ - `connect()` - Start bidirectional synchronization. Both `location.pathname` and `location.search` are forwarded to the actor on first connect, so users arriving on `/profile?tab=posts` have `query: { tab: "posts" }` in the initial `play.route` event.
130
130
  - `disconnect()` - Stop synchronization and cleanup bridge resources.
131
131
  - `dispose()` - Alias of `disconnect()`.
132
132
 
133
+ `connect()` creates a Solid-owned router watcher, and `disconnect()`/`dispose()` tears it down immediately. Do not rely on owner unmount alone if you need the bridge to stop processing router updates earlier.
134
+
133
135
  **Internal Behavior:**
134
136
 
135
137
  - Uses `RouterBridgeBase` TC39 watcher lifecycle for actor→router synchronization
136
138
  - Updates SolidJS Router via `navigate(path)` when actor state changes
137
139
  - Uses `createEffect(on(...))` to watch `location.pathname` signal
138
- - Sends `play.route` events to actor when user navigates
140
+ - Sends `play.route` events to actor when user navigates, using Solid's pre-parsed `useParams()` for path parameter extraction (no URLPattern polyfill required)
139
141
  - Prevents circular updates with path tracking and processing flags
140
142
 
141
143
  ### `RouteMap`
142
144
 
143
145
  Bidirectional mapping between XMachines state IDs and SolidJS Router paths with pattern matching support.
144
146
 
145
- **Type Signature:**
147
+ `RouteMap` extends `BaseRouteMap` from `@xmachines/play-router`, inheriting bucket-indexed
148
+ bidirectional route matching. No routing logic lives in the adapter itself.
146
149
 
147
150
  ```typescript
148
151
  interface RouteMapping {
149
- stateId: string;
150
- path: string;
152
+ readonly stateId: string;
153
+ readonly path: string;
151
154
  }
152
155
 
153
- class RouteMap {
154
- constructor(mappings: RouteMapping[]);
155
- getPath(stateId: string, params?: Record<string, string>): string | undefined;
156
- getStateIdByPath(path: string): string | undefined;
157
- }
156
+ // RouteMap is a thin subclass of BaseRouteMap — no extra methods
157
+ class RouteMap extends BaseRouteMap {}
158
+
159
+ // Inherited API:
160
+ routeMap.getStateIdByPath(path: string): string | null
161
+ routeMap.getPathByStateId(stateId: string): string | null
158
162
  ```
159
163
 
164
+ `getStateIdByPath` and `getPathByStateId` both return `null` (not `undefined`) for misses.
165
+
160
166
  **Constructor Parameters:**
161
167
 
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'`)
168
+ - `mappings` - Array of `{ stateId, path }` entries:
169
+ - `stateId` State machine state ID (e.g., `'#profile'`)
170
+ - `path` SolidJS Router path pattern (e.g., `'/profile/:userId'`)
165
171
 
166
172
  **Methods:**
167
173
 
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)
174
+ - `getPathByStateId(stateId)` Find path pattern from state ID
175
+ - `getStateIdByPath(path)` Find state ID from path with pattern matching (supports `:param` and `:param?` syntax)
170
176
 
171
177
  **Pattern Matching:**
172
178
 
173
- Uses URLPattern API for robust dynamic route matching:
179
+ Uses bucket-indexed RegExp matching for dynamic routes:
174
180
 
175
181
  ```typescript
176
182
  const routeMap = new RouteMap([{ stateId: "#settings", path: "/settings/:section?" }]);
@@ -178,6 +184,7 @@ const routeMap = new RouteMap([{ stateId: "#settings", path: "/settings/:section
178
184
  routeMap.getStateIdByPath("/settings"); // '#settings'
179
185
  routeMap.getStateIdByPath("/settings/account"); // '#settings'
180
186
  routeMap.getStateIdByPath("/settings/privacy"); // '#settings'
187
+ routeMap.getStateIdByPath("/other"); // null
181
188
  ```
182
189
 
183
190
  ## Examples
@@ -186,8 +193,20 @@ routeMap.getStateIdByPath("/settings/privacy"); // '#settings'
186
193
 
187
194
  ```typescript
188
195
  import { Router, Route } from '@solidjs/router';
189
- import { createSignal } from 'solid-js';
190
- import { defineCatalog } from '@xmachines/play-catalog';
196
+ import { defineCatalog } from "@json-render/core";
197
+ import { schema } from "@json-render/react/schema";
198
+ import { defineRegistry } from "@xmachines/play-solid";
199
+ import { z } from "zod";
200
+
201
+ // Catalog and registry
202
+ const appCatalog = defineCatalog(schema, {
203
+ components: {
204
+ Home: { props: z.object({}) },
205
+ About: { props: z.object({}) },
206
+ Contact: { props: z.object({}) },
207
+ },
208
+ });
209
+ const registry = defineRegistry(appCatalog, { components: { Home, About, Contact } });
191
210
 
192
211
  // State machine with 3 states
193
212
  const appMachine = setup({
@@ -210,12 +229,6 @@ const appMachine = setup({
210
229
  }
211
230
  });
212
231
 
213
- const catalog = defineCatalog({
214
- Home,
215
- About,
216
- Contact,
217
- });
218
-
219
232
  // Component setup
220
233
  function App() {
221
234
  const navigate = useNavigate();
@@ -224,7 +237,7 @@ function App() {
224
237
 
225
238
  const routeMap = createRouteMap(appMachine);
226
239
 
227
- const createPlayer = definePlayer({ machine: appMachine, catalog });
240
+ const createPlayer = definePlayer({ machine: appMachine, catalog: appCatalog });
228
241
  const actor = createPlayer();
229
242
  actor.start();
230
243
  const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
@@ -246,7 +259,18 @@ function App() {
246
259
  ```typescript
247
260
  // State machine with parameter routes
248
261
  import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
249
- import { defineCatalog } from "@xmachines/play-catalog";
262
+ import { defineCatalog } from "@json-render/core";
263
+ import { schema } from "@json-render/react/schema";
264
+ import { defineRegistry } from "@xmachines/play-solid";
265
+ import { z } from "zod";
266
+
267
+ const appCatalog = defineCatalog(schema, {
268
+ components: {
269
+ Profile: { props: z.object({ userId: z.string() }) },
270
+ Settings: { props: z.object({ section: z.string().optional() }) },
271
+ },
272
+ });
273
+ const registry = defineRegistry(appCatalog, { components: { Profile, Settings } });
250
274
 
251
275
  const machineConfig = {
252
276
  id: 'app',
@@ -274,11 +298,6 @@ const appMachine = setup({
274
298
  }
275
299
  }).createMachine(formatPlayRouteTransitions(machineConfig));
276
300
 
277
- const catalog = defineCatalog({
278
- Profile,
279
- Settings,
280
- });
281
-
282
301
  // Router with dynamic routes
283
302
  function App() {
284
303
  const navigate = useNavigate();
@@ -287,7 +306,7 @@ function App() {
287
306
 
288
307
  const routeMap = createRouteMap(appMachine);
289
308
 
290
- const createPlayer = definePlayer({ machine: appMachine, catalog });
309
+ const createPlayer = definePlayer({ machine: appMachine, catalog: appCatalog });
291
310
  const actor = createPlayer();
292
311
  actor.start();
293
312
  const bridge = new SolidRouterBridge(navigate, location, params, actor, routeMap);
@@ -328,7 +347,17 @@ function ProfileButton(props: { userId: string }) {
328
347
  ```typescript
329
348
  // State machine with query param handling
330
349
  import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
331
- import { defineCatalog } from "@xmachines/play-catalog";
350
+ import { defineCatalog } from "@json-render/core";
351
+ import { schema } from "@json-render/react/schema";
352
+ import { defineRegistry } from "@xmachines/play-solid";
353
+ import { z } from "zod";
354
+
355
+ const searchCatalog = defineCatalog(schema, {
356
+ components: {
357
+ Search: { props: z.object({ query: z.string().optional() }) },
358
+ },
359
+ });
360
+ const registry = defineRegistry(searchCatalog, { components: { Search } });
332
361
 
333
362
  const machineConfig = {
334
363
  context: { query: '', filters: {} },
@@ -349,11 +378,7 @@ const searchMachine = setup({
349
378
  }
350
379
  }).createMachine(formatPlayRouteTransitions(machineConfig));
351
380
 
352
- const catalog = defineCatalog({
353
- Search,
354
- });
355
-
356
- const player = definePlayer({ machine: searchMachine, catalog });
381
+ const player = definePlayer({ machine: searchMachine, catalog: searchCatalog });
357
382
 
358
383
  // Component sends query params
359
384
  function SearchBar(props) {
@@ -387,7 +412,19 @@ function SearchBar(props) {
387
412
 
388
413
  ```typescript
389
414
  // State machine with auth guards
390
- import { defineCatalog } from "@xmachines/play-catalog";
415
+ import { defineCatalog } from "@json-render/core";
416
+ import { schema } from "@json-render/react/schema";
417
+ import { defineRegistry } from "@xmachines/play-solid";
418
+ import { z } from "zod";
419
+
420
+ const authCatalog = defineCatalog(schema, {
421
+ components: {
422
+ Home: { props: z.object({}) },
423
+ Login: { props: z.object({ title: z.string() }) },
424
+ Dashboard: { props: z.object({ username: z.string() }) },
425
+ },
426
+ });
427
+ const registry = defineRegistry(authCatalog, { components: { Home, Login, Dashboard } });
391
428
 
392
429
  const authMachine = setup({
393
430
  types: {
@@ -420,13 +457,7 @@ const authMachine = setup({
420
457
  },
421
458
  });
422
459
 
423
- const catalog = defineCatalog({
424
- Home,
425
- Login,
426
- Dashboard,
427
- });
428
-
429
- const player = definePlayer({ machine: authMachine, catalog });
460
+ const player = definePlayer({ machine: authMachine, catalog: authCatalog });
430
461
  ```
431
462
 
432
463
  **Guard behavior:**
@@ -479,7 +510,7 @@ function App() {
479
510
  2. `actor.currentRoute` signal updates
480
511
  3. `createEffect(on(...))` fires with new route value
481
512
  4. Bridge extracts state ID from signal
482
- 5. Bridge looks up path via `routeMap.getPath(stateId, params)`
513
+ 5. Bridge looks up path via `routeMap.getPathByStateId(stateId)`
483
514
  6. Bridge calls `navigate(path)`
484
515
  7. SolidJS Router updates URL and renders component
485
516
 
@@ -603,15 +634,11 @@ function App() {
603
634
 
604
635
  ### Pattern Matching for Dynamic Routes
605
636
 
606
- **URLPattern API integration:**
637
+ **Bucket-indexed matching via `BaseRouteMap`:**
607
638
 
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
- ```
639
+ Routes are grouped by their first path segment into buckets. On each lookup only the
640
+ relevant bucket (plus the wildcard `*` bucket for `:param`-first routes) is scanned —
641
+ typically far fewer than all registered routes.
615
642
 
616
643
  **Supported syntax:**
617
644
 
@@ -634,4 +661,7 @@ routeMap.getStateIdByPath("/settings/privacy"); // '#settings'
634
661
 
635
662
  ## License
636
663
 
637
- MIT
664
+ Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
665
+
666
+ This work is licensed under the terms of the MIT license.
667
+ For a copy, see <https://opensource.org/licenses/MIT>.
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  export { SolidRouterBridge } from "./solid-router-bridge.js";
7
7
  export { PlayRouterProvider } from "./play-router-provider.js";
8
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";
9
+ export { RouteMap, createRouteMapFromMachine as createRouteMap, type RouteMapping, } from "@xmachines/play-router";
10
+ export type { PlayRouteEvent, RouterBridge } from "./types.js";
11
+ export type { AbstractActor } from "@xmachines/play-actor";
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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,EACN,QAAQ,EACR,yBAAyB,IAAI,cAAc,EAC3C,KAAK,YAAY,GACjB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,5 @@
5
5
  */
6
6
  export { SolidRouterBridge } from "./solid-router-bridge.js";
7
7
  export { PlayRouterProvider } from "./play-router-provider.js";
8
- export { RouteMap } from "./route-map.js";
9
- export { createRouteMap } from "./create-route-map.js";
8
+ export { RouteMap, createRouteMapFromMachine as createRouteMap, } from "@xmachines/play-router";
10
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"}
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,EACN,QAAQ,EACR,yBAAyB,IAAI,cAAc,GAE3C,MAAM,wBAAwB,CAAC"}
@@ -1,21 +1,72 @@
1
1
  import { type JSX } from "solid-js";
2
+ import type { Navigator, Location, Params } from "@solidjs/router";
2
3
  import type { AbstractActor, Routable, Viewable } from "@xmachines/play-actor";
3
4
  import type { AnyActorLogic } from "xstate";
4
- import type { RouteMap } from "./route-map.js";
5
+ import type { RouteMap } from "@xmachines/play-router";
6
+ /** Minimum actor shape accepted by PlayRouterProvider. */
5
7
  export type RoutableActor = AbstractActor<AnyActorLogic> & Routable & Viewable;
8
+ /**
9
+ * The three Solid Router hook results that `PlayRouterProvider` and `SolidRouterBridge`
10
+ * require. Pass these directly from your component's hook calls:
11
+ *
12
+ * ```tsx
13
+ * const navigate = useNavigate(); // → SolidRouterHooks.navigate
14
+ * const location = useLocation(); // → SolidRouterHooks.location
15
+ * const params = useParams(); // → SolidRouterHooks.params
16
+ * ```
17
+ *
18
+ * - `navigate` — used to push URL changes when the actor's `currentRoute` changes.
19
+ * - `location` — `pathname` and `search` are read at `connect()` time for deep-link sync.
20
+ * Subsequent pathname changes drive router→actor sync via `createEffect`.
21
+ * - `params` — Solid's pre-parsed path parameters for the current route segment. Used
22
+ * directly in `extractParams()` to avoid re-parsing with URLPattern.
23
+ */
6
24
  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>;
25
+ navigate: Navigator;
26
+ location: Location;
27
+ params: Params;
13
28
  };
14
- export interface PlayRouterProviderProps {
15
- actor: RoutableActor;
29
+ export interface PlayRouterProviderProps<TActor extends RoutableActor = RoutableActor> {
30
+ /** The actor to sync with Solid Router. */
31
+ actor: TActor;
32
+ /** Bidirectional route map for state ID ↔ URL path lookups. */
16
33
  routeMap: RouteMap;
34
+ /**
35
+ * The three Solid Router hook results that drive bidirectional sync.
36
+ * Obtain these from `useNavigate()`, `useLocation()`, and `useParams()`
37
+ * in the parent component (they must be called inside a router context).
38
+ */
17
39
  router: SolidRouterHooks;
18
- renderer: (actor: RoutableActor, router: SolidRouterHooks) => JSX.Element;
40
+ /** Renderer callback receives the same concrete actor type that was passed in. */
41
+ renderer: (actor: TActor, router: SolidRouterHooks) => JSX.Element;
19
42
  }
20
- export declare function PlayRouterProvider(props: PlayRouterProviderProps): any;
43
+ /**
44
+ * Connects a `PlayerActor` to Solid Router, keeping actor state and browser URL
45
+ * in sync bidirectionally.
46
+ *
47
+ * The bridge is created synchronously at component evaluation time (Solid's
48
+ * execution model) and torn down via `onCleanup` when the component is disposed.
49
+ * Unlike React, prop stability is not a concern — Solid's `props` accessor is
50
+ * already reactive and the bridge is created once per component instance.
51
+ *
52
+ * The `router` prop must be obtained from Solid Router hooks in the parent component:
53
+ *
54
+ * ```tsx
55
+ * function AppShell() {
56
+ * const navigate = useNavigate();
57
+ * const location = useLocation();
58
+ * const params = useParams();
59
+ *
60
+ * return (
61
+ * <PlayRouterProvider
62
+ * actor={actor}
63
+ * routeMap={routeMap}
64
+ * router={{ navigate, location, params }}
65
+ * renderer={(a) => <PlayRenderer actor={a} registry={registry} />}
66
+ * />
67
+ * );
68
+ * }
69
+ * ```
70
+ */
71
+ export declare function PlayRouterProvider<TActor extends RoutableActor>(props: PlayRouterProviderProps<TActor>): any;
21
72
  //# sourceMappingURL=play-router-provider.d.ts.map
@@ -1 +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"}
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,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACnE,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,wBAAwB,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,WAAW,uBAAuB,CAAC,MAAM,SAAS,aAAa,GAAG,aAAa;IACpF,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,QAAQ,EAAE,QAAQ,CAAC;IACnB;;;;OAIG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB,kFAAkF;IAClF,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,KAAK,GAAG,CAAC,OAAO,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,SAAS,aAAa,EAC9D,KAAK,EAAE,uBAAuB,CAAC,MAAM,CAAC,OAgBtC"}
@@ -1,5 +1,33 @@
1
1
  import { onCleanup } from "solid-js";
2
2
  import { SolidRouterBridge } from "./solid-router-bridge.js";
3
+ /**
4
+ * Connects a `PlayerActor` to Solid Router, keeping actor state and browser URL
5
+ * in sync bidirectionally.
6
+ *
7
+ * The bridge is created synchronously at component evaluation time (Solid's
8
+ * execution model) and torn down via `onCleanup` when the component is disposed.
9
+ * Unlike React, prop stability is not a concern — Solid's `props` accessor is
10
+ * already reactive and the bridge is created once per component instance.
11
+ *
12
+ * The `router` prop must be obtained from Solid Router hooks in the parent component:
13
+ *
14
+ * ```tsx
15
+ * function AppShell() {
16
+ * const navigate = useNavigate();
17
+ * const location = useLocation();
18
+ * const params = useParams();
19
+ *
20
+ * return (
21
+ * <PlayRouterProvider
22
+ * actor={actor}
23
+ * routeMap={routeMap}
24
+ * router={{ navigate, location, params }}
25
+ * renderer={(a) => <PlayRenderer actor={a} registry={registry} />}
26
+ * />
27
+ * );
28
+ * }
29
+ * ```
30
+ */
3
31
  export function PlayRouterProvider(props) {
4
32
  const bridge = new SolidRouterBridge(props.router.navigate, props.router.location, props.router.params, props.actor, props.routeMap);
5
33
  bridge.connect();
@@ -1 +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"}
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;AAI/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AA2C7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,kBAAkB,CACjC,KAAsC;IAEtC,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"}
@@ -5,8 +5,10 @@
5
5
  * Uses Solid's native reactive primitives (createEffect) for router→actor direction.
6
6
  *
7
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.
8
+ * or createRoot). The `createEffect()` in `watchRouterChanges()` runs inside
9
+ * `createRoot()`, which deliberately isolates it from any parent owner — automatic
10
+ * cleanup on component unmount does NOT happen. You MUST call `disconnect()` (or
11
+ * `dispose()`) explicitly, typically in `onCleanup()`.
10
12
  *
11
13
  * @example
12
14
  * ```tsx
@@ -30,20 +32,32 @@
30
32
  * }
31
33
  * ```
32
34
  */
35
+ import type { Navigator, Params } from "@solidjs/router";
33
36
  import { RouterBridgeBase } from "@xmachines/play-router";
37
+ import type { LocationLike } from "@xmachines/play-router";
34
38
  import type { AbstractActor, Routable } from "@xmachines/play-actor";
35
39
  import type { AnyActorLogic } from "xstate";
36
- import type { RouteMap } from "./route-map.js";
40
+ import type { RouteMap } from "@xmachines/play-router";
37
41
  /**
38
42
  * SolidJS Router integration bridge extending RouterBridgeBase
39
43
  *
40
44
  * Implements RouterBridge protocol for SolidJS Router using Solid's reactive
41
45
  * primitives. The actor→router direction uses TC39 Signal watcher (from base class).
42
46
  * The router→actor direction uses Solid's createEffect for native reactivity.
47
+ *
48
+ * Path parameters are extracted from Solid's `useParams()` reactive proxy rather than
49
+ * re-parsing the URL with URLPattern. This means parameterized routes work without the
50
+ * URLPattern polyfill — Solid's router has already extracted the values.
43
51
  */
44
52
  export declare class SolidRouterBridge extends RouterBridgeBase {
45
53
  private readonly solidNavigate;
46
54
  private readonly location;
55
+ private disposeRouterWatcher;
56
+ /**
57
+ * Live reactive params object from Solid's `useParams()`.
58
+ * Read inside the createEffect callback so it always reflects the current route.
59
+ */
60
+ private readonly solidParams;
47
61
  /**
48
62
  * Create a SolidJS Router bridge
49
63
  *
@@ -51,14 +65,28 @@ export declare class SolidRouterBridge extends RouterBridgeBase {
51
65
  *
52
66
  * @param solidNavigate - Result of useNavigate() hook
53
67
  * @param location - Result of useLocation() hook
54
- * @param _params - Result of useParams() hook
68
+ * @param params - Result of useParams() hook — used directly for path parameter extraction,
69
+ * avoiding the URLPattern polyfill requirement for parameterized routes
55
70
  * @param actor - XMachines actor instance
56
71
  * @param routeMap - Bidirectional state ID ↔ path mapping
57
72
  */
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);
73
+ constructor(solidNavigate: Navigator, location: LocationLike, params: Params, actor: AbstractActor<AnyActorLogic> & Routable, routeMap: RouteMap);
74
+ /**
75
+ * Extract path parameters using Solid's pre-parsed `useParams()` values.
76
+ *
77
+ * Solid's router has already extracted all named parameters for the matched route
78
+ * segment. Reading `this.solidParams` inside the createEffect callback that drives
79
+ * `syncActorFromRouter` is safe — the reactive proxy always reflects the current
80
+ * route at the time the effect runs.
81
+ *
82
+ * Falls back to URLPattern-based extraction (base class) only when Solid provided
83
+ * no params for this route (i.e. the route has no `:param` segments).
84
+ *
85
+ * @param pathname - The actual URL path (unused — params already extracted by Solid)
86
+ * @param stateId - The matched state ID (unused — params already extracted by Solid)
87
+ * @returns Normalized path parameters with undefined/empty values filtered out
88
+ */
89
+ protected extractParams(pathname: string, stateId: string): Record<string, string>;
62
90
  /**
63
91
  * Navigate SolidJS Router to the given path.
64
92
  */
@@ -67,18 +95,32 @@ export declare class SolidRouterBridge extends RouterBridgeBase {
67
95
  * Get the current router pathname for initial URL -> actor sync on connect.
68
96
  */
69
97
  protected getInitialRouterPath(): string | null;
98
+ /**
99
+ * Return the initial URL search string for query-param forwarding on `connect()`.
100
+ *
101
+ * Reads `this.location.search` from Solid's `useLocation()` reactive object —
102
+ * the same source used by `getInitialRouterPath()`. An empty string (no query
103
+ * params) returns `undefined` so `syncActorFromRouter` produces `query: {}`.
104
+ */
105
+ protected getInitialRouterSearch(): string | undefined;
70
106
  /**
71
107
  * Subscribe to SolidJS Router location changes using createEffect.
72
108
  *
73
- * MUST be called inside a Solid reactive owner (component/createRoot).
74
- * Solid auto-cleans createEffect subscriptions on component unmount.
109
+ * MUST be called inside a Solid reactive owner (component or createRoot).
110
+ *
111
+ * The effect runs inside `createRoot()` to give it a stable owner independent
112
+ * of the calling component's lifecycle — this prevents the effect from being
113
+ * disposed if the component re-renders while the bridge should stay active.
114
+ * The trade-off is that component unmount does NOT automatically clean up the
115
+ * effect; `disconnect()` (or `dispose()`) MUST be called explicitly to avoid a leak.
75
116
  */
76
117
  protected watchRouterChanges(): void;
77
118
  /**
78
119
  * Stop watching SolidJS Router changes.
79
120
  *
80
- * Solid auto-cleans createEffect subscriptions when component unmounts.
81
- * This sets the processing flag to prevent pending operations.
121
+ * Calls the `dispose` function returned by `createRoot()` in `watchRouterChanges()`,
122
+ * tearing down the reactive effect and freeing the isolated owner. This is the only
123
+ * cleanup path — component unmount does NOT trigger this automatically.
82
124
  */
83
125
  protected unwatchRouterChanges(): void;
84
126
  /**
@@ -1 +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;;OAEG;cACgB,oBAAoB,IAAI,MAAM,GAAG,IAAI;IAIxD;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAcpC;;;;;OAKG;IACH,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAMtC;;;;;;;OAOG;IACH,OAAO,IAAI,IAAI;CAGf"}
1
+ {"version":3,"file":"solid-router-bridge.d.ts","sourceRoot":"","sources":["../src/solid-router-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,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,wBAAwB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,qBAAa,iBAAkB,SAAQ,gBAAgB;IAsBrD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAtB1B,OAAO,CAAC,oBAAoB,CAA6B;IAEzD;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC;;;;;;;;;;;OAWG;gBAEe,aAAa,EAAE,SAAS,EACxB,QAAQ,EAAE,YAAY,EACvC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,EAC9C,QAAQ,EAAE,QAAQ;IASnB;;;;;;;;;;;;;;OAcG;cACgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAY3F;;OAEG;IACH,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI5C;;OAEG;cACgB,oBAAoB,IAAI,MAAM,GAAG,IAAI;IAIxD;;;;;;OAMG;cACgB,sBAAsB,IAAI,MAAM,GAAG,SAAS;IAI/D;;;;;;;;;;OAUG;IACH,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAepC;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAKtC;;;;;;;OAOG;IACH,OAAO,IAAI,IAAI;CAGf"}
@@ -5,8 +5,10 @@
5
5
  * Uses Solid's native reactive primitives (createEffect) for router→actor direction.
6
6
  *
7
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.
8
+ * or createRoot). The `createEffect()` in `watchRouterChanges()` runs inside
9
+ * `createRoot()`, which deliberately isolates it from any parent owner — automatic
10
+ * cleanup on component unmount does NOT happen. You MUST call `disconnect()` (or
11
+ * `dispose()`) explicitly, typically in `onCleanup()`.
10
12
  *
11
13
  * @example
12
14
  * ```tsx
@@ -30,7 +32,7 @@
30
32
  * }
31
33
  * ```
32
34
  */
33
- import { createEffect, on } from "solid-js";
35
+ import { createEffect, createRoot, on } from "solid-js";
34
36
  import { RouterBridgeBase } from "@xmachines/play-router";
35
37
  /**
36
38
  * SolidJS Router integration bridge extending RouterBridgeBase
@@ -38,10 +40,20 @@ import { RouterBridgeBase } from "@xmachines/play-router";
38
40
  * Implements RouterBridge protocol for SolidJS Router using Solid's reactive
39
41
  * primitives. The actor→router direction uses TC39 Signal watcher (from base class).
40
42
  * The router→actor direction uses Solid's createEffect for native reactivity.
43
+ *
44
+ * Path parameters are extracted from Solid's `useParams()` reactive proxy rather than
45
+ * re-parsing the URL with URLPattern. This means parameterized routes work without the
46
+ * URLPattern polyfill — Solid's router has already extracted the values.
41
47
  */
42
48
  export class SolidRouterBridge extends RouterBridgeBase {
43
49
  solidNavigate;
44
50
  location;
51
+ disposeRouterWatcher = null;
52
+ /**
53
+ * Live reactive params object from Solid's `useParams()`.
54
+ * Read inside the createEffect callback so it always reflects the current route.
55
+ */
56
+ solidParams;
45
57
  /**
46
58
  * Create a SolidJS Router bridge
47
59
  *
@@ -49,17 +61,42 @@ export class SolidRouterBridge extends RouterBridgeBase {
49
61
  *
50
62
  * @param solidNavigate - Result of useNavigate() hook
51
63
  * @param location - Result of useLocation() hook
52
- * @param _params - Result of useParams() hook
64
+ * @param params - Result of useParams() hook — used directly for path parameter extraction,
65
+ * avoiding the URLPattern polyfill requirement for parameterized routes
53
66
  * @param actor - XMachines actor instance
54
67
  * @param routeMap - Bidirectional state ID ↔ path mapping
55
68
  */
56
- constructor(solidNavigate, location, _params, actor, routeMap) {
69
+ constructor(solidNavigate, location, params, actor, routeMap) {
57
70
  super(actor, {
58
71
  getStateIdByPath: (path) => routeMap.getStateIdByPath(path),
59
- getPathByStateId: (id) => routeMap.getPath(id),
72
+ getPathByStateId: (id) => routeMap.getPathByStateId(id),
60
73
  });
61
74
  this.solidNavigate = solidNavigate;
62
75
  this.location = location;
76
+ this.solidParams = params;
77
+ }
78
+ /**
79
+ * Extract path parameters using Solid's pre-parsed `useParams()` values.
80
+ *
81
+ * Solid's router has already extracted all named parameters for the matched route
82
+ * segment. Reading `this.solidParams` inside the createEffect callback that drives
83
+ * `syncActorFromRouter` is safe — the reactive proxy always reflects the current
84
+ * route at the time the effect runs.
85
+ *
86
+ * Falls back to URLPattern-based extraction (base class) only when Solid provided
87
+ * no params for this route (i.e. the route has no `:param` segments).
88
+ *
89
+ * @param pathname - The actual URL path (unused — params already extracted by Solid)
90
+ * @param stateId - The matched state ID (unused — params already extracted by Solid)
91
+ * @returns Normalized path parameters with undefined/empty values filtered out
92
+ */
93
+ extractParams(pathname, stateId) {
94
+ const entries = Object.entries(this.solidParams).filter((entry) => entry[1] !== undefined && entry[1] !== null && entry[1] !== "");
95
+ if (entries.length > 0) {
96
+ return Object.fromEntries(entries);
97
+ }
98
+ // No params from Solid — fall back to URLPattern for routes with no segments
99
+ return super.extractParams(pathname, stateId);
63
100
  }
64
101
  /**
65
102
  * Navigate SolidJS Router to the given path.
@@ -73,30 +110,46 @@ export class SolidRouterBridge extends RouterBridgeBase {
73
110
  getInitialRouterPath() {
74
111
  return this.location.pathname ?? null;
75
112
  }
113
+ /**
114
+ * Return the initial URL search string for query-param forwarding on `connect()`.
115
+ *
116
+ * Reads `this.location.search` from Solid's `useLocation()` reactive object —
117
+ * the same source used by `getInitialRouterPath()`. An empty string (no query
118
+ * params) returns `undefined` so `syncActorFromRouter` produces `query: {}`.
119
+ */
120
+ getInitialRouterSearch() {
121
+ return this.location.search || undefined;
122
+ }
76
123
  /**
77
124
  * Subscribe to SolidJS Router location changes using createEffect.
78
125
  *
79
- * MUST be called inside a Solid reactive owner (component/createRoot).
80
- * Solid auto-cleans createEffect subscriptions on component unmount.
126
+ * MUST be called inside a Solid reactive owner (component or createRoot).
127
+ *
128
+ * The effect runs inside `createRoot()` to give it a stable owner independent
129
+ * of the calling component's lifecycle — this prevents the effect from being
130
+ * disposed if the component re-renders while the bridge should stay active.
131
+ * The trade-off is that component unmount does NOT automatically clean up the
132
+ * effect; `disconnect()` (or `dispose()`) MUST be called explicitly to avoid a leak.
81
133
  */
82
134
  watchRouterChanges() {
83
- // MUST be called inside Solid reactive owner (component or createRoot)
84
- // createEffect in watchRouterChanges() relies on Solid's reactive system
85
- createEffect(on(() => this.location.pathname, (pathname) => {
86
- const search = this.location.search ?? "";
87
- this.syncActorFromRouter(pathname, search);
88
- }));
135
+ this.disposeRouterWatcher = createRoot((dispose) => {
136
+ createEffect(on(() => this.location.pathname, (pathname) => {
137
+ const search = this.location.search ?? "";
138
+ this.syncActorFromRouter(pathname, search);
139
+ }));
140
+ return dispose;
141
+ });
89
142
  }
90
143
  /**
91
144
  * Stop watching SolidJS Router changes.
92
145
  *
93
- * Solid auto-cleans createEffect subscriptions when component unmounts.
94
- * This sets the processing flag to prevent pending operations.
146
+ * Calls the `dispose` function returned by `createRoot()` in `watchRouterChanges()`,
147
+ * tearing down the reactive effect and freeing the isolated owner. This is the only
148
+ * cleanup path — component unmount does NOT trigger this automatically.
95
149
  */
96
150
  unwatchRouterChanges() {
97
- // Solid auto-cleans createEffect on component unmount
98
- // Set flag to prevent any pending operations
99
- this.isProcessingNavigation = true;
151
+ this.disposeRouterWatcher?.();
152
+ this.disposeRouterWatcher = null;
100
153
  }
101
154
  /**
102
155
  * Dispose the bridge (alias for disconnect).
@@ -1 +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;;OAEG;IACgB,oBAAoB;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,IAAI,CAAC;IACvC,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"}
1
+ {"version":3,"file":"solid-router-bridge.js","sourceRoot":"","sources":["../src/solid-router-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAM1D;;;;;;;;;;GAUG;AACH,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;IAsBpC;IACA;IAtBV,oBAAoB,GAAwB,IAAI,CAAC;IAEzD;;;OAGG;IACc,WAAW,CAAS;IAErC;;;;;;;;;;;OAWG;IACH,YACkB,aAAwB,EACxB,QAAsB,EACvC,MAAc,EACd,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,gBAAgB,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;QATc,kBAAa,GAAb,aAAa,CAAW;QACxB,aAAQ,GAAR,QAAQ,CAAc;QASvC,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;IAC3B,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACgB,aAAa,CAAC,QAAgB,EAAE,OAAe;QACjE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CACtD,CAAC,KAAK,EAA6B,EAAE,CACpC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,CAC/D,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QACD,6EAA6E;QAC7E,OAAO,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACO,cAAc,CAAC,IAAY;QACpC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACgB,oBAAoB;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,IAAI,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACgB,sBAAsB;QACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,CAAC;IAC1C,CAAC;IAED;;;;;;;;;;OAUG;IACO,kBAAkB;QAC3B,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE;YAClD,YAAY,CACX,EAAE,CACD,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAC5B,CAAC,QAAgB,EAAE,EAAE;gBACpB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;gBAC1C,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC,CACD,CACD,CAAC;YACF,OAAO,OAAO,CAAC;QAChB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACO,oBAAoB;QAC7B,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;QAC9B,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAClC,CAAC;IAED;;;;;;;OAOG;IACH,OAAO;QACN,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;CACD"}
package/dist/types.d.ts CHANGED
@@ -1,21 +1,6 @@
1
1
  /**
2
2
  * Type definitions for @xmachines/play-solid-router
3
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
4
  export type { PlayRouteEvent, RouterBridge } from "@xmachines/play-router";
20
5
  export type { AbstractActor } from "@xmachines/play-actor";
21
6
  //# sourceMappingURL=types.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3E,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmachines/play-solid-router",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.40",
4
4
  "description": "SolidJS Router adapter for XMachines Universal Player Architecture",
5
5
  "license": "MIT",
6
6
  "author": "XMachines Contributors",
@@ -19,6 +19,7 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "exports": {
21
21
  ".": {
22
+ "source": "./src/index.ts",
22
23
  "types": "./dist/index.d.ts",
23
24
  "default": "./dist/index.js"
24
25
  }
@@ -28,27 +29,35 @@
28
29
  },
29
30
  "scripts": {
30
31
  "build": "tsc --build",
31
- "test": "vitest run",
32
+ "lint": "oxlint .",
33
+ "format": "oxfmt .",
34
+ "test": "vitest",
32
35
  "test:watch": "vitest",
33
- "typecheck": "tsc --noEmit",
34
- "clean": "rm -rf dist *.tsbuildinfo",
36
+ "clean": "rm -rf dist *.tsbuildinfo coverage node_modules/.svelte2tsx-*",
35
37
  "prepublishOnly": "npm run build"
36
38
  },
37
39
  "dependencies": {
38
- "@xmachines/play": "1.0.0-beta.4",
39
- "@xmachines/play-actor": "1.0.0-beta.4",
40
- "@xmachines/play-router": "1.0.0-beta.4",
41
- "@xmachines/play-signals": "1.0.0-beta.4"
40
+ "@xmachines/play": "1.0.0-beta.40",
41
+ "@xmachines/play-actor": "1.0.0-beta.40",
42
+ "@xmachines/play-router": "1.0.0-beta.40",
43
+ "@xmachines/play-signals": "1.0.0-beta.40"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@solidjs/router": "^0.16.1",
45
47
  "@solidjs/testing-library": "^0.8.10",
46
- "@xmachines/shared": "1.0.0-beta.4",
47
- "solid-js": "^1.9.11",
48
- "vite-plugin-solid": "^2.11.11"
48
+ "@xmachines/play-xstate": "1.0.0-beta.40",
49
+ "@xmachines/shared": "1.0.0-beta.40",
50
+ "jsdom": "^29.0.2",
51
+ "oxfmt": "^0.45.0",
52
+ "oxlint": "^1.60.0",
53
+ "solid-js": "^1.9.12",
54
+ "vite-plugin-solid": "^2.11.11",
55
+ "vitest": "^4.1.4",
56
+ "xstate": "^5.30.0"
49
57
  },
50
58
  "peerDependencies": {
51
- "@solidjs/router": "^0.15.4",
52
- "solid-js": "^1.8.0"
59
+ "@solidjs/router": "^0.16.1",
60
+ "solid-js": "^1.8.0",
61
+ "xstate": "^5.30.0"
53
62
  }
54
63
  }
@@ -1,25 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,36 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,75 +0,0 @@
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
@@ -1 +0,0 @@
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"}
package/dist/route-map.js DELETED
@@ -1,107 +0,0 @@
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
@@ -1 +0,0 @@
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"}