@xmachines/play-xstate 1.0.0-beta.2 → 1.0.0-beta.21
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 +263 -107
- package/dist/define-player.d.ts +6 -56
- package/dist/define-player.d.ts.map +1 -1
- package/dist/define-player.js +8 -60
- package/dist/define-player.js.map +1 -1
- package/dist/define-player.typecheck.d.ts +2 -0
- package/dist/define-player.typecheck.d.ts.map +1 -0
- package/dist/define-player.typecheck.js +48 -0
- package/dist/define-player.typecheck.js.map +1 -0
- package/dist/errors.d.ts +66 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +76 -0
- package/dist/errors.js.map +1 -0
- package/dist/guards/compose.d.ts +14 -3
- package/dist/guards/compose.d.ts.map +1 -1
- package/dist/guards/compose.js +26 -0
- package/dist/guards/compose.js.map +1 -1
- package/dist/guards/helpers.d.ts +13 -17
- package/dist/guards/helpers.d.ts.map +1 -1
- package/dist/guards/helpers.js +20 -25
- package/dist/guards/helpers.js.map +1 -1
- package/dist/guards/index.d.ts +2 -1
- package/dist/guards/index.d.ts.map +1 -1
- package/dist/guards/index.js +1 -1
- package/dist/guards/index.js.map +1 -1
- package/dist/guards/types.d.ts +3 -2
- package/dist/guards/types.d.ts.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/player-actor.d.ts +70 -22
- package/dist/player-actor.d.ts.map +1 -1
- package/dist/player-actor.js +290 -88
- package/dist/player-actor.js.map +1 -1
- package/dist/player-actor.typecheck.d.ts +2 -0
- package/dist/player-actor.typecheck.d.ts.map +1 -0
- package/dist/player-actor.typecheck.js +27 -0
- package/dist/player-actor.typecheck.js.map +1 -0
- package/dist/routing/build-url.d.ts +22 -16
- package/dist/routing/build-url.d.ts.map +1 -1
- package/dist/routing/build-url.js +27 -20
- package/dist/routing/build-url.js.map +1 -1
- package/dist/routing/derive-route.d.ts +2 -2
- package/dist/routing/derive-route.d.ts.map +1 -1
- package/dist/routing/derive-route.js +3 -3
- package/dist/routing/derive-route.js.map +1 -1
- package/dist/routing/format-play-route-transitions.d.ts +41 -4
- package/dist/routing/format-play-route-transitions.d.ts.map +1 -1
- package/dist/routing/format-play-route-transitions.js +22 -19
- package/dist/routing/format-play-route-transitions.js.map +1 -1
- package/dist/routing/index.d.ts +2 -1
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/types.d.ts +8 -13
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/signals/index.d.ts +0 -1
- package/dist/signals/index.d.ts.map +1 -1
- package/dist/signals/index.js +0 -1
- package/dist/signals/index.js.map +1 -1
- package/dist/signals/state-signal.d.ts +1 -1
- package/dist/signals/state-signal.d.ts.map +1 -1
- package/dist/types.d.ts +20 -14
- package/dist/types.d.ts.map +1 -1
- package/package.json +26 -19
- package/dist/catalog/index.d.ts +0 -12
- package/dist/catalog/index.d.ts.map +0 -1
- package/dist/catalog/index.js +0 -11
- package/dist/catalog/index.js.map +0 -1
- package/dist/catalog/types.d.ts +0 -36
- package/dist/catalog/types.d.ts.map +0 -1
- package/dist/catalog/types.js +0 -2
- package/dist/catalog/types.js.map +0 -1
- package/dist/catalog/validate-binding.d.ts +0 -21
- package/dist/catalog/validate-binding.d.ts.map +0 -1
- package/dist/catalog/validate-binding.js +0 -30
- package/dist/catalog/validate-binding.js.map +0 -1
- package/dist/catalog/validate-props.d.ts +0 -41
- package/dist/catalog/validate-props.d.ts.map +0 -1
- package/dist/catalog/validate-props.js +0 -95
- package/dist/catalog/validate-props.js.map +0 -1
package/dist/player-actor.js
CHANGED
|
@@ -1,9 +1,213 @@
|
|
|
1
|
-
import { createActor } from "xstate";
|
|
2
|
-
import { AbstractActor } from "@xmachines/play-actor";
|
|
1
|
+
import { createActor, } from "xstate";
|
|
2
|
+
import { AbstractActor, } from "@xmachines/play-actor";
|
|
3
3
|
import { Signal } from "@xmachines/play-signals";
|
|
4
4
|
import { StateSignalManager } from "./signals/state-signal.js";
|
|
5
|
+
import { InvalidEventError, MissingRouteParamError } from "./errors.js";
|
|
5
6
|
import { deriveRoute, buildRouteUrl } from "./routing/index.js";
|
|
6
|
-
|
|
7
|
+
const hasSnapshotStatus = (snapshot) => {
|
|
8
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (!("status" in snapshot)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return typeof snapshot.status === "string";
|
|
15
|
+
};
|
|
16
|
+
const isActiveSnapshot = (snapshot) => {
|
|
17
|
+
return hasSnapshotStatus(snapshot) && snapshot.status === "active";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Derive the actor's current URL from state metadata and context.
|
|
21
|
+
*
|
|
22
|
+
* Resolves the route template from the current state's `meta.route` and substitutes
|
|
23
|
+
* any `:param` placeholders from `context.routeParams` (preferred) or flat `context`.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` — rather than throwing — when:
|
|
26
|
+
* - The snapshot has no route metadata (non-routable state)
|
|
27
|
+
* - A required route parameter is absent from context (`MissingRouteParamError`)
|
|
28
|
+
*
|
|
29
|
+
* The `null` return on missing params is intentional: it keeps the computed signal
|
|
30
|
+
* stable during transient states (e.g. mid-transition before context is fully updated,
|
|
31
|
+
* or after logout when `context.username` is `null` but the router bridge has not yet
|
|
32
|
+
* synced to the new state). The router bridge and URL bar are updated on the next
|
|
33
|
+
* stable snapshot once context is complete.
|
|
34
|
+
*
|
|
35
|
+
* @param snapshot - Current XState machine snapshot.
|
|
36
|
+
* @returns Resolved URL string, or `null` if the route cannot be resolved.
|
|
37
|
+
*/
|
|
38
|
+
const deriveCurrentRoute = (snapshot) => {
|
|
39
|
+
if (!snapshot || typeof snapshot.getMeta !== "function") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const meta = snapshot.getMeta();
|
|
43
|
+
if (!meta || typeof meta !== "object") {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const routeTemplate = deriveRoute(meta);
|
|
47
|
+
if (!routeTemplate) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return buildRouteUrl(routeTemplate, snapshot.context ?? {});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// A required route parameter is absent from context — this can happen transiently
|
|
55
|
+
// when the machine is mid-transition (e.g. actor in profile state but username not
|
|
56
|
+
// yet assigned, or after logout before the router bridge has redirected).
|
|
57
|
+
// Return null and let the signal recompute on the next stable snapshot.
|
|
58
|
+
if (error instanceof MissingRouteParamError) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const resolveViewMeta = (meta) => {
|
|
65
|
+
for (const stateMeta of Object.values(meta)) {
|
|
66
|
+
const maybeView = stateMeta && typeof stateMeta === "object"
|
|
67
|
+
? stateMeta.view
|
|
68
|
+
: undefined;
|
|
69
|
+
if (maybeView && typeof maybeView === "object") {
|
|
70
|
+
return maybeView;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Merge route parameters and an explicit allowlist of context fields into a single
|
|
77
|
+
* element's props.
|
|
78
|
+
*
|
|
79
|
+
* Priority rule (highest → lowest):
|
|
80
|
+
* 1. Explicit non-`undefined` spec prop — always wins (static values are authoritative).
|
|
81
|
+
* 2. URL route params (`context.routeParams`) — fills `undefined` slots from the URL path.
|
|
82
|
+
* 3. Allowlisted context fields (`contextProps`) — fills remaining `undefined` slots from
|
|
83
|
+
* the machine context (e.g. `context.username` on a state with no URL username param).
|
|
84
|
+
*
|
|
85
|
+
* The `contextProps` allowlist is the key safety mechanism: only fields the machine
|
|
86
|
+
* explicitly opts in to are ever exposed to components.
|
|
87
|
+
*
|
|
88
|
+
* @param routeParams - Extracted URL path parameters from `context.routeParams`.
|
|
89
|
+
* @param contextValues - Object containing only the allowlisted context fields.
|
|
90
|
+
* @param existingProps - The element's props as declared in the machine spec.
|
|
91
|
+
* @returns Merged props object with URL params and allowlisted context values filling
|
|
92
|
+
* undefined slots.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* // spec: { section: undefined, username: undefined, title: "Dashboard" }
|
|
97
|
+
* // routeParams: { section: "profile" } ← from URL /:section
|
|
98
|
+
* // contextValues: { username: "alice" } ← from contextProps: ["username"]
|
|
99
|
+
* // result: { section: "profile", username: "alice", title: "Dashboard" }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
function mergeRouteParamsIntoProps(routeParams, contextValues, existingProps) {
|
|
103
|
+
// Layer 3 (lowest priority): allowlisted context values
|
|
104
|
+
const merged = { ...contextValues };
|
|
105
|
+
// Layer 2: route params override context values for the same key
|
|
106
|
+
for (const [k, v] of Object.entries(routeParams)) {
|
|
107
|
+
merged[k] = v;
|
|
108
|
+
}
|
|
109
|
+
// Layer 1 (highest priority): explicit non-undefined spec props always win
|
|
110
|
+
for (const [k, v] of Object.entries(existingProps)) {
|
|
111
|
+
if (v !== undefined)
|
|
112
|
+
merged[k] = v;
|
|
113
|
+
}
|
|
114
|
+
return merged;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Derive the actor's current view from state metadata.
|
|
118
|
+
*
|
|
119
|
+
* Always returns a **fresh** `ViewMetadata` object so `Signal.State.set()` always notifies
|
|
120
|
+
* watchers — including on self-transitions (`reenter: true`) where the component name and
|
|
121
|
+
* spec reference would otherwise be identical. Returning the same object reference would
|
|
122
|
+
* cause `Signal.State`'s `Object.is` equality check to suppress the notification.
|
|
123
|
+
*
|
|
124
|
+
* ### Prop enrichment
|
|
125
|
+
*
|
|
126
|
+
* Each spec element's `props` are enriched before the view is emitted using two sources:
|
|
127
|
+
*
|
|
128
|
+
* **1. URL route params** (`context.routeParams`, populated by `formatPlayRouteTransitions`):
|
|
129
|
+
* Makes `:section?` / `:username` URL path parameters automatically available as component
|
|
130
|
+
* props without manual wiring.
|
|
131
|
+
*
|
|
132
|
+
* **2. Context fields** (`spec.contextProps` allowlist):
|
|
133
|
+
* Exposes selected context fields as component props for states where no URL param covers
|
|
134
|
+
* the data (e.g. `context.username` on a `/dashboard` route). Only fields explicitly named
|
|
135
|
+
* in `spec.contextProps: string[]` are ever exposed — nothing leaks from context implicitly.
|
|
136
|
+
*
|
|
137
|
+
* Merge priority (see `mergeRouteParamsIntoProps`):
|
|
138
|
+
* 1. Explicit non-`undefined` spec prop — always wins (authoritative static value).
|
|
139
|
+
* 2. URL route param (`context.routeParams`) — fills `undefined` slots from the URL path.
|
|
140
|
+
* 3. Allowlisted context field (`contextProps`) — fills remaining `undefined` slots.
|
|
141
|
+
*
|
|
142
|
+
* @param snapshot - Current XState machine snapshot.
|
|
143
|
+
* @returns Enriched `ViewMetadata`, or `null` if the current state has no view metadata.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* // spec: { contextProps: ["username"], elements: { root: { props: { username: undefined } } } }
|
|
148
|
+
* // context.username = "alice", routeParams = {}
|
|
149
|
+
* // Derived props: { username: "alice" }
|
|
150
|
+
*
|
|
151
|
+
* // spec: { contextProps: ["username"], elements: { root: { props: { username: undefined } } } }
|
|
152
|
+
* // context.username = "alice", routeParams = { username: "demo" }
|
|
153
|
+
* // Derived props: { username: "demo" } ← routeParams wins
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
const deriveCurrentView = (snapshot) => {
|
|
157
|
+
if (!snapshot) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const meta = snapshot.getMeta();
|
|
161
|
+
if (!meta || typeof meta !== "object") {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const viewMeta = resolveViewMeta(meta);
|
|
165
|
+
if (!viewMeta) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
// Extract routeParams from context (set by formatPlayRouteTransitions on play.route events)
|
|
169
|
+
const context = snapshot.context !== null &&
|
|
170
|
+
snapshot.context !== undefined &&
|
|
171
|
+
typeof snapshot.context === "object"
|
|
172
|
+
? snapshot.context
|
|
173
|
+
: null;
|
|
174
|
+
const routeParams = context !== null && typeof context.routeParams === "object" && context.routeParams !== null
|
|
175
|
+
? context.routeParams
|
|
176
|
+
: {};
|
|
177
|
+
// Extract the allowlisted context fields declared in spec.contextProps.
|
|
178
|
+
// Only fields explicitly named in the allowlist are ever exposed to components.
|
|
179
|
+
const contextPropsAllowlist = viewMeta.spec?.contextProps ?? [];
|
|
180
|
+
const contextValues = {};
|
|
181
|
+
if (context !== null && contextPropsAllowlist.length > 0) {
|
|
182
|
+
for (const key of contextPropsAllowlist) {
|
|
183
|
+
if (key in context && context[key] !== null && context[key] !== undefined) {
|
|
184
|
+
contextValues[key] = context[key];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Enrich element props when there is anything to merge.
|
|
189
|
+
if (viewMeta.spec?.elements) {
|
|
190
|
+
const enrichedElements = Object.fromEntries(Object.entries(viewMeta.spec.elements).map(([key, el]) => {
|
|
191
|
+
const element = el;
|
|
192
|
+
const existingProps = element.props ?? {};
|
|
193
|
+
return [
|
|
194
|
+
key,
|
|
195
|
+
{
|
|
196
|
+
...element,
|
|
197
|
+
props: mergeRouteParamsIntoProps(routeParams, contextValues, existingProps),
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
}));
|
|
201
|
+
return {
|
|
202
|
+
component: viewMeta.component,
|
|
203
|
+
spec: { ...viewMeta.spec, elements: enrichedElements },
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
component: viewMeta.component,
|
|
208
|
+
spec: viewMeta.spec,
|
|
209
|
+
};
|
|
210
|
+
};
|
|
7
211
|
/**
|
|
8
212
|
* Concrete XState actor implementing Play Architecture signal protocol
|
|
9
213
|
*
|
|
@@ -64,7 +268,7 @@ import { validateComponentBinding, validateViewProps, mergeViewProps } from "./c
|
|
|
64
268
|
* // Watcher notification scheduled via microtask
|
|
65
269
|
* ```
|
|
66
270
|
*
|
|
67
|
-
* @see
|
|
271
|
+
* @see [Play RFC](../../docs/rfc/play.md)
|
|
68
272
|
* @see {@link definePlayer} for factory creation
|
|
69
273
|
* @see {@link @xmachines/play-actor!AbstractActor} for signal protocol
|
|
70
274
|
* @see {@link @xmachines/play-actor!Routable} for routing capability
|
|
@@ -83,30 +287,87 @@ export class PlayerActor extends AbstractActor {
|
|
|
83
287
|
_xstateActor;
|
|
84
288
|
_stateManager;
|
|
85
289
|
_options;
|
|
86
|
-
_catalog;
|
|
87
290
|
_viewSignal;
|
|
88
291
|
// AbstractActor protocol requirements
|
|
89
292
|
state;
|
|
293
|
+
/**
|
|
294
|
+
* A TC39 `Signal.Computed` that derives the current URL path from the active
|
|
295
|
+
* machine state's `meta.route` template and the actor's context.
|
|
296
|
+
*
|
|
297
|
+
* Returns `null` when the current state has no `meta.route`, or when the route
|
|
298
|
+
* template cannot be fully resolved (e.g. a required parameter is absent from
|
|
299
|
+
* context).
|
|
300
|
+
*
|
|
301
|
+
* @throws {MissingRouteParamError} When a required `:param` placeholder in the
|
|
302
|
+
* route template has no matching value in the actor's context. Import the class
|
|
303
|
+
* from `@xmachines/play-xstate/errors`.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```typescript
|
|
307
|
+
* // Returns "/profile/alice" when context.userId === "alice"
|
|
308
|
+
* const route = actor.currentRoute.get();
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
90
311
|
currentRoute;
|
|
312
|
+
/**
|
|
313
|
+
* The route derived from the machine's initial state — fixed at construction,
|
|
314
|
+
* never changes even when the actor is restored from a snapshot.
|
|
315
|
+
*
|
|
316
|
+
* Router bridges compare this against the browser URL to distinguish a deep-link
|
|
317
|
+
* (non-initial URL → router wins) from a restore (initial URL + actor at a
|
|
318
|
+
* different restored route → actor wins).
|
|
319
|
+
*/
|
|
320
|
+
initialRoute;
|
|
321
|
+
/**
|
|
322
|
+
* Reactive signal containing the current view spec derived from the active state's
|
|
323
|
+
* `meta.view` metadata.
|
|
324
|
+
*
|
|
325
|
+
* Always emits a **fresh object reference** on every state transition (including
|
|
326
|
+
* self-transitions with `reenter: true`) so TC39 Signal equality checks reliably
|
|
327
|
+
* detect changes.
|
|
328
|
+
*
|
|
329
|
+
* The emitted `ViewMetadata` has its spec element `props` enriched with
|
|
330
|
+
* `context.routeParams` before emission — URL path parameters (e.g. `:section?`)
|
|
331
|
+
* flow into component props automatically. See `mergeRouteParamsIntoProps` for the
|
|
332
|
+
* merge priority rules.
|
|
333
|
+
*
|
|
334
|
+
* Returns `null` when the current state has no `meta.view` metadata.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* const view = actor.currentView.get();
|
|
339
|
+
* if (view) {
|
|
340
|
+
* console.log(view.component); // e.g. "Dashboard"
|
|
341
|
+
* console.log(view.spec); // @json-render/core Spec object
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
91
345
|
currentView;
|
|
92
|
-
|
|
93
|
-
constructor(machine, catalog, options, input) {
|
|
346
|
+
constructor(machine, options, input, snapshot) {
|
|
94
347
|
// Defensive check: Validate machine before passing to createActor
|
|
95
348
|
if (!machine || typeof machine !== "object") {
|
|
96
349
|
throw new Error("PlayerActor requires a valid XState machine");
|
|
97
350
|
}
|
|
98
351
|
// Create XState actor
|
|
99
|
-
|
|
352
|
+
// XState 5.28.0: createActor() options has a conditional type constraint on `input` that
|
|
353
|
+
// TypeScript cannot resolve against an unbound generic TMachine parameter.
|
|
354
|
+
// Option B: Cast to ActorOptions<TMachine> — narrower than `as any` because we remain
|
|
355
|
+
// within the XState type system. The `input` parameter is typed as InputFrom<TMachine>
|
|
356
|
+
// at the constructor call site, so consumers receive compile-time validation.
|
|
357
|
+
// Track XState typing improvements: https://github.com/statelyai/xstate/issues
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
359
|
+
const xstateActor = createActor(machine, { input, snapshot });
|
|
100
360
|
// Defensive check: Validate actor was created successfully
|
|
101
361
|
if (!xstateActor) {
|
|
102
362
|
throw new Error("Failed to create XState actor - machine may be null or invalid");
|
|
103
363
|
}
|
|
104
364
|
// Call AbstractActor constructor with actor logic
|
|
105
365
|
super(xstateActor.logic);
|
|
366
|
+
// Derive the machine's initial route from a fresh (non-restored) snapshot.
|
|
367
|
+
// createActor(machine) without options is cheap and synchronous — never started.
|
|
368
|
+
this.initialRoute = deriveCurrentRoute(createActor(machine).getSnapshot());
|
|
106
369
|
this._xstateActor = xstateActor;
|
|
107
370
|
this._options = options || {};
|
|
108
|
-
this._catalog = catalog || {};
|
|
109
|
-
this.catalog = this._catalog;
|
|
110
371
|
// Initialize state signal manager
|
|
111
372
|
this._stateManager = new StateSignalManager(xstateActor.getSnapshot());
|
|
112
373
|
this.state = this._stateManager.signal;
|
|
@@ -115,30 +376,14 @@ export class PlayerActor extends AbstractActor {
|
|
|
115
376
|
// Initialize currentRoute computed signal
|
|
116
377
|
this.currentRoute = new Signal.Computed(() => {
|
|
117
378
|
const snapshot = this.state.get();
|
|
118
|
-
|
|
119
|
-
if (!snapshot || typeof snapshot.getMeta !== "function") {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
const meta = snapshot.getMeta();
|
|
123
|
-
// Defensive check: Validate meta is an object
|
|
124
|
-
if (!meta || typeof meta !== "object") {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
// Derive route from meta.route per Plan 04
|
|
128
|
-
const routeTemplate = deriveRoute(meta);
|
|
129
|
-
if (!routeTemplate) {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
// Build full URL with params, query, hash
|
|
133
|
-
// Per CONTEXT.md: "Full URL generation including query params, hash, base path from context"
|
|
134
|
-
return buildRouteUrl(routeTemplate, snapshot.context);
|
|
379
|
+
return deriveCurrentRoute(snapshot);
|
|
135
380
|
});
|
|
136
381
|
// Expose view signal directly for proper watcher propagation
|
|
137
382
|
this.currentView = this._viewSignal;
|
|
138
383
|
// Subscribe to XState actor transitions
|
|
139
384
|
this._xstateActor.subscribe((snapshot) => {
|
|
140
385
|
// Only update on stable/active states per CONTEXT.md
|
|
141
|
-
if (snapshot
|
|
386
|
+
if (isActiveSnapshot(snapshot)) {
|
|
142
387
|
// Update state signal with microtask batching
|
|
143
388
|
this._stateManager.scheduleUpdate(snapshot);
|
|
144
389
|
// Validate view at state entry per CONTEXT.md: "Prop validation: At state entry"
|
|
@@ -177,33 +422,34 @@ export class PlayerActor extends AbstractActor {
|
|
|
177
422
|
return this;
|
|
178
423
|
}
|
|
179
424
|
/**
|
|
180
|
-
* Send event to actor
|
|
425
|
+
* Send an event to the underlying XState actor.
|
|
426
|
+
*
|
|
427
|
+
* The actor's state machine guards decide whether the event causes a transition.
|
|
428
|
+
* Pass any event from the machine's event union — domain events, routing events, etc.
|
|
181
429
|
*
|
|
182
|
-
*
|
|
183
|
-
* guards determine whether each event is valid from the current state.
|
|
430
|
+
* @param event - An event from the machine's `EventFromLogic<TMachine>` union.
|
|
184
431
|
*
|
|
185
|
-
* @
|
|
432
|
+
* @throws {InvalidEventError} When `event` is not a plain object (`null`, `undefined`,
|
|
433
|
+
* a string, number, etc.). Import the class from `@xmachines/play-xstate/errors`.
|
|
186
434
|
*
|
|
187
435
|
* @example
|
|
188
436
|
* ```typescript
|
|
189
|
-
* // Domain event
|
|
190
|
-
* actor.send({ type:
|
|
437
|
+
* // Domain event (typed to machine's event union)
|
|
438
|
+
* actor.send({ type: "auth.login", userId: "123" });
|
|
191
439
|
*
|
|
192
440
|
* // Routing event
|
|
193
|
-
* actor.send({ type:
|
|
441
|
+
* actor.send({ type: "play.route", to: "#home" });
|
|
194
442
|
* ```
|
|
195
443
|
*/
|
|
196
444
|
send(event) {
|
|
197
445
|
// Defensive check: Validate event is not null/undefined
|
|
198
446
|
if (!event || typeof event !== "object") {
|
|
199
|
-
|
|
200
|
-
return;
|
|
447
|
+
throw new InvalidEventError(event);
|
|
201
448
|
}
|
|
202
449
|
// Store previous state for onTransition hook
|
|
203
450
|
const prevSnapshot = this._xstateActor.getSnapshot();
|
|
204
451
|
// Send to XState actor
|
|
205
|
-
//
|
|
206
|
-
// than our generic PlayEvent. At runtime, both are { type: string, ...fields }.
|
|
452
|
+
// EventFromLogic<TMachine> is exactly what _xstateActor.send expects — no cast needed.
|
|
207
453
|
this._xstateActor.send(event);
|
|
208
454
|
// Call onTransition hook
|
|
209
455
|
if (this._options.onTransition) {
|
|
@@ -226,61 +472,17 @@ export class PlayerActor extends AbstractActor {
|
|
|
226
472
|
* @param snapshot - Current XState snapshot
|
|
227
473
|
*/
|
|
228
474
|
_validateAndCacheView(snapshot) {
|
|
229
|
-
// Defensive check: Validate snapshot and getMeta exists
|
|
230
|
-
if (!snapshot || typeof snapshot.getMeta !== "function") {
|
|
231
|
-
this._viewSignal.set(null);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const meta = snapshot.getMeta();
|
|
235
|
-
// Defensive check: Validate meta is an object
|
|
236
|
-
if (!meta || typeof meta !== "object") {
|
|
237
|
-
this._viewSignal.set(null);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
// Find meta.view in active state nodes
|
|
241
|
-
let viewMeta = null;
|
|
242
|
-
for (const [_stateId, stateMeta] of Object.entries(meta)) {
|
|
243
|
-
if (stateMeta && stateMeta.view) {
|
|
244
|
-
viewMeta = stateMeta.view;
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (!viewMeta) {
|
|
249
|
-
this._viewSignal.set(null);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
// Validate component binding
|
|
253
475
|
try {
|
|
254
|
-
|
|
476
|
+
const view = deriveCurrentView(snapshot);
|
|
477
|
+
this._viewSignal.set(view);
|
|
255
478
|
}
|
|
256
479
|
catch (error) {
|
|
257
|
-
// Call onError hook if validation fails
|
|
258
480
|
if (this._options.onError) {
|
|
259
|
-
|
|
481
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
482
|
+
this._options.onError(this, err);
|
|
260
483
|
}
|
|
261
|
-
|
|
262
|
-
this._viewSignal.set(null);
|
|
263
|
-
return;
|
|
484
|
+
// On error: keep the last valid view (don't clear)
|
|
264
485
|
}
|
|
265
|
-
// Merge meta.view with context for props
|
|
266
|
-
const props = mergeViewProps(viewMeta, snapshot.context);
|
|
267
|
-
// Validate props against Zod schema
|
|
268
|
-
const validation = validateViewProps(viewMeta.component, props, this._catalog);
|
|
269
|
-
if (!validation.success) {
|
|
270
|
-
// Call onError hook if prop validation fails
|
|
271
|
-
if (this._options.onError) {
|
|
272
|
-
this._options.onError(this, new Error(`Invalid props for component "${viewMeta.component}": ${validation.error?.message || "Unknown error"}`));
|
|
273
|
-
}
|
|
274
|
-
console.error("View props validation failed:", validation.error);
|
|
275
|
-
// Store view structure even if validation fails (allow app to handle gracefully)
|
|
276
|
-
// Per RESEARCH.md open question: prefer error events over throwing in production
|
|
277
|
-
}
|
|
278
|
-
// Cache validated view structure (validated once at state entry)
|
|
279
|
-
const newView = {
|
|
280
|
-
component: viewMeta.component,
|
|
281
|
-
props: validation.success ? validation.data : props,
|
|
282
|
-
};
|
|
283
|
-
this._viewSignal.set(newView);
|
|
284
486
|
}
|
|
285
487
|
/**
|
|
286
488
|
* Convenience dispose method for cleanup
|
package/dist/player-actor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player-actor.js","sourceRoot":"","sources":["../src/player-actor.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"player-actor.js","sourceRoot":"","sources":["../src/player-actor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-actor.typecheck.d.ts","sourceRoot":"","sources":["../src/player-actor.typecheck.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { setup } from "xstate";
|
|
2
|
+
import { definePlayer } from "./define-player.js";
|
|
3
|
+
const machine = setup({
|
|
4
|
+
types: {
|
|
5
|
+
context: {},
|
|
6
|
+
input: {},
|
|
7
|
+
events: {},
|
|
8
|
+
},
|
|
9
|
+
}).createMachine({
|
|
10
|
+
context: ({ input }) => ({ value: input.value }),
|
|
11
|
+
initial: "idle",
|
|
12
|
+
states: {
|
|
13
|
+
idle: {},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const createPlayer = definePlayer({ machine });
|
|
17
|
+
const actor = createPlayer({ value: 1 });
|
|
18
|
+
const restoredActor = createPlayer({ value: 1 }, { snapshot: actor.getSnapshot() });
|
|
19
|
+
// Verify actor instances are created successfully
|
|
20
|
+
void actor;
|
|
21
|
+
void restoredActor;
|
|
22
|
+
// CONS-16: [key: string]: unknown index signature removed.
|
|
23
|
+
// Accessing unknown properties on PlayerActor is now a type error (correct behavior).
|
|
24
|
+
// @ts-expect-error — unknown fields no longer accessible via index signature
|
|
25
|
+
const unknownField = actor["custom-extension-field"];
|
|
26
|
+
void unknownField;
|
|
27
|
+
//# sourceMappingURL=player-actor.typecheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-actor.typecheck.js","sourceRoot":"","sources":["../src/player-actor.typecheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,OAAO,GAAG,KAAK,CAAC;IACrB,KAAK,EAAE;QACN,OAAO,EAAE,EAAuB;QAChC,KAAK,EAAE,EAAuB;QAC9B,MAAM,EAAE,EAAsB;KAC9B;CACD,CAAC,CAAC,aAAa,CAAC;IAChB,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAChD,OAAO,EAAE,MAAM;IACf,MAAM,EAAE;QACP,IAAI,EAAE,EAAE;KACR;CACD,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AACzC,MAAM,aAAa,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAEpF,kDAAkD;AAClD,KAAK,KAAK,CAAC;AACX,KAAK,aAAa,CAAC;AAEnB,2DAA2D;AAC3D,sFAAsF;AACtF,6EAA6E;AAC7E,MAAM,YAAY,GAAG,KAAK,CAAC,wBAAwB,CAAC,CAAC;AACrD,KAAK,YAAY,CAAC"}
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import type { RouteContext } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
* Build full URL from route template and context
|
|
3
|
+
* Build a full URL from a route template and the actor's context.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - "Parameters: String template syntax — /user/:id"
|
|
8
|
-
* - "Inheritance: relative paths inherit parent route"
|
|
5
|
+
* Substitutes `:param` and `:param?` placeholders from `context.routeParams`
|
|
6
|
+
* or flat `context` values, then appends query params and hash fragments.
|
|
9
7
|
*
|
|
10
|
-
*
|
|
8
|
+
* Parameter lookup order for each placeholder:
|
|
9
|
+
* 1. `context.routeParams[param]` — preferred when the state machine stores
|
|
10
|
+
* route parameters in a dedicated sub-object
|
|
11
|
+
* 2. `context[param]` — flat context fallback
|
|
12
|
+
*
|
|
13
|
+
* @param routeTemplate - Route path template, e.g. `"/profile/:userId"` or
|
|
14
|
+
* `"/settings/:section?"`.
|
|
15
|
+
* @param context - Actor context object containing parameter values, optional
|
|
16
|
+
* `query` record, optional `hash` string, and optional `basePath` prefix.
|
|
17
|
+
* @returns The fully resolved URL string.
|
|
18
|
+
*
|
|
19
|
+
* @throws {MissingRouteParamError} When a **required** `:param` placeholder has no
|
|
20
|
+
* matching value in context. Optional parameters (`:param?`) are silently omitted
|
|
21
|
+
* when missing. Import the class from `@xmachines/play-xstate/errors`.
|
|
11
22
|
*
|
|
12
23
|
* @example
|
|
13
24
|
* ```typescript
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* query: { tab: 'profile' },
|
|
17
|
-
* hash: 'section-1'
|
|
18
|
-
* });
|
|
19
|
-
* // Result: '/user/123?tab=profile#section-1'
|
|
20
|
-
* ```
|
|
25
|
+
* buildRouteUrl("/user/:id", { id: "123", query: { tab: "profile" }, hash: "top" });
|
|
26
|
+
* // → "/user/123?tab=profile#top"
|
|
21
27
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
28
|
+
* buildRouteUrl("/settings/:section?", {});
|
|
29
|
+
* // → "/settings" (optional param omitted)
|
|
30
|
+
* ```
|
|
25
31
|
*/
|
|
26
32
|
export declare const buildRouteUrl: (routeTemplate: string, context?: RouteContext) => string;
|
|
27
33
|
//# sourceMappingURL=build-url.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-url.d.ts","sourceRoot":"","sources":["../../src/routing/build-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"build-url.d.ts","sourceRoot":"","sources":["../../src/routing/build-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,aAAa,GAAI,eAAe,MAAM,EAAE,UAAS,YAAiB,KAAG,MA6BjF,CAAC"}
|