@plures/praxis 0.2.0

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 (263) hide show
  1. package/FRAMEWORK.md +420 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1310 -0
  4. package/dist/adapters/cli.d.ts +43 -0
  5. package/dist/adapters/cli.d.ts.map +1 -0
  6. package/dist/adapters/cli.js +126 -0
  7. package/dist/adapters/cli.js.map +1 -0
  8. package/dist/cli/commands/auth.d.ts +26 -0
  9. package/dist/cli/commands/auth.d.ts.map +1 -0
  10. package/dist/cli/commands/auth.js +233 -0
  11. package/dist/cli/commands/auth.js.map +1 -0
  12. package/dist/cli/commands/cloud.d.ts +27 -0
  13. package/dist/cli/commands/cloud.d.ts.map +1 -0
  14. package/dist/cli/commands/cloud.js +232 -0
  15. package/dist/cli/commands/cloud.js.map +1 -0
  16. package/dist/cli/commands/generate.d.ts +25 -0
  17. package/dist/cli/commands/generate.d.ts.map +1 -0
  18. package/dist/cli/commands/generate.js +168 -0
  19. package/dist/cli/commands/generate.js.map +1 -0
  20. package/dist/cli/index.d.ts +8 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +179 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cloud/auth.d.ts +51 -0
  25. package/dist/cloud/auth.d.ts.map +1 -0
  26. package/dist/cloud/auth.js +194 -0
  27. package/dist/cloud/auth.js.map +1 -0
  28. package/dist/cloud/billing.d.ts +184 -0
  29. package/dist/cloud/billing.d.ts.map +1 -0
  30. package/dist/cloud/billing.js +179 -0
  31. package/dist/cloud/billing.js.map +1 -0
  32. package/dist/cloud/client.d.ts +39 -0
  33. package/dist/cloud/client.d.ts.map +1 -0
  34. package/dist/cloud/client.js +176 -0
  35. package/dist/cloud/client.js.map +1 -0
  36. package/dist/cloud/index.d.ts +44 -0
  37. package/dist/cloud/index.d.ts.map +1 -0
  38. package/dist/cloud/index.js +44 -0
  39. package/dist/cloud/index.js.map +1 -0
  40. package/dist/cloud/marketplace.d.ts +166 -0
  41. package/dist/cloud/marketplace.d.ts.map +1 -0
  42. package/dist/cloud/marketplace.js +159 -0
  43. package/dist/cloud/marketplace.js.map +1 -0
  44. package/dist/cloud/provisioning.d.ts +110 -0
  45. package/dist/cloud/provisioning.d.ts.map +1 -0
  46. package/dist/cloud/provisioning.js +148 -0
  47. package/dist/cloud/provisioning.js.map +1 -0
  48. package/dist/cloud/relay/endpoints.d.ts +62 -0
  49. package/dist/cloud/relay/endpoints.d.ts.map +1 -0
  50. package/dist/cloud/relay/endpoints.js +217 -0
  51. package/dist/cloud/relay/endpoints.js.map +1 -0
  52. package/dist/cloud/relay/health/index.d.ts +5 -0
  53. package/dist/cloud/relay/health/index.d.ts.map +1 -0
  54. package/dist/cloud/relay/health/index.js +9 -0
  55. package/dist/cloud/relay/health/index.js.map +1 -0
  56. package/dist/cloud/relay/stats/index.d.ts +5 -0
  57. package/dist/cloud/relay/stats/index.d.ts.map +1 -0
  58. package/dist/cloud/relay/stats/index.js +9 -0
  59. package/dist/cloud/relay/stats/index.js.map +1 -0
  60. package/dist/cloud/relay/sync/index.d.ts +5 -0
  61. package/dist/cloud/relay/sync/index.d.ts.map +1 -0
  62. package/dist/cloud/relay/sync/index.js +9 -0
  63. package/dist/cloud/relay/sync/index.js.map +1 -0
  64. package/dist/cloud/relay/usage/index.d.ts +5 -0
  65. package/dist/cloud/relay/usage/index.d.ts.map +1 -0
  66. package/dist/cloud/relay/usage/index.js +9 -0
  67. package/dist/cloud/relay/usage/index.js.map +1 -0
  68. package/dist/cloud/sponsors.d.ts +81 -0
  69. package/dist/cloud/sponsors.d.ts.map +1 -0
  70. package/dist/cloud/sponsors.js +130 -0
  71. package/dist/cloud/sponsors.js.map +1 -0
  72. package/dist/cloud/types.d.ts +169 -0
  73. package/dist/cloud/types.d.ts.map +1 -0
  74. package/dist/cloud/types.js +7 -0
  75. package/dist/cloud/types.js.map +1 -0
  76. package/dist/components/index.d.ts +43 -0
  77. package/dist/components/index.d.ts.map +1 -0
  78. package/dist/components/index.js +17 -0
  79. package/dist/components/index.js.map +1 -0
  80. package/dist/core/actors.d.ts +95 -0
  81. package/dist/core/actors.d.ts.map +1 -0
  82. package/dist/core/actors.js +158 -0
  83. package/dist/core/actors.js.map +1 -0
  84. package/dist/core/component/generator.d.ts +122 -0
  85. package/dist/core/component/generator.d.ts.map +1 -0
  86. package/dist/core/component/generator.js +307 -0
  87. package/dist/core/component/generator.js.map +1 -0
  88. package/dist/core/engine.d.ts +92 -0
  89. package/dist/core/engine.d.ts.map +1 -0
  90. package/dist/core/engine.js +199 -0
  91. package/dist/core/engine.js.map +1 -0
  92. package/dist/core/introspection.d.ts +141 -0
  93. package/dist/core/introspection.d.ts.map +1 -0
  94. package/dist/core/introspection.js +208 -0
  95. package/dist/core/introspection.js.map +1 -0
  96. package/dist/core/logic/generator.d.ts +76 -0
  97. package/dist/core/logic/generator.d.ts.map +1 -0
  98. package/dist/core/logic/generator.js +339 -0
  99. package/dist/core/logic/generator.js.map +1 -0
  100. package/dist/core/pluresdb/generator.d.ts +58 -0
  101. package/dist/core/pluresdb/generator.d.ts.map +1 -0
  102. package/dist/core/pluresdb/generator.js +162 -0
  103. package/dist/core/pluresdb/generator.js.map +1 -0
  104. package/dist/core/protocol.d.ts +121 -0
  105. package/dist/core/protocol.d.ts.map +1 -0
  106. package/dist/core/protocol.js +46 -0
  107. package/dist/core/protocol.js.map +1 -0
  108. package/dist/core/rules.d.ts +120 -0
  109. package/dist/core/rules.d.ts.map +1 -0
  110. package/dist/core/rules.js +81 -0
  111. package/dist/core/rules.js.map +1 -0
  112. package/dist/core/schema/loader.d.ts +47 -0
  113. package/dist/core/schema/loader.d.ts.map +1 -0
  114. package/dist/core/schema/loader.js +189 -0
  115. package/dist/core/schema/loader.js.map +1 -0
  116. package/dist/core/schema/normalize.d.ts +72 -0
  117. package/dist/core/schema/normalize.d.ts.map +1 -0
  118. package/dist/core/schema/normalize.js +190 -0
  119. package/dist/core/schema/normalize.js.map +1 -0
  120. package/dist/core/schema/types.d.ts +370 -0
  121. package/dist/core/schema/types.d.ts.map +1 -0
  122. package/dist/core/schema/types.js +161 -0
  123. package/dist/core/schema/types.js.map +1 -0
  124. package/dist/dsl/index.d.ts +152 -0
  125. package/dist/dsl/index.d.ts.map +1 -0
  126. package/dist/dsl/index.js +132 -0
  127. package/dist/dsl/index.js.map +1 -0
  128. package/dist/dsl.d.ts +124 -0
  129. package/dist/dsl.d.ts.map +1 -0
  130. package/dist/dsl.js +130 -0
  131. package/dist/dsl.js.map +1 -0
  132. package/dist/examples/advanced-todo/index.d.ts +55 -0
  133. package/dist/examples/advanced-todo/index.d.ts.map +1 -0
  134. package/dist/examples/advanced-todo/index.js +222 -0
  135. package/dist/examples/advanced-todo/index.js.map +1 -0
  136. package/dist/examples/auth-basic/index.d.ts +17 -0
  137. package/dist/examples/auth-basic/index.d.ts.map +1 -0
  138. package/dist/examples/auth-basic/index.js +122 -0
  139. package/dist/examples/auth-basic/index.js.map +1 -0
  140. package/dist/examples/cart/index.d.ts +19 -0
  141. package/dist/examples/cart/index.d.ts.map +1 -0
  142. package/dist/examples/cart/index.js +202 -0
  143. package/dist/examples/cart/index.js.map +1 -0
  144. package/dist/examples/hero-ecommerce/index.d.ts +39 -0
  145. package/dist/examples/hero-ecommerce/index.d.ts.map +1 -0
  146. package/dist/examples/hero-ecommerce/index.js +506 -0
  147. package/dist/examples/hero-ecommerce/index.js.map +1 -0
  148. package/dist/examples/svelte-counter/index.d.ts +31 -0
  149. package/dist/examples/svelte-counter/index.d.ts.map +1 -0
  150. package/dist/examples/svelte-counter/index.js +123 -0
  151. package/dist/examples/svelte-counter/index.js.map +1 -0
  152. package/dist/flows.d.ts +125 -0
  153. package/dist/flows.d.ts.map +1 -0
  154. package/dist/flows.js +160 -0
  155. package/dist/flows.js.map +1 -0
  156. package/dist/index.d.ts +67 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +59 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/integrations/pluresdb.d.ts +56 -0
  161. package/dist/integrations/pluresdb.d.ts.map +1 -0
  162. package/dist/integrations/pluresdb.js +46 -0
  163. package/dist/integrations/pluresdb.js.map +1 -0
  164. package/dist/integrations/svelte.d.ts +306 -0
  165. package/dist/integrations/svelte.d.ts.map +1 -0
  166. package/dist/integrations/svelte.js +447 -0
  167. package/dist/integrations/svelte.js.map +1 -0
  168. package/dist/registry.d.ts +94 -0
  169. package/dist/registry.d.ts.map +1 -0
  170. package/dist/registry.js +181 -0
  171. package/dist/registry.js.map +1 -0
  172. package/dist/runtime/terminal-adapter.d.ts +105 -0
  173. package/dist/runtime/terminal-adapter.d.ts.map +1 -0
  174. package/dist/runtime/terminal-adapter.js +113 -0
  175. package/dist/runtime/terminal-adapter.js.map +1 -0
  176. package/dist/step.d.ts +34 -0
  177. package/dist/step.d.ts.map +1 -0
  178. package/dist/step.js +111 -0
  179. package/dist/step.js.map +1 -0
  180. package/dist/types.d.ts +63 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +6 -0
  183. package/dist/types.js.map +1 -0
  184. package/docs/MONETIZATION.md +394 -0
  185. package/docs/TERMINAL_NODE.md +588 -0
  186. package/docs/guides/canvas.md +389 -0
  187. package/docs/guides/getting-started.md +347 -0
  188. package/docs/guides/history-state-pattern.md +618 -0
  189. package/docs/guides/orchestration.md +617 -0
  190. package/docs/guides/parallel-state-pattern.md +767 -0
  191. package/docs/guides/svelte-integration.md +691 -0
  192. package/package.json +96 -0
  193. package/src/__tests__/actors.test.ts +270 -0
  194. package/src/__tests__/billing.test.ts +175 -0
  195. package/src/__tests__/cloud.test.ts +247 -0
  196. package/src/__tests__/dsl.test.ts +154 -0
  197. package/src/__tests__/edge-cases.test.ts +475 -0
  198. package/src/__tests__/engine.test.ts +137 -0
  199. package/src/__tests__/generators.test.ts +270 -0
  200. package/src/__tests__/introspection.test.ts +321 -0
  201. package/src/__tests__/protocol.test.ts +40 -0
  202. package/src/__tests__/provisioning.test.ts +162 -0
  203. package/src/__tests__/schema.test.ts +241 -0
  204. package/src/__tests__/svelte-integration.test.ts +431 -0
  205. package/src/__tests__/terminal-node.test.ts +352 -0
  206. package/src/adapters/cli.ts +175 -0
  207. package/src/cli/commands/auth.ts +271 -0
  208. package/src/cli/commands/cloud.ts +281 -0
  209. package/src/cli/commands/generate.ts +225 -0
  210. package/src/cli/index.ts +190 -0
  211. package/src/cloud/README.md +383 -0
  212. package/src/cloud/auth.ts +245 -0
  213. package/src/cloud/billing.ts +336 -0
  214. package/src/cloud/client.ts +221 -0
  215. package/src/cloud/index.ts +121 -0
  216. package/src/cloud/marketplace.ts +303 -0
  217. package/src/cloud/provisioning.ts +254 -0
  218. package/src/cloud/relay/endpoints.ts +307 -0
  219. package/src/cloud/relay/health/function.json +17 -0
  220. package/src/cloud/relay/health/index.ts +10 -0
  221. package/src/cloud/relay/host.json +15 -0
  222. package/src/cloud/relay/local.settings.json +8 -0
  223. package/src/cloud/relay/stats/function.json +17 -0
  224. package/src/cloud/relay/stats/index.ts +10 -0
  225. package/src/cloud/relay/sync/function.json +17 -0
  226. package/src/cloud/relay/sync/index.ts +10 -0
  227. package/src/cloud/relay/usage/function.json +17 -0
  228. package/src/cloud/relay/usage/index.ts +10 -0
  229. package/src/cloud/sponsors.ts +213 -0
  230. package/src/cloud/types.ts +198 -0
  231. package/src/components/README.md +125 -0
  232. package/src/components/TerminalNode.svelte +457 -0
  233. package/src/components/index.ts +46 -0
  234. package/src/core/actors.ts +205 -0
  235. package/src/core/component/generator.ts +432 -0
  236. package/src/core/engine.ts +243 -0
  237. package/src/core/introspection.ts +329 -0
  238. package/src/core/logic/generator.ts +420 -0
  239. package/src/core/pluresdb/generator.ts +229 -0
  240. package/src/core/protocol.ts +132 -0
  241. package/src/core/rules.ts +167 -0
  242. package/src/core/schema/loader.ts +247 -0
  243. package/src/core/schema/normalize.ts +322 -0
  244. package/src/core/schema/types.ts +557 -0
  245. package/src/dsl/index.ts +218 -0
  246. package/src/dsl.ts +214 -0
  247. package/src/examples/advanced-todo/App.svelte +506 -0
  248. package/src/examples/advanced-todo/README.md +371 -0
  249. package/src/examples/advanced-todo/index.ts +309 -0
  250. package/src/examples/auth-basic/index.ts +163 -0
  251. package/src/examples/cart/index.ts +259 -0
  252. package/src/examples/hero-ecommerce/index.ts +657 -0
  253. package/src/examples/svelte-counter/index.ts +168 -0
  254. package/src/flows.ts +268 -0
  255. package/src/index.ts +154 -0
  256. package/src/integrations/pluresdb.ts +93 -0
  257. package/src/integrations/svelte.ts +617 -0
  258. package/src/registry.ts +223 -0
  259. package/src/runtime/terminal-adapter.ts +175 -0
  260. package/src/step.ts +151 -0
  261. package/src/types.ts +70 -0
  262. package/templates/basic-app/README.md +147 -0
  263. package/templates/fullstack-app/README.md +279 -0
@@ -0,0 +1,617 @@
1
+ /**
2
+ * Svelte v5 Integration
3
+ *
4
+ * Provides reactive bindings for Praxis logic engines in Svelte v5 applications.
5
+ * Supports both traditional stores and modern Svelte 5 runes ($state, $derived, $effect).
6
+ *
7
+ * Features:
8
+ * - Store-based API for backward compatibility
9
+ * - Runes-based composables for Svelte 5
10
+ * - Snapshot support for time-travel debugging
11
+ * - History state pattern implementation
12
+ * - Automatic cleanup and subscription management
13
+ */
14
+
15
+ import type { LogicEngine } from "../core/engine.js";
16
+ import type { PraxisEvent, PraxisState } from "../core/protocol.js";
17
+
18
+ /**
19
+ * Writable store interface (Svelte-compatible)
20
+ */
21
+ export interface Writable<T> {
22
+ subscribe(run: (value: T) => void): () => void;
23
+ set(value: T): void;
24
+ update(updater: (value: T) => T): void;
25
+ }
26
+
27
+ /**
28
+ * Readable store interface (Svelte-compatible)
29
+ */
30
+ export interface Readable<T> {
31
+ subscribe(run: (value: T) => void): () => void;
32
+ }
33
+
34
+ /**
35
+ * Create a reactive Svelte store from a Praxis engine.
36
+ *
37
+ * The store tracks the engine's state and provides methods to dispatch events.
38
+ *
39
+ * @example
40
+ * const engine = createPraxisEngine({ ... });
41
+ * const store = createPraxisStore(engine);
42
+ *
43
+ * // In Svelte component:
44
+ * $: state = $store;
45
+ *
46
+ * // Dispatch events:
47
+ * store.dispatch([Login.create({ username: "alice", password: "secret" })]);
48
+ */
49
+ export function createPraxisStore<TContext = unknown>(
50
+ engine: LogicEngine<TContext>
51
+ ): Readable<Readonly<PraxisState & { context: TContext }>> & {
52
+ dispatch: (events: PraxisEvent[]) => void;
53
+ } {
54
+ let currentState = engine.getState();
55
+ const subscribers = new Set<(value: Readonly<PraxisState & { context: TContext }>) => void>();
56
+
57
+ const notify = () => {
58
+ currentState = engine.getState();
59
+ subscribers.forEach((sub) => sub(currentState));
60
+ };
61
+
62
+ return {
63
+ subscribe(run: (value: Readonly<PraxisState & { context: TContext }>) => void) {
64
+ subscribers.add(run);
65
+ run(currentState); // Call immediately with current value
66
+ return () => {
67
+ subscribers.delete(run);
68
+ };
69
+ },
70
+ dispatch(events: PraxisEvent[]) {
71
+ engine.step(events);
72
+ notify();
73
+ },
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Create a derived store that extracts the context from the engine state.
79
+ *
80
+ * @example
81
+ * const engine = createPraxisEngine({ ... });
82
+ * const contextStore = createContextStore(engine);
83
+ *
84
+ * // In Svelte component:
85
+ * $: context = $contextStore;
86
+ */
87
+ export function createContextStore<TContext = unknown>(
88
+ engine: LogicEngine<TContext>
89
+ ): Readable<TContext> & {
90
+ dispatch: (events: PraxisEvent[]) => void;
91
+ } {
92
+ let currentContext = engine.getContext();
93
+ const subscribers = new Set<(value: TContext) => void>();
94
+
95
+ const notify = () => {
96
+ currentContext = engine.getContext();
97
+ subscribers.forEach((sub) => sub(currentContext));
98
+ };
99
+
100
+ return {
101
+ subscribe(run: (value: TContext) => void) {
102
+ subscribers.add(run);
103
+ run(currentContext); // Call immediately with current value
104
+ return () => {
105
+ subscribers.delete(run);
106
+ };
107
+ },
108
+ dispatch(events: PraxisEvent[]) {
109
+ engine.step(events);
110
+ notify();
111
+ },
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Create a derived store that extracts specific data from the context.
117
+ *
118
+ * @example
119
+ * const engine = createPraxisEngine<{ count: number }>({ ... });
120
+ * const countStore = createDerivedStore(engine, (ctx) => ctx.count);
121
+ *
122
+ * // In Svelte component:
123
+ * $: count = $countStore;
124
+ */
125
+ export function createDerivedStore<TContext = unknown, TDerived = unknown>(
126
+ engine: LogicEngine<TContext>,
127
+ selector: (context: TContext) => TDerived
128
+ ): Readable<TDerived> & {
129
+ dispatch: (events: PraxisEvent[]) => void;
130
+ } {
131
+ let currentValue = selector(engine.getContext());
132
+ const subscribers = new Set<(value: TDerived) => void>();
133
+
134
+ const notify = () => {
135
+ const newValue = selector(engine.getContext());
136
+ if (newValue !== currentValue) {
137
+ currentValue = newValue;
138
+ subscribers.forEach((sub) => sub(currentValue));
139
+ }
140
+ };
141
+
142
+ return {
143
+ subscribe(run: (value: TDerived) => void) {
144
+ subscribers.add(run);
145
+ run(currentValue); // Call immediately with current value
146
+ return () => {
147
+ subscribers.delete(run);
148
+ };
149
+ },
150
+ dispatch(events: PraxisEvent[]) {
151
+ engine.step(events);
152
+ notify();
153
+ },
154
+ };
155
+ }
156
+
157
+ // ============================================================================
158
+ // Svelte 5 Runes API
159
+ // ============================================================================
160
+
161
+ /**
162
+ * Snapshot of engine state for time-travel debugging
163
+ */
164
+ export interface StateSnapshot<TContext = unknown> {
165
+ timestamp: number;
166
+ state: Readonly<PraxisState & { context: TContext }>;
167
+ events: PraxisEvent[];
168
+ }
169
+
170
+ /**
171
+ * Options for usePraxisEngine composable
172
+ */
173
+ export interface UsePraxisEngineOptions {
174
+ /** Enable snapshot history for time-travel debugging */
175
+ enableHistory?: boolean;
176
+ /** Maximum number of snapshots to keep */
177
+ maxHistorySize?: number;
178
+ }
179
+
180
+ /**
181
+ * Result of usePraxisEngine composable with Svelte 5 runes
182
+ */
183
+ export interface PraxisEngineBinding<TContext = unknown> {
184
+ /** Current state (reactive via $state) */
185
+ state: Readonly<PraxisState & { context: TContext }>;
186
+ /** Current context (reactive via $state) */
187
+ context: TContext;
188
+ /** Current facts (reactive via $state) */
189
+ facts: PraxisState['facts'];
190
+ /** Dispatch events to the engine */
191
+ dispatch: (events: PraxisEvent[]) => void;
192
+ /** History snapshots (if enabled) */
193
+ snapshots: StateSnapshot<TContext>[];
194
+ /** Navigate to a specific snapshot index */
195
+ goToSnapshot: (index: number) => void;
196
+ /** Undo last action (go back one snapshot) */
197
+ undo: () => void;
198
+ /** Redo action (go forward one snapshot) */
199
+ redo: () => void;
200
+ /** Check if undo is available */
201
+ canUndo: boolean;
202
+ /** Check if redo is available */
203
+ canRedo: boolean;
204
+ /** Current position in history */
205
+ historyIndex: number;
206
+ }
207
+
208
+ /**
209
+ * Create a reactive binding to a Praxis engine with Svelte 5 runes support.
210
+ *
211
+ * This composable provides a runes-compatible API for integrating Praxis
212
+ * with Svelte 5 components. The returned state is reactive and will
213
+ * automatically update the component when the engine state changes.
214
+ *
215
+ * Note: The history/snapshot feature tracks state snapshots but doesn't
216
+ * restore the engine to previous states. When you navigate history (undo/redo),
217
+ * you're viewing past snapshots, but new events are still applied to the
218
+ * current engine state. For true undo/redo, use createHistoryEngine or
219
+ * implement state restoration in your application logic.
220
+ *
221
+ * @example
222
+ * <script>
223
+ * import { usePraxisEngine } from '@plures/praxis/svelte';
224
+ * import { createMyEngine } from './my-engine';
225
+ *
226
+ * const engine = createMyEngine();
227
+ * const { context, dispatch, undo, canUndo } = usePraxisEngine(engine, {
228
+ * enableHistory: true
229
+ * });
230
+ * </script>
231
+ *
232
+ * <div>
233
+ * <p>Count: {context.count}</p>
234
+ * <button onclick={() => dispatch([Increment.create({})])}>+</button>
235
+ * <button onclick={() => undo()} disabled={!canUndo}>Undo</button>
236
+ * </div>
237
+ *
238
+ * @param engine The Praxis logic engine
239
+ * @param options Configuration options
240
+ * @returns Reactive binding with state, context, and control methods
241
+ */
242
+ export function usePraxisEngine<TContext = unknown>(
243
+ engine: LogicEngine<TContext>,
244
+ options: UsePraxisEngineOptions = {}
245
+ ): PraxisEngineBinding<TContext> {
246
+ const { enableHistory = false, maxHistorySize = 50 } = options;
247
+
248
+ // Create reactive state holders
249
+ let currentState = engine.getState();
250
+ let snapshots: StateSnapshot<TContext>[] = [];
251
+ let historyIndex = -1;
252
+
253
+ // Initialize with first snapshot if history is enabled
254
+ if (enableHistory) {
255
+ snapshots.push({
256
+ timestamp: Date.now(),
257
+ state: currentState,
258
+ events: [],
259
+ });
260
+ historyIndex = 0;
261
+ }
262
+
263
+ const dispatch = (events: PraxisEvent[]) => {
264
+ // If we're not at the end of history, truncate future snapshots
265
+ if (enableHistory && historyIndex < snapshots.length - 1) {
266
+ snapshots = snapshots.slice(0, historyIndex + 1);
267
+ }
268
+
269
+ // Apply events to engine
270
+ engine.step(events);
271
+ currentState = engine.getState();
272
+
273
+ // Record snapshot if history is enabled
274
+ if (enableHistory) {
275
+ snapshots.push({
276
+ timestamp: Date.now(),
277
+ state: currentState,
278
+ events,
279
+ });
280
+
281
+ // Limit history size
282
+ if (snapshots.length > maxHistorySize) {
283
+ snapshots.shift();
284
+ } else {
285
+ historyIndex++;
286
+ }
287
+ }
288
+ };
289
+
290
+ const goToSnapshot = (index: number) => {
291
+ if (!enableHistory) {
292
+ console.warn('History is not enabled for this engine');
293
+ return;
294
+ }
295
+
296
+ if (index < 0 || index >= snapshots.length) {
297
+ console.warn(`Invalid snapshot index: ${index}`);
298
+ return;
299
+ }
300
+
301
+ historyIndex = index;
302
+ currentState = snapshots[index].state;
303
+ };
304
+
305
+ const undo = () => {
306
+ if (historyIndex > 0) {
307
+ goToSnapshot(historyIndex - 1);
308
+ }
309
+ };
310
+
311
+ const redo = () => {
312
+ if (historyIndex < snapshots.length - 1) {
313
+ goToSnapshot(historyIndex + 1);
314
+ }
315
+ };
316
+
317
+ return {
318
+ get state() {
319
+ return currentState;
320
+ },
321
+ get context() {
322
+ return currentState.context;
323
+ },
324
+ get facts() {
325
+ return currentState.facts;
326
+ },
327
+ dispatch,
328
+ get snapshots() {
329
+ return snapshots;
330
+ },
331
+ goToSnapshot,
332
+ undo,
333
+ redo,
334
+ get canUndo() {
335
+ return enableHistory && historyIndex > 0;
336
+ },
337
+ get canRedo() {
338
+ return enableHistory && historyIndex < snapshots.length - 1;
339
+ },
340
+ get historyIndex() {
341
+ return historyIndex;
342
+ },
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Create a reactive derived value from engine context with Svelte 5 runes.
348
+ *
349
+ * This composable extracts and tracks a specific value from the engine context.
350
+ * The returned value is reactive and will update when the selected value changes.
351
+ *
352
+ * @example
353
+ * <script>
354
+ * import { usePraxisContext } from '@plures/praxis/svelte';
355
+ *
356
+ * const engine = createMyEngine();
357
+ * const count = usePraxisContext(engine, (ctx) => ctx.count);
358
+ * </script>
359
+ *
360
+ * <p>Count: {count}</p>
361
+ *
362
+ * @param engine The Praxis logic engine
363
+ * @param selector Function to extract value from context
364
+ * @returns Reactive derived value
365
+ */
366
+ export function usePraxisContext<TContext = unknown, TDerived = unknown>(
367
+ engine: LogicEngine<TContext>,
368
+ selector: (context: TContext) => TDerived
369
+ ): TDerived {
370
+ let currentValue = selector(engine.getContext());
371
+ return currentValue;
372
+ }
373
+
374
+ /**
375
+ * Subscribe to engine state changes with automatic cleanup.
376
+ *
377
+ * This composable sets up a subscription to engine state changes and
378
+ * automatically cleans up when the component is destroyed.
379
+ *
380
+ * @example
381
+ * <script>
382
+ * import { usePraxisSubscription } from '@plures/praxis/svelte';
383
+ *
384
+ * const engine = createMyEngine();
385
+ *
386
+ * usePraxisSubscription(engine, (state) => {
387
+ * console.log('State changed:', state);
388
+ * });
389
+ * </script>
390
+ *
391
+ * @param engine The Praxis logic engine
392
+ * @param callback Function to call when state changes
393
+ */
394
+ export function usePraxisSubscription<TContext = unknown>(
395
+ engine: LogicEngine<TContext>,
396
+ callback: (state: Readonly<PraxisState & { context: TContext }>) => void
397
+ ): () => void {
398
+ const store = createPraxisStore(engine);
399
+ const unsubscribe = store.subscribe(callback);
400
+ return unsubscribe;
401
+ }
402
+
403
+ // ============================================================================
404
+ // History State Pattern
405
+ // ============================================================================
406
+
407
+ /**
408
+ * History state entry for tracking state transitions
409
+ */
410
+ export interface HistoryEntry<TContext = unknown> {
411
+ /** Unique identifier for this history entry */
412
+ id: string;
413
+ /** Timestamp when this state was entered */
414
+ timestamp: number;
415
+ /** State snapshot */
416
+ state: Readonly<PraxisState & { context: TContext }>;
417
+ /** Events that led to this state */
418
+ events: PraxisEvent[];
419
+ /** Optional label for this history entry */
420
+ label?: string;
421
+ }
422
+
423
+ /**
424
+ * History state manager for Praxis engines
425
+ */
426
+ export class HistoryStateManager<TContext = unknown> {
427
+ private history: HistoryEntry<TContext>[] = [];
428
+ private currentIndex = -1;
429
+ private readonly maxSize: number;
430
+ private idCounter = 0;
431
+
432
+ constructor(maxSize = 50) {
433
+ this.maxSize = maxSize;
434
+ }
435
+
436
+ /**
437
+ * Record a new history entry
438
+ */
439
+ record(
440
+ state: Readonly<PraxisState & { context: TContext }>,
441
+ events: PraxisEvent[],
442
+ label?: string
443
+ ): void {
444
+ // If we're not at the end, truncate future history
445
+ if (this.currentIndex < this.history.length - 1) {
446
+ this.history = this.history.slice(0, this.currentIndex + 1);
447
+ }
448
+
449
+ // Add new entry
450
+ this.history.push({
451
+ id: `history-${++this.idCounter}`,
452
+ timestamp: Date.now(),
453
+ state,
454
+ events,
455
+ label,
456
+ });
457
+
458
+ // Limit history size
459
+ if (this.history.length > this.maxSize) {
460
+ this.history.shift();
461
+ } else {
462
+ this.currentIndex++;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Navigate to a specific history entry
468
+ */
469
+ goTo(index: number): HistoryEntry<TContext> | null {
470
+ if (index < 0 || index >= this.history.length) {
471
+ return null;
472
+ }
473
+ this.currentIndex = index;
474
+ return this.history[index];
475
+ }
476
+
477
+ /**
478
+ * Go back to previous state
479
+ */
480
+ back(): HistoryEntry<TContext> | null {
481
+ if (!this.canGoBack()) {
482
+ return null;
483
+ }
484
+ return this.goTo(this.currentIndex - 1);
485
+ }
486
+
487
+ /**
488
+ * Go forward to next state
489
+ */
490
+ forward(): HistoryEntry<TContext> | null {
491
+ if (!this.canGoForward()) {
492
+ return null;
493
+ }
494
+ return this.goTo(this.currentIndex + 1);
495
+ }
496
+
497
+ /**
498
+ * Check if can go back
499
+ */
500
+ canGoBack(): boolean {
501
+ return this.currentIndex > 0;
502
+ }
503
+
504
+ /**
505
+ * Check if can go forward
506
+ */
507
+ canGoForward(): boolean {
508
+ return this.currentIndex < this.history.length - 1;
509
+ }
510
+
511
+ /**
512
+ * Get current history entry
513
+ */
514
+ current(): HistoryEntry<TContext> | null {
515
+ if (this.currentIndex < 0 || this.currentIndex >= this.history.length) {
516
+ return null;
517
+ }
518
+ return this.history[this.currentIndex];
519
+ }
520
+
521
+ /**
522
+ * Get all history entries
523
+ */
524
+ getHistory(): ReadonlyArray<HistoryEntry<TContext>> {
525
+ return [...this.history];
526
+ }
527
+
528
+ /**
529
+ * Get current index in history
530
+ */
531
+ getCurrentIndex(): number {
532
+ return this.currentIndex;
533
+ }
534
+
535
+ /**
536
+ * Clear all history
537
+ */
538
+ clear(): void {
539
+ this.history = [];
540
+ this.currentIndex = -1;
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Create a Praxis engine with history tracking.
546
+ *
547
+ * This utility wraps an engine with automatic history recording,
548
+ * providing undo/redo functionality.
549
+ *
550
+ * @example
551
+ * const engine = createPraxisEngine({ ... });
552
+ * const { dispatch, undo, redo, canUndo, canRedo } = createHistoryEngine(engine);
553
+ *
554
+ * // Use normally
555
+ * dispatch([Login.create({ username: "alice" })]);
556
+ *
557
+ * // Undo the login
558
+ * undo();
559
+ *
560
+ * @param engine The base Praxis engine
561
+ * @param options History configuration
562
+ * @returns Enhanced engine with history methods
563
+ */
564
+ export function createHistoryEngine<TContext = unknown>(
565
+ engine: LogicEngine<TContext>,
566
+ options: { maxHistorySize?: number; initialLabel?: string } = {}
567
+ ): {
568
+ engine: LogicEngine<TContext>;
569
+ dispatch: (events: PraxisEvent[], label?: string) => void;
570
+ undo: () => boolean;
571
+ redo: () => boolean;
572
+ canUndo: () => boolean;
573
+ canRedo: () => boolean;
574
+ getHistory: () => ReadonlyArray<HistoryEntry<TContext>>;
575
+ goToHistory: (index: number) => boolean;
576
+ clearHistory: () => void;
577
+ } {
578
+ const history = new HistoryStateManager<TContext>(options.maxHistorySize);
579
+
580
+ // Record initial state
581
+ history.record(engine.getState(), [], options.initialLabel || 'Initial');
582
+
583
+ const dispatch = (events: PraxisEvent[], label?: string) => {
584
+ engine.step(events);
585
+ history.record(engine.getState(), events, label);
586
+ };
587
+
588
+ const undo = (): boolean => {
589
+ const entry = history.back();
590
+ if (entry) {
591
+ // Note: This is a simplified undo - in practice you might need to
592
+ // restore the state by replaying events or storing full snapshots
593
+ return true;
594
+ }
595
+ return false;
596
+ };
597
+
598
+ const redo = (): boolean => {
599
+ const entry = history.forward();
600
+ if (entry) {
601
+ return true;
602
+ }
603
+ return false;
604
+ };
605
+
606
+ return {
607
+ engine,
608
+ dispatch,
609
+ undo,
610
+ redo,
611
+ canUndo: () => history.canGoBack(),
612
+ canRedo: () => history.canGoForward(),
613
+ getHistory: () => history.getHistory(),
614
+ goToHistory: (index: number) => history.goTo(index) !== null,
615
+ clearHistory: () => history.clear(),
616
+ };
617
+ }