@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
@@ -0,0 +1,97 @@
1
+ /**
2
+ * DevTools integration validation
3
+ *
4
+ * Per CONTEXT.md: "XState DevTools connects successfully and displays actor state/transitions"
5
+ * Per CONTEXT.md: "Snapshot format: Standard XState snapshots (no Play-specific data)"
6
+ */
7
+
8
+ import { describe, it as test, expect } from "vitest";
9
+
10
+ import { setup } from "xstate";
11
+ import { definePlayer } from "../src/define-player.js";
12
+
13
+ describe("DevTools Integration", () => {
14
+ test("PlayerActor maintains XState snapshot format", () => {
15
+ // Create simple machine
16
+ const machine = setup({
17
+ types: {
18
+ context: {} as { count: number },
19
+ events: {} as { type: "increment" },
20
+ },
21
+ }).createMachine({
22
+ context: { count: 0 },
23
+ on: {
24
+ increment: {
25
+ actions: ({ context }) => {
26
+ context.count++;
27
+ },
28
+ },
29
+ },
30
+ });
31
+
32
+ // Create player
33
+ const createPlayer = definePlayer({ machine });
34
+ const actor = createPlayer({ count: 0 });
35
+ actor.start();
36
+
37
+ // Get snapshot
38
+ const snapshot = actor.getSnapshot();
39
+
40
+ // Verify snapshot has standard XState properties (no Play pollution)
41
+ expect(snapshot, "Snapshot exists").toBeTruthy();
42
+ expect("value" in snapshot, "Snapshot has 'value' property").toBeTruthy();
43
+ expect("context" in snapshot, "Snapshot has 'context' property").toBeTruthy();
44
+ expect("status" in snapshot, "Snapshot has 'status' property").toBeTruthy();
45
+
46
+ // Verify NO Play-specific properties in snapshot
47
+ expect("_play" in snapshot).toBe(false, "Snapshot should not have '_play' property");
48
+ expect("_signals" in snapshot).toBe(false, "Snapshot should not have '_signals' property");
49
+
50
+ actor.stop();
51
+ });
52
+
53
+ test("PlayerActor signals are accessible separately from snapshots", () => {
54
+ const machine = setup({
55
+ types: {
56
+ context: {} as { userId: string },
57
+ },
58
+ }).createMachine({
59
+ context: { userId: "" },
60
+ initial: "idle",
61
+ states: {
62
+ idle: {
63
+ meta: {
64
+ route: "/home",
65
+ view: { component: "Home" },
66
+ },
67
+ },
68
+ },
69
+ });
70
+
71
+ const catalog = {
72
+ Home: {
73
+ schema: {
74
+ parse: () => ({}),
75
+ safeParse: () => ({ success: true, data: {} }),
76
+ } as any, // Mock Zod schema
77
+ component: () => "Home",
78
+ },
79
+ };
80
+
81
+ const createPlayer = definePlayer({ machine, catalog });
82
+ const actor = createPlayer({ userId: "user123" });
83
+ actor.start();
84
+
85
+ // Signals accessible via actor properties (not in snapshot)
86
+ expect(actor.state, "Actor has state signal").toBeTruthy();
87
+ expect(actor.currentRoute, "Actor has currentRoute signal").toBeTruthy();
88
+ expect(actor.currentView, "Actor has currentView signal").toBeTruthy();
89
+
90
+ // Signals have get() method
91
+ expect(typeof actor.state.get).toBe("function");
92
+ expect(typeof actor.currentRoute.get).toBe("function");
93
+ expect(typeof actor.currentView.get).toBe("function");
94
+
95
+ actor.stop();
96
+ });
97
+ });
@@ -0,0 +1,187 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createActor, createMachine, type EventObject } from "xstate";
3
+ import { formatPlayRouteTransitions } from "../src/routing/format-play-route-transitions.js";
4
+
5
+ describe("formatPlayRouteTransitions - queryParams context assignment", () => {
6
+ it("should assign routeParams from event.params to context", () => {
7
+ const machineConfig = {
8
+ id: "testMachine",
9
+ initial: "home",
10
+ context: {
11
+ routeParams: {},
12
+ queryParams: {},
13
+ },
14
+ states: {
15
+ home: {
16
+ id: "home",
17
+ meta: { route: "/" },
18
+ },
19
+ profile: {
20
+ id: "profile",
21
+ meta: { route: "/profile/:userId" },
22
+ },
23
+ },
24
+ };
25
+
26
+ const machine = createMachine(formatPlayRouteTransitions(machineConfig));
27
+ const actor = createActor(machine);
28
+ actor.start();
29
+
30
+ // Send play.route event with params
31
+ actor.send({
32
+ type: "play.route",
33
+ to: "#profile",
34
+ params: { userId: "123" },
35
+ } as EventObject);
36
+
37
+ // Verify routeParams assigned
38
+ const snapshot = actor.getSnapshot();
39
+ expect(snapshot.context).toMatchObject({
40
+ routeParams: { userId: "123" },
41
+ });
42
+ });
43
+
44
+ it("should assign queryParams from event.query to context", () => {
45
+ const machineConfig = {
46
+ id: "testMachine",
47
+ initial: "home",
48
+ context: {
49
+ routeParams: {},
50
+ queryParams: {},
51
+ },
52
+ states: {
53
+ home: {
54
+ id: "home",
55
+ meta: { route: "/" },
56
+ },
57
+ settings: {
58
+ id: "settings",
59
+ meta: { route: "/settings" },
60
+ },
61
+ },
62
+ };
63
+
64
+ const machine = createMachine(formatPlayRouteTransitions(machineConfig));
65
+ const actor = createActor(machine);
66
+ actor.start();
67
+
68
+ // Send play.route event with query
69
+ actor.send({
70
+ type: "play.route",
71
+ to: "#settings",
72
+ query: { tab: "security", theme: "dark" },
73
+ } as EventObject);
74
+
75
+ // Verify queryParams assigned
76
+ const snapshot = actor.getSnapshot();
77
+ expect(snapshot.context).toMatchObject({
78
+ queryParams: { tab: "security", theme: "dark" },
79
+ });
80
+ });
81
+
82
+ it("should assign empty object when query is undefined", () => {
83
+ const machineConfig = {
84
+ id: "testMachine",
85
+ initial: "home",
86
+ context: {
87
+ routeParams: {},
88
+ queryParams: {},
89
+ },
90
+ states: {
91
+ home: {
92
+ id: "home",
93
+ meta: { route: "/" },
94
+ },
95
+ },
96
+ };
97
+
98
+ const machine = createMachine(formatPlayRouteTransitions(machineConfig));
99
+ const actor = createActor(machine);
100
+ actor.start();
101
+
102
+ // Send play.route event without query
103
+ actor.send({
104
+ type: "play.route",
105
+ to: "#home",
106
+ } as EventObject);
107
+
108
+ // Verify queryParams is empty object
109
+ const snapshot = actor.getSnapshot();
110
+ expect(snapshot.context).toMatchObject({
111
+ queryParams: {},
112
+ });
113
+ });
114
+
115
+ it("should assign both routeParams and queryParams when both present", () => {
116
+ const machineConfig = {
117
+ id: "testMachine",
118
+ initial: "home",
119
+ context: {
120
+ routeParams: {},
121
+ queryParams: {},
122
+ },
123
+ states: {
124
+ home: {
125
+ id: "home",
126
+ meta: { route: "/" },
127
+ },
128
+ profile: {
129
+ id: "profile",
130
+ meta: { route: "/profile/:userId" },
131
+ },
132
+ },
133
+ };
134
+
135
+ const machine = createMachine(formatPlayRouteTransitions(machineConfig));
136
+ const actor = createActor(machine);
137
+ actor.start();
138
+
139
+ // Send play.route event with both params and query
140
+ actor.send({
141
+ type: "play.route",
142
+ to: "#profile",
143
+ params: { userId: "456" },
144
+ query: { view: "detailed", sort: "date" },
145
+ } as EventObject);
146
+
147
+ // Verify both assigned correctly
148
+ const snapshot = actor.getSnapshot();
149
+ expect(snapshot.context).toMatchObject({
150
+ routeParams: { userId: "456" },
151
+ queryParams: { view: "detailed", sort: "date" },
152
+ });
153
+ });
154
+
155
+ it("should preserve context types with queryParams field", () => {
156
+ // Type-level test: Verify context type includes queryParams
157
+ type TestContext = {
158
+ routeParams: Record<string, string>;
159
+ queryParams: Record<string, string>;
160
+ };
161
+
162
+ const machineConfig = {
163
+ id: "testMachine",
164
+ initial: "home",
165
+ context: {
166
+ routeParams: {},
167
+ queryParams: {},
168
+ } satisfies TestContext,
169
+ states: {
170
+ home: {
171
+ id: "home",
172
+ meta: { route: "/" },
173
+ },
174
+ },
175
+ };
176
+
177
+ const machine = createMachine(formatPlayRouteTransitions(machineConfig));
178
+ const actor = createActor(machine);
179
+ actor.start();
180
+
181
+ // Verify context includes queryParams
182
+ const snapshot = actor.getSnapshot();
183
+
184
+ expect(snapshot.context).toHaveProperty("queryParams");
185
+ expect(typeof (snapshot.context as any).queryParams).toBe("object");
186
+ });
187
+ });