@xmachines/play-xstate 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.
Files changed (115) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.oxlintrc.json +3 -0
  3. package/README.md +454 -0
  4. package/dist/catalog/index.d.ts +12 -0
  5. package/dist/catalog/index.d.ts.map +1 -0
  6. package/dist/catalog/index.js +11 -0
  7. package/dist/catalog/index.js.map +1 -0
  8. package/dist/catalog/types.d.ts +36 -0
  9. package/dist/catalog/types.d.ts.map +1 -0
  10. package/dist/catalog/types.js +2 -0
  11. package/dist/catalog/types.js.map +1 -0
  12. package/dist/catalog/validate-binding.d.ts +21 -0
  13. package/dist/catalog/validate-binding.d.ts.map +1 -0
  14. package/dist/catalog/validate-binding.js +30 -0
  15. package/dist/catalog/validate-binding.js.map +1 -0
  16. package/dist/catalog/validate-props.d.ts +41 -0
  17. package/dist/catalog/validate-props.d.ts.map +1 -0
  18. package/dist/catalog/validate-props.js +95 -0
  19. package/dist/catalog/validate-props.js.map +1 -0
  20. package/dist/define-player.d.ts +110 -0
  21. package/dist/define-player.d.ts.map +1 -0
  22. package/dist/define-player.js +116 -0
  23. package/dist/define-player.js.map +1 -0
  24. package/dist/guards/compose.d.ts +136 -0
  25. package/dist/guards/compose.d.ts.map +1 -0
  26. package/dist/guards/compose.js +156 -0
  27. package/dist/guards/compose.js.map +1 -0
  28. package/dist/guards/helpers.d.ts +60 -0
  29. package/dist/guards/helpers.d.ts.map +1 -0
  30. package/dist/guards/helpers.js +91 -0
  31. package/dist/guards/helpers.js.map +1 -0
  32. package/dist/guards/index.d.ts +12 -0
  33. package/dist/guards/index.d.ts.map +1 -0
  34. package/dist/guards/index.js +11 -0
  35. package/dist/guards/index.js.map +1 -0
  36. package/dist/guards/types.d.ts +21 -0
  37. package/dist/guards/types.d.ts.map +1 -0
  38. package/dist/guards/types.js +2 -0
  39. package/dist/guards/types.js.map +1 -0
  40. package/dist/index.d.ts +22 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +21 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/player-actor.d.ts +143 -0
  45. package/dist/player-actor.d.ts.map +1 -0
  46. package/dist/player-actor.js +294 -0
  47. package/dist/player-actor.js.map +1 -0
  48. package/dist/routing/build-url.d.ts +27 -0
  49. package/dist/routing/build-url.d.ts.map +1 -0
  50. package/dist/routing/build-url.js +111 -0
  51. package/dist/routing/build-url.js.map +1 -0
  52. package/dist/routing/derive-route.d.ts +111 -0
  53. package/dist/routing/derive-route.d.ts.map +1 -0
  54. package/dist/routing/derive-route.js +144 -0
  55. package/dist/routing/derive-route.js.map +1 -0
  56. package/dist/routing/format-play-route-transitions.d.ts +31 -0
  57. package/dist/routing/format-play-route-transitions.d.ts.map +1 -0
  58. package/dist/routing/format-play-route-transitions.js +70 -0
  59. package/dist/routing/format-play-route-transitions.js.map +1 -0
  60. package/dist/routing/index.d.ts +13 -0
  61. package/dist/routing/index.d.ts.map +1 -0
  62. package/dist/routing/index.js +12 -0
  63. package/dist/routing/index.js.map +1 -0
  64. package/dist/routing/types.d.ts +25 -0
  65. package/dist/routing/types.d.ts.map +1 -0
  66. package/dist/routing/types.js +2 -0
  67. package/dist/routing/types.js.map +1 -0
  68. package/dist/signals/debounce.d.ts +18 -0
  69. package/dist/signals/debounce.d.ts.map +1 -0
  70. package/dist/signals/debounce.js +35 -0
  71. package/dist/signals/debounce.js.map +1 -0
  72. package/dist/signals/index.d.ts +3 -0
  73. package/dist/signals/index.d.ts.map +1 -0
  74. package/dist/signals/index.js +3 -0
  75. package/dist/signals/index.js.map +1 -0
  76. package/dist/signals/state-signal.d.ts +33 -0
  77. package/dist/signals/state-signal.d.ts.map +1 -0
  78. package/dist/signals/state-signal.js +41 -0
  79. package/dist/signals/state-signal.js.map +1 -0
  80. package/dist/types.d.ts +39 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +2 -0
  83. package/dist/types.js.map +1 -0
  84. package/examples/simple-machine.ts +187 -0
  85. package/package.json +46 -0
  86. package/src/catalog/index.ts +12 -0
  87. package/src/catalog/types.ts +38 -0
  88. package/src/catalog/validate-binding.ts +35 -0
  89. package/src/catalog/validate-props.ts +109 -0
  90. package/src/define-player.ts +121 -0
  91. package/src/guards/compose.ts +169 -0
  92. package/src/guards/helpers.ts +104 -0
  93. package/src/guards/index.ts +12 -0
  94. package/src/guards/types.ts +23 -0
  95. package/src/index.ts +40 -0
  96. package/src/player-actor.ts +346 -0
  97. package/src/routing/build-url.ts +127 -0
  98. package/src/routing/derive-route.ts +152 -0
  99. package/src/routing/format-play-route-transitions.ts +77 -0
  100. package/src/routing/index.ts +13 -0
  101. package/src/routing/types.ts +26 -0
  102. package/src/signals/debounce.ts +38 -0
  103. package/src/signals/index.ts +2 -0
  104. package/src/signals/state-signal.ts +45 -0
  105. package/src/types.ts +47 -0
  106. package/test/derive-route.test.ts +166 -0
  107. package/test/devtools-integration.spec.ts +97 -0
  108. package/test/format-play-route-transitions-query.test.ts +187 -0
  109. package/test/guards-edge-cases.spec.ts +630 -0
  110. package/test/player-actor-basic.spec.ts +189 -0
  111. package/test/player-actor-edge-cases.spec.ts +769 -0
  112. package/test/routing-edge-cases.spec.ts +340 -0
  113. package/tsconfig.json +15 -0
  114. package/tsconfig.tsbuildinfo +1 -0
  115. package/vitest.config.ts +27 -0
package/.oxfmtrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@xmachines/shared/oxfmt"]
3
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@xmachines/shared/oxlint"]
3
+ }
package/README.md ADDED
@@ -0,0 +1,454 @@
1
+ # @xmachines/play-xstate
2
+
3
+ **XState v5 adapter for Play Architecture with signal-driven reactivity and routing**
4
+
5
+ Transform declarative state machines into live actors with TC39 Signals and parameter-aware navigation.
6
+
7
+ ## Overview
8
+
9
+ `@xmachines/play-xstate` provides `definePlayer()`, the primary API for binding XState v5 state machines to the Play Architecture actor base. It enables business logic to control routing and state through guard-enforced transitions with catalog binding, signal lifecycle management, and XState DevTools compatibility.
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 guards decide navigation validity
14
+ - **Strict Separation (INV-02):** Zero React/framework imports in business logic
15
+ - **Signal-Only Reactivity (INV-05):** TC39 Signals expose all state changes
16
+
17
+ **Routing:** Supports `meta.route` patterns, `play.route` events with parameters, and route extraction.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install xstate@^5.0.0 zod@^3.23.0
23
+ npm install @xmachines/play-xstate
24
+ ```
25
+
26
+ ## Current Exports
27
+
28
+ - `definePlayer`, `PlayerActor`
29
+ - player types: `PlayerConfig`, `PlayerOptions`, `PlayerFactory`
30
+ - guard utilities: `composeGuards`, `composeGuardsOr`, `negateGuard`, `hasContext`, `eventMatches`, `stateMatches`
31
+ - routing utilities: `deriveRoute`, `isAbsoluteRoute`, `buildRouteUrl`, `formatPlayRouteTransitions`
32
+ - catalog utilities: `validateComponentBinding`, `validateViewProps`, `mergeViewProps`
33
+
34
+ **Peer dependencies:**
35
+
36
+ - `xstate` ^5.0.0 - State machine runtime
37
+ - `zod` ^3.23.0 - Schema validation for component props
38
+
39
+ ## Quick Start
40
+
41
+ ```typescript
42
+ import { setup } from "xstate";
43
+ import { z } from "zod";
44
+ import { definePlayer } from "@xmachines/play-xstate";
45
+ import { defineCatalog } from "@xmachines/play-catalog";
46
+
47
+ // 1. Define XState machine with meta.route
48
+ const machine = setup({
49
+ types: {
50
+ context: {} as { userId: string },
51
+ events: {} as { type: "play.route"; to: string } | { type: "auth.login"; userId: string },
52
+ },
53
+ guards: {
54
+ isLoggedIn: ({ context }) => !!context.userId,
55
+ },
56
+ }).createMachine({
57
+ id: "app",
58
+ initial: "login",
59
+ context: { userId: "" },
60
+ states: {
61
+ login: {
62
+ id: "login",
63
+ meta: {
64
+ route: "/login",
65
+ view: { component: "LoginForm" },
66
+ },
67
+ on: {
68
+ "auth.login": {
69
+ guard: "isLoggedIn",
70
+ target: "dashboard",
71
+ },
72
+ },
73
+ },
74
+ dashboard: {
75
+ id: "dashboard",
76
+ meta: {
77
+ route: "/dashboard",
78
+ view: { component: "Dashboard", props: { userId: "" } },
79
+ },
80
+ },
81
+ },
82
+ });
83
+
84
+ // 2. Define catalog with Zod schemas
85
+ const catalog = defineCatalog({
86
+ LoginForm: z.object({ error: z.string().optional() }),
87
+ Dashboard: z.object({ userId: z.string() }),
88
+ });
89
+
90
+ // 3. Create player factory
91
+ const createPlayer = definePlayer({ machine, catalog });
92
+
93
+ // 4. Create and start actor
94
+ const actor = createPlayer({ userId: "" });
95
+ actor.start();
96
+
97
+ // 5. Send events (play.route with parameters)
98
+ actor.send({ type: "play.route", to: "/login" });
99
+
100
+ // 6. Observe signals
101
+ console.log(actor.currentRoute.get()); // "/login"
102
+ console.log(actor.currentView.get()); // { component: "LoginForm", props: {...} }
103
+
104
+ // 7. Cleanup
105
+ actor.dispose();
106
+ ```
107
+
108
+ ## API Reference
109
+
110
+ ### definePlayer()
111
+
112
+ Create a player factory from XState machine and catalog:
113
+
114
+ ```typescript
115
+ const createPlayer = definePlayer<TMachine, TCatalog>({
116
+ machine: AnyStateMachine,
117
+ catalog?: Catalog,
118
+ options?: PlayerOptions,
119
+ }): PlayerFactory;
120
+ ```
121
+
122
+ **Config:**
123
+
124
+ - `machine` (required) - XState v5 state machine
125
+ - `catalog` (optional) - UI component catalog with Zod schemas
126
+ - `options` (optional) - Lifecycle hooks
127
+
128
+ **Returns:** Factory function `(input?) => PlayerActor`
129
+
130
+ **Example:**
131
+
132
+ ```typescript
133
+ const createPlayer = definePlayer({
134
+ machine: authMachine,
135
+ catalog: authCatalog,
136
+ options: {
137
+ onStart: (actor) => console.log("Started:", actor.id),
138
+ onTransition: (actor, prev, next) => {
139
+ console.log("Transition:", prev.value, "→", next.value);
140
+ },
141
+ },
142
+ });
143
+
144
+ const actor1 = createPlayer({ userId: "user1" });
145
+ const actor2 = createPlayer({ userId: "user2" });
146
+ // Multiple independent actor instances
147
+ ```
148
+
149
+ ### PlayerActor
150
+
151
+ Concrete actor implementing Play signal protocol:
152
+
153
+ **Signal Properties:**
154
+
155
+ - `state: Signal.State<Snapshot>` - Reactive snapshot of current state
156
+ - `currentRoute: Signal.Computed<string | null>` - Derived navigation path
157
+ - `currentView: Signal.Computed<ViewStructure | null>` - Derived UI structure
158
+
159
+ **Actor Properties:**
160
+
161
+ - `catalog: Catalog` - Component catalog
162
+
163
+ **Methods:**
164
+
165
+ - `start()` - Start the actor (must call after creation)
166
+ - `stop()` - Stop the actor
167
+ - `send(event: PlayEvent)` - Send event to actor
168
+ - `dispose()` - Convenience cleanup (calls stop())
169
+
170
+ **Example:**
171
+
172
+ ```typescript
173
+ const actor = createPlayer();
174
+ actor.start();
175
+
176
+ // Observe signals with watcher
177
+ const watcher = new Signal.subtle.Watcher(() => {
178
+ queueMicrotask(() => {
179
+ const route = actor.currentRoute.get();
180
+ console.log("Route changed:", route);
181
+ });
182
+ });
183
+ watcher.watch(actor.currentRoute);
184
+ actor.currentRoute.get(); // Initial read
185
+ ```
186
+
187
+ ### Guard Composition
188
+
189
+ ```typescript
190
+ import {
191
+ composeGuards,
192
+ composeGuardsOr,
193
+ negateGuard,
194
+ hasContext,
195
+ eventMatches,
196
+ stateMatches,
197
+ } from "@xmachines/play-xstate";
198
+
199
+ const machine = setup({
200
+ guards: {
201
+ isLoggedIn: hasContext("userId"),
202
+ isAdmin: ({ context }) => context.role === "admin",
203
+ },
204
+ }).createMachine({
205
+ on: {
206
+ accessAdmin: {
207
+ // Array means AND - all guards must pass
208
+ guard: composeGuards(["isLoggedIn", "isAdmin"]),
209
+ target: "adminPanel",
210
+ },
211
+ accessPublic: {
212
+ // OR composition - any guard passes
213
+ guard: composeGuardsOr(["isLoggedIn", ({ event }) => event.type === "guest.access"]),
214
+ target: "publicArea",
215
+ },
216
+ logout: {
217
+ // NOT composition
218
+ guard: negateGuard("isLoggedIn"),
219
+ target: "login",
220
+ },
221
+ },
222
+ });
223
+ ```
224
+
225
+ **Helpers:**
226
+
227
+ - `hasContext(path: string)` - Check if context property is truthy
228
+ - `eventMatches(type: string)` - Check event type
229
+ - `stateMatches(value: string)` - Check state value
230
+ - `composeGuards(guards: Array)` - AND composition
231
+ - `composeGuardsOr(guards: Array)` - OR composition
232
+ - `negateGuard(guard)` - NOT composition
233
+
234
+ **Complete API:** See [API Documentation](../../docs/api/@xmachines/play-xstate)
235
+
236
+ ## Examples
237
+
238
+ ### Guard Placement Philosophy
239
+
240
+ **Guards check if you can BE in a state (state entry), not if you can TAKE an event (event handlers).**
241
+
242
+ ```typescript
243
+ import { setup } from "xstate";
244
+ import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
245
+ import { defineCatalog } from "@xmachines/play-catalog";
246
+
247
+ // Pattern 1: RECOMMENDED - Use formatPlayRouteTransitions utility
248
+ const machineConfig = {
249
+ id: "app",
250
+ initial: "home",
251
+ context: { isAuthenticated: false },
252
+ states: {
253
+ home: {
254
+ id: "home",
255
+ meta: { route: "/", view: { component: "Home" } },
256
+ },
257
+ dashboard: {
258
+ id: "dashboard",
259
+ meta: { route: "/dashboard", view: { component: "Dashboard" } },
260
+ // Always-guard validates state entry
261
+ always: [
262
+ {
263
+ target: "login",
264
+ guard: ({ context }) => !context.isAuthenticated,
265
+ },
266
+ ],
267
+ },
268
+ login: {
269
+ id: "login",
270
+ meta: { route: "/login", view: { component: "Login" } },
271
+ },
272
+ },
273
+ };
274
+
275
+ // formatPlayRouteTransitions handles routing infrastructure
276
+ const machine = setup({
277
+ types: {
278
+ events: {} as { type: "play.route"; to: string } | { type: "auth.login" },
279
+ },
280
+ }).createMachine(formatPlayRouteTransitions(machineConfig));
281
+
282
+ const catalog = defineCatalog({
283
+ Home,
284
+ Dashboard,
285
+ Login,
286
+ });
287
+
288
+ const createPlayer = definePlayer({ machine, catalog });
289
+ const actor = createPlayer();
290
+ actor.start();
291
+
292
+ // Navigation via play.route event
293
+ actor.send({ type: "play.route", to: "/dashboard" });
294
+ // Guard validates: Can I BE in dashboard state?
295
+ // If !isAuthenticated → redirects to login
296
+ ```
297
+
298
+ **Why this works:**
299
+
300
+ - `formatPlayRouteTransitions` adds routing infrastructure (event.to → state mapping)
301
+ - Always-guards handle business logic (authentication checks)
302
+ - Clear separation: routing is infrastructure, guards are business logic
303
+
304
+ **Anti-pattern (DON'T DO THIS):**
305
+
306
+ ```typescript
307
+ // ❌ WRONG - Guard on event checking event properties
308
+ on: {
309
+ "play.route": {
310
+ guard: ({ event }) => event.to === "/dashboard",
311
+ target: "dashboard"
312
+ }
313
+ }
314
+ ```
315
+
316
+ **Reference:** See `docs/examples/routing-patterns.md` for canonical `formatPlayRouteTransitions` usage with always-guards for authentication.
317
+
318
+ ### Lifecycle Hooks
319
+
320
+ ```typescript
321
+ const createPlayer = definePlayer({
322
+ machine,
323
+ catalog,
324
+ options: {
325
+ onStart: (actor) => {
326
+ console.log("Actor started:", actor.id);
327
+ },
328
+ onStop: (actor) => {
329
+ console.log("Actor stopped:", actor.id);
330
+ },
331
+ onTransition: (actor, prev, next) => {
332
+ console.log("State change:", {
333
+ from: prev.value,
334
+ to: next.value,
335
+ timestamp: Date.now(),
336
+ });
337
+ },
338
+ onStateChange: (actor, state) => {
339
+ // Called on every state update
340
+ console.log("Snapshot updated:", state.value);
341
+ },
342
+ onError: (actor, error) => {
343
+ console.error("Actor error:", error);
344
+ // Log to monitoring service, show error UI, etc.
345
+ },
346
+ },
347
+ });
348
+ ```
349
+
350
+ ### XState DevTools Integration
351
+
352
+ ```typescript
353
+ import { createBrowserInspector } from "@statelyai/inspect";
354
+ import { definePlayer } from "@xmachines/play-xstate";
355
+
356
+ const { inspect } = createBrowserInspector();
357
+
358
+ const createPlayer = definePlayer({ machine, catalog });
359
+ const actor = createPlayer();
360
+ actor.start();
361
+
362
+ // PlayerActor maintains XState Inspector compatibility
363
+ // Inspector displays:
364
+ // - State transitions and values
365
+ // - Context data
366
+ // - Events sent to actor
367
+ // - Guard evaluation results
368
+
369
+ // Signals accessible via actor properties, not snapshots
370
+ console.log(actor.currentRoute.get()); // "/dashboard"
371
+ ```
372
+
373
+ ## Metadata Conventions
374
+
375
+ ### Route Metadata
376
+
377
+ ```typescript
378
+ // meta.route marks states as routable
379
+ states: {
380
+ dashboard: {
381
+ id: "dashboard",
382
+ meta: {
383
+ route: "/dashboard", // URL path - marks state as routable
384
+ },
385
+ },
386
+ }
387
+
388
+ // Parameters
389
+ meta: {
390
+ route: "/profile/:userId", // Required parameter
391
+ route: "/settings/:section?", // Optional parameter
392
+ }
393
+
394
+ // Inheritance
395
+ meta: {
396
+ route: "/absolute", // Starts with / → doesn't inherit parent route
397
+ route: "relative", // Doesn't start with / → inherits parent route
398
+ }
399
+ ```
400
+
401
+ ### View Metadata
402
+
403
+ ```typescript
404
+ meta: {
405
+ view: {
406
+ component: "Dashboard", // Must exist in catalog
407
+ props: { userId: "user123" }, // Validated against Zod schema
408
+ title: "Dashboard", // Additional metadata
409
+ },
410
+ }
411
+
412
+ // Dynamic props from context
413
+ meta: {
414
+ view: {
415
+ component: "Dashboard",
416
+ props: (context) => ({
417
+ userId: context.userId,
418
+ notifications: context.unreadCount,
419
+ }),
420
+ },
421
+ }
422
+ ```
423
+
424
+ ## Architecture
425
+
426
+ This package implements RFC Play v1 requirements:
427
+
428
+ **Architectural Invariants:**
429
+
430
+ - **Actor Authority (INV-01):** Guards decide navigation validity
431
+ - **Strict Separation (INV-02):** Zero framework imports
432
+ - **Signal-Only Reactivity (INV-05):** All state via TC39 Signals
433
+
434
+ **XState DevTools:** Maintains Inspector compatibility — snapshots remain pure XState format, signals accessible via actor properties.
435
+
436
+ **Routing:**
437
+
438
+ - `meta.route` property marks states as routable
439
+ - `play.route` events support parameters (enhancement)
440
+ - Route extraction for URL patterns
441
+
442
+ **Note:** Route parameter extraction uses URLPattern API. See [@xmachines/play-tanstack-react-router browser support](../play-tanstack-react-router/README.md#browser-support) for polyfill requirements.
443
+
444
+ ## Related Packages
445
+
446
+ - **[@xmachines/play-actor](../play-actor)** - AbstractActor base class
447
+ - **[@xmachines/play-signals](../play-signals)** - TC39 Signals polyfill
448
+ - **[@xmachines/play-catalog](../play-catalog)** - UI schema validation
449
+ - **[@xmachines/play-router](../play-router)** - Route extraction
450
+ - **[@xmachines/play](../play)** - Protocol types (PlayEvent)
451
+
452
+ ## License
453
+
454
+ MIT
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Catalog binding and validation utilities
3
+ *
4
+ * Provides functions for validating component references and props
5
+ * using Zod schemas from the catalog.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export { validateComponentBinding } from "./validate-binding.js";
10
+ export { validateViewProps, mergeViewProps } from "./validate-props.js";
11
+ export type { Catalog, CatalogEntry, ViewMetadata } from "./types.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/catalog/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Catalog binding and validation utilities
3
+ *
4
+ * Provides functions for validating component references and props
5
+ * using Zod schemas from the catalog.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export { validateComponentBinding } from "./validate-binding.js";
10
+ export { validateViewProps, mergeViewProps } from "./validate-props.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/catalog/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { z } from "zod";
2
+ import type { Catalog as BaseCatalog } from "@xmachines/play-catalog";
3
+ /**
4
+ * UI component catalog entry with schema and component reference
5
+ *
6
+ * Per CONTEXT.md: String keys reference components, Zod schemas validate props
7
+ */
8
+ export interface CatalogEntry {
9
+ /** Zod schema for component props */
10
+ schema: z.ZodType<any>;
11
+ /** React component (or other framework component) */
12
+ component: any;
13
+ }
14
+ /**
15
+ * Component catalog mapping
16
+ *
17
+ * Re-export from @xmachines/play-catalog for consistent API.
18
+ *
19
+ * Supports two formats:
20
+ * - Direct Zod schemas: `Record<string, z.ZodType>` via defineCatalog()
21
+ * - CatalogEntry objects: `Record<string, CatalogEntry>` with { schema, component }
22
+ *
23
+ * PlayerActor accepts either format - if entry has 'schema' property, uses CatalogEntry pattern,
24
+ * otherwise treats as direct Zod schema.
25
+ */
26
+ export type Catalog = BaseCatalog | Record<string, CatalogEntry>;
27
+ /**
28
+ * View metadata from state machine
29
+ */
30
+ export interface ViewMetadata {
31
+ /** Component name from catalog */
32
+ component: string;
33
+ /** Additional view props/data */
34
+ [key: string]: any;
35
+ }
36
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/catalog/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B,qCAAqC;IACrC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,qDAAqD;IACrD,SAAS,EAAE,GAAG,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/catalog/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import type { Catalog, ViewMetadata } from "./types.js";
2
+ /**
3
+ * Validate component binding against catalog
4
+ *
5
+ * Per CONTEXT.md:
6
+ * - "Component references: String keys"
7
+ * - "Mismatch detection: Both compile-time (TypeScript) and runtime validation"
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const view = { component: 'Dashboard', userId: '123' };
12
+ * validateComponentBinding(view, catalog);
13
+ * // Throws if 'Dashboard' not in catalog
14
+ * ```
15
+ *
16
+ * @param view - View metadata from meta.view
17
+ * @param catalog - Component catalog
18
+ * @throws Error if component not found in catalog
19
+ */
20
+ export declare const validateComponentBinding: (view: ViewMetadata, catalog: Catalog) => void;
21
+ //# sourceMappingURL=validate-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-binding.d.ts","sourceRoot":"","sources":["../../src/catalog/validate-binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,YAAY,EAAE,SAAS,OAAO,KAAG,IAc/E,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Validate component binding against catalog
3
+ *
4
+ * Per CONTEXT.md:
5
+ * - "Component references: String keys"
6
+ * - "Mismatch detection: Both compile-time (TypeScript) and runtime validation"
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const view = { component: 'Dashboard', userId: '123' };
11
+ * validateComponentBinding(view, catalog);
12
+ * // Throws if 'Dashboard' not in catalog
13
+ * ```
14
+ *
15
+ * @param view - View metadata from meta.view
16
+ * @param catalog - Component catalog
17
+ * @throws Error if component not found in catalog
18
+ */
19
+ export const validateComponentBinding = (view, catalog) => {
20
+ if (!view || !view.component) {
21
+ throw new Error("Invalid view metadata: missing 'component' property");
22
+ }
23
+ const { component } = view;
24
+ // Runtime validation
25
+ if (!(component in catalog)) {
26
+ const available = Object.keys(catalog).join(", ");
27
+ throw new Error(`Component "${component}" not found in catalog. Available components: ${available || "(none)"}`);
28
+ }
29
+ };
30
+ //# sourceMappingURL=validate-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-binding.js","sourceRoot":"","sources":["../../src/catalog/validate-binding.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAkB,EAAE,OAAgB,EAAQ,EAAE;IACtF,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAE3B,qBAAqB;IACrB,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,IAAI,KAAK,CACd,cAAc,SAAS,iDAAiD,SAAS,IAAI,QAAQ,EAAE,CAC/F,CAAC;IACH,CAAC;AACF,CAAC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { z } from "zod";
2
+ import type { Catalog, ViewMetadata } from "./types.js";
3
+ /**
4
+ * Validate view props against Zod schema
5
+ *
6
+ * Per CONTEXT.md:
7
+ * - "Prop validation: At state entry"
8
+ * - "currentView derivation: Merge meta.view with relevant context data"
9
+ *
10
+ * Per RESEARCH.md Pattern 4: Use safeParse() for validation
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const props = { userId: '123', stats: { logins: 5 } };
15
+ * const result = validateViewProps('Dashboard', props, catalog);
16
+ * if (!result.success) {
17
+ * console.error(result.error);
18
+ * }
19
+ * ```
20
+ *
21
+ * @param componentName - Component name from catalog
22
+ * @param props - Props to validate
23
+ * @param catalog - Component catalog with schemas
24
+ * @returns Zod parse result
25
+ */
26
+ export declare const validateViewProps: (componentName: string, props: any, catalog: Catalog) => {
27
+ success: boolean;
28
+ data?: any;
29
+ error?: z.ZodError | Error;
30
+ };
31
+ /**
32
+ * Merge view metadata with context for props
33
+ *
34
+ * Per CONTEXT.md: "Merge meta.view with relevant context data for component props"
35
+ *
36
+ * @param view - View metadata from meta.view
37
+ * @param context - Machine context
38
+ * @returns Merged props object
39
+ */
40
+ export declare const mergeViewProps: (view: ViewMetadata, context: any) => Record<string, any>;
41
+ //# sourceMappingURL=validate-props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-props.d.ts","sourceRoot":"","sources":["../../src/catalog/validate-props.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,eAAe,MAAM,EACrB,OAAO,GAAG,EACV,SAAS,OAAO,KACd;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAA;CA2B5D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,EAAE,SAAS,GAAG,KAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAwCnF,CAAC"}