@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,618 @@
1
+ # History State Pattern in Praxis
2
+
3
+ This guide explains how to implement and use the history state pattern in Praxis, similar to XState's history states.
4
+
5
+ ## Overview
6
+
7
+ The history state pattern allows you to track and navigate through state transitions in your application. This is particularly useful for:
8
+
9
+ - **Undo/Redo functionality**: Let users reverse actions
10
+ - **Time-travel debugging**: Inspect and navigate application state history
11
+ - **Authentication flows**: Return to previous states after authentication
12
+ - **Complex workflows**: Remember where users were before interruptions
13
+
14
+ ## Table of Contents
15
+
16
+ 1. [Basic History Tracking](#basic-history-tracking)
17
+ 2. [Using History State Manager](#using-history-state-manager)
18
+ 3. [History Engine Wrapper](#history-engine-wrapper)
19
+ 4. [Svelte 5 Integration](#svelte-5-integration)
20
+ 5. [Authentication Flow Example](#authentication-flow-example)
21
+ 6. [Best Practices](#best-practices)
22
+
23
+ ## Basic History Tracking
24
+
25
+ The simplest way to track history is using the `HistoryStateManager`:
26
+
27
+ ```typescript
28
+ import { HistoryStateManager } from '@plures/praxis/svelte';
29
+
30
+ // Create a history manager with max 50 entries
31
+ const history = new HistoryStateManager<MyContext>(50);
32
+
33
+ // Record state changes
34
+ history.record(engine.getState(), events, 'Login Action');
35
+
36
+ // Navigate history
37
+ if (history.canGoBack()) {
38
+ const previousState = history.back();
39
+ console.log('Previous state:', previousState);
40
+ }
41
+
42
+ if (history.canGoForward()) {
43
+ const nextState = history.forward();
44
+ console.log('Next state:', nextState);
45
+ }
46
+
47
+ // Get all history
48
+ const allHistory = history.getHistory();
49
+ console.log(`Total history entries: ${allHistory.length}`);
50
+ ```
51
+
52
+ ## Using History State Manager
53
+
54
+ The `HistoryStateManager` provides full control over state history:
55
+
56
+ ### Recording History
57
+
58
+ ```typescript
59
+ import { createPraxisEngine, HistoryStateManager } from '@plures/praxis';
60
+
61
+ interface AppContext {
62
+ user: string | null;
63
+ page: string;
64
+ }
65
+
66
+ const engine = createPraxisEngine<AppContext>({ /* ... */ });
67
+ const history = new HistoryStateManager<AppContext>(100);
68
+
69
+ // Record initial state
70
+ history.record(engine.getState(), [], 'Initial State');
71
+
72
+ // Process events and record
73
+ const events = [Login.create({ username: 'alice' })];
74
+ engine.step(events);
75
+ history.record(engine.getState(), events, 'User Login');
76
+ ```
77
+
78
+ ### Navigating History
79
+
80
+ ```typescript
81
+ // Go back one step
82
+ const previousEntry = history.back();
83
+ if (previousEntry) {
84
+ console.log('Moved to:', previousEntry.label);
85
+ console.log('State:', previousEntry.state);
86
+ }
87
+
88
+ // Go forward one step
89
+ const nextEntry = history.forward();
90
+
91
+ // Jump to specific point
92
+ const entry = history.goTo(5);
93
+
94
+ // Check navigation availability
95
+ console.log('Can undo:', history.canGoBack());
96
+ console.log('Can redo:', history.canGoForward());
97
+ console.log('Current position:', history.getCurrentIndex());
98
+ ```
99
+
100
+ ### Inspecting History
101
+
102
+ ```typescript
103
+ // Get current state
104
+ const current = history.current();
105
+ if (current) {
106
+ console.log('Current:', current.label);
107
+ console.log('Timestamp:', new Date(current.timestamp));
108
+ console.log('Events:', current.events);
109
+ }
110
+
111
+ // Get all history entries
112
+ const allEntries = history.getHistory();
113
+ allEntries.forEach((entry, index) => {
114
+ console.log(`${index}: ${entry.label} at ${new Date(entry.timestamp)}`);
115
+ });
116
+
117
+ // Clear history
118
+ history.clear();
119
+ ```
120
+
121
+ ## History Engine Wrapper
122
+
123
+ For automatic history tracking, use `createHistoryEngine`:
124
+
125
+ ```typescript
126
+ import { createPraxisEngine, createHistoryEngine } from '@plures/praxis/svelte';
127
+
128
+ // Create base engine
129
+ const baseEngine = createPraxisEngine<AppContext>({
130
+ initialContext: { user: null, page: 'home' },
131
+ registry,
132
+ });
133
+
134
+ // Wrap with history tracking
135
+ const historyEngine = createHistoryEngine(baseEngine, {
136
+ maxHistorySize: 50,
137
+ initialLabel: 'App Started',
138
+ });
139
+
140
+ // Use like normal engine, but with history
141
+ historyEngine.dispatch([Login.create({ username: 'alice' })], 'User Login');
142
+
143
+ // Undo/redo
144
+ if (historyEngine.canUndo()) {
145
+ historyEngine.undo();
146
+ }
147
+
148
+ if (historyEngine.canRedo()) {
149
+ historyEngine.redo();
150
+ }
151
+
152
+ // Navigate to specific point
153
+ historyEngine.goToHistory(3);
154
+
155
+ // Inspect history
156
+ const history = historyEngine.getHistory();
157
+ history.forEach((entry) => {
158
+ console.log(`${entry.label}: ${entry.events.length} events`);
159
+ });
160
+
161
+ // Clear history if needed
162
+ historyEngine.clearHistory();
163
+ ```
164
+
165
+ ## Svelte 5 Integration
166
+
167
+ Praxis provides first-class Svelte 5 support with runes:
168
+
169
+ ### Using `usePraxisEngine` with History
170
+
171
+ ```svelte
172
+ <script lang="ts">
173
+ import { usePraxisEngine } from '@plures/praxis/svelte';
174
+ import { createMyEngine, Login, Logout } from './my-engine';
175
+
176
+ const engine = createMyEngine();
177
+ const {
178
+ context,
179
+ dispatch,
180
+ undo,
181
+ redo,
182
+ canUndo,
183
+ canRedo,
184
+ snapshots,
185
+ historyIndex
186
+ } = usePraxisEngine(engine, {
187
+ enableHistory: true,
188
+ maxHistorySize: 50
189
+ });
190
+ </script>
191
+
192
+ <div class="app">
193
+ <header>
194
+ <h1>Welcome {context.user?.name || 'Guest'}</h1>
195
+ <p>History: {historyIndex + 1} / {snapshots.length}</p>
196
+ </header>
197
+
198
+ <main>
199
+ {#if !context.user}
200
+ <button onclick={() => dispatch([Login.create({ username: 'alice' })])}>
201
+ Login
202
+ </button>
203
+ {:else}
204
+ <button onclick={() => dispatch([Logout.create({})])}>
205
+ Logout
206
+ </button>
207
+ {/if}
208
+ </main>
209
+
210
+ <footer>
211
+ <button onclick={undo} disabled={!canUndo}>
212
+ Undo
213
+ </button>
214
+ <button onclick={redo} disabled={!canRedo}>
215
+ Redo
216
+ </button>
217
+ </footer>
218
+ </div>
219
+ ```
220
+
221
+ ### Time-Travel Debugging
222
+
223
+ ```svelte
224
+ <script lang="ts">
225
+ import { usePraxisEngine } from '@plures/praxis/svelte';
226
+ import { createMyEngine } from './my-engine';
227
+
228
+ const engine = createMyEngine();
229
+ const { context, snapshots, goToSnapshot, historyIndex } =
230
+ usePraxisEngine(engine, { enableHistory: true });
231
+ </script>
232
+
233
+ <div class="debugger">
234
+ <h2>Time-Travel Debugger</h2>
235
+
236
+ <div class="timeline">
237
+ {#each snapshots as snapshot, index}
238
+ <button
239
+ class:active={index === historyIndex}
240
+ onclick={() => goToSnapshot(index)}
241
+ >
242
+ {new Date(snapshot.timestamp).toLocaleTimeString()}
243
+ {#if snapshot.events.length > 0}
244
+ ({snapshot.events.length} events)
245
+ {/if}
246
+ </button>
247
+ {/each}
248
+ </div>
249
+
250
+ <div class="state-view">
251
+ <h3>Current State</h3>
252
+ <pre>{JSON.stringify(context, null, 2)}</pre>
253
+ </div>
254
+ </div>
255
+ ```
256
+
257
+ ## Authentication Flow Example
258
+
259
+ Here's a complete example showing how to use history states for auth flows:
260
+
261
+ ```typescript
262
+ // auth-engine.ts
263
+ import {
264
+ createPraxisEngine,
265
+ PraxisRegistry,
266
+ defineFact,
267
+ defineEvent,
268
+ defineRule,
269
+ findEvent,
270
+ } from '@plures/praxis';
271
+
272
+ interface AuthContext {
273
+ user: { id: string; name: string } | null;
274
+ previousPage: string | null;
275
+ currentPage: string;
276
+ attemptedSecurePage: string | null;
277
+ }
278
+
279
+ // Define facts
280
+ const UserAuthenticated = defineFact<'UserAuthenticated', {
281
+ userId: string;
282
+ name: string
283
+ }>('UserAuthenticated');
284
+
285
+ const NavigatedToPage = defineFact<'NavigatedToPage', {
286
+ page: string
287
+ }>('NavigatedToPage');
288
+
289
+ // Define events
290
+ const Login = defineEvent<'LOGIN', {
291
+ username: string;
292
+ password: string
293
+ }>('LOGIN');
294
+
295
+ const Logout = defineEvent<'LOGOUT', {}>('LOGOUT');
296
+
297
+ const NavigateTo = defineEvent<'NAVIGATE_TO', {
298
+ page: string;
299
+ requiresAuth?: boolean;
300
+ }>('NAVIGATE_TO');
301
+
302
+ // Define rules
303
+ const loginRule = defineRule<AuthContext>({
304
+ id: 'auth.login',
305
+ description: 'Authenticate user',
306
+ impl: (state, events) => {
307
+ const loginEvent = findEvent(events, Login);
308
+ if (!loginEvent) return [];
309
+
310
+ // Simulate authentication
311
+ const { username } = loginEvent.payload;
312
+ state.context.user = { id: username, name: username };
313
+
314
+ // Return to attempted secure page if exists
315
+ if (state.context.attemptedSecurePage) {
316
+ state.context.currentPage = state.context.attemptedSecurePage;
317
+ state.context.attemptedSecurePage = null;
318
+ }
319
+
320
+ return [
321
+ UserAuthenticated.create({ userId: username, name: username })
322
+ ];
323
+ },
324
+ });
325
+
326
+ const logoutRule = defineRule<AuthContext>({
327
+ id: 'auth.logout',
328
+ description: 'Log out user',
329
+ impl: (state, events) => {
330
+ const logoutEvent = findEvent(events, Logout);
331
+ if (!logoutEvent) return [];
332
+
333
+ state.context.previousPage = state.context.currentPage;
334
+ state.context.user = null;
335
+ state.context.currentPage = 'login';
336
+
337
+ return [];
338
+ },
339
+ });
340
+
341
+ const navigationRule = defineRule<AuthContext>({
342
+ id: 'navigation.navigate',
343
+ description: 'Handle navigation',
344
+ impl: (state, events) => {
345
+ const navEvent = findEvent(events, NavigateTo);
346
+ if (!navEvent) return [];
347
+
348
+ const { page, requiresAuth } = navEvent.payload;
349
+
350
+ // Check if page requires authentication
351
+ if (requiresAuth && !state.context.user) {
352
+ state.context.attemptedSecurePage = page;
353
+ state.context.currentPage = 'login';
354
+ return [];
355
+ }
356
+
357
+ state.context.previousPage = state.context.currentPage;
358
+ state.context.currentPage = page;
359
+
360
+ return [NavigatedToPage.create({ page })];
361
+ },
362
+ });
363
+
364
+ export function createAuthEngine() {
365
+ const registry = new PraxisRegistry<AuthContext>();
366
+ registry.registerRule(loginRule);
367
+ registry.registerRule(logoutRule);
368
+ registry.registerRule(navigationRule);
369
+
370
+ return createPraxisEngine<AuthContext>({
371
+ initialContext: {
372
+ user: null,
373
+ previousPage: null,
374
+ currentPage: 'home',
375
+ attemptedSecurePage: null,
376
+ },
377
+ registry,
378
+ });
379
+ }
380
+
381
+ export { Login, Logout, NavigateTo };
382
+ ```
383
+
384
+ ### Using the Auth Engine with History
385
+
386
+ ```svelte
387
+ <script lang="ts">
388
+ import { usePraxisEngine } from '@plures/praxis/svelte';
389
+ import { createAuthEngine, Login, Logout, NavigateTo } from './auth-engine';
390
+
391
+ const engine = createAuthEngine();
392
+ const { context, dispatch, undo, canUndo } = usePraxisEngine(engine, {
393
+ enableHistory: true
394
+ });
395
+
396
+ function login() {
397
+ dispatch([Login.create({
398
+ username: 'alice',
399
+ password: 'secret123'
400
+ })], 'User Login');
401
+ }
402
+
403
+ function logout() {
404
+ dispatch([Logout.create({})], 'User Logout');
405
+ }
406
+
407
+ function goToProfile() {
408
+ dispatch([NavigateTo.create({
409
+ page: 'profile',
410
+ requiresAuth: true
411
+ })], 'Navigate to Profile');
412
+ }
413
+
414
+ function returnToPrevious() {
415
+ if (context.previousPage) {
416
+ dispatch([NavigateTo.create({
417
+ page: context.previousPage
418
+ })], 'Return to Previous Page');
419
+ } else if (canUndo) {
420
+ undo();
421
+ }
422
+ }
423
+ </script>
424
+
425
+ <div class="auth-app">
426
+ <nav>
427
+ <button onclick={() => dispatch([NavigateTo.create({ page: 'home' })])}>
428
+ Home
429
+ </button>
430
+ <button onclick={goToProfile}>
431
+ Profile {context.user ? '' : '(requires login)'}
432
+ </button>
433
+ </nav>
434
+
435
+ <main>
436
+ {#if context.currentPage === 'login'}
437
+ <div class="login">
438
+ <h2>Please Login</h2>
439
+ {#if context.attemptedSecurePage}
440
+ <p>You need to login to access: {context.attemptedSecurePage}</p>
441
+ {/if}
442
+ <button onclick={login}>Login as Alice</button>
443
+ </div>
444
+ {:else if context.currentPage === 'profile'}
445
+ <div class="profile">
446
+ <h2>Profile</h2>
447
+ <p>Welcome, {context.user?.name}!</p>
448
+ <button onclick={logout}>Logout</button>
449
+ </div>
450
+ {:else}
451
+ <div class="home">
452
+ <h2>Home</h2>
453
+ {#if context.user}
454
+ <p>Logged in as: {context.user.name}</p>
455
+ <button onclick={logout}>Logout</button>
456
+ {:else}
457
+ <button onclick={login}>Login</button>
458
+ {/if}
459
+ </div>
460
+ {/if}
461
+ </main>
462
+
463
+ <footer>
464
+ {#if context.previousPage || canUndo}
465
+ <button onclick={returnToPrevious}>
466
+ ← Back
467
+ </button>
468
+ {/if}
469
+ </footer>
470
+ </div>
471
+ ```
472
+
473
+ ## Best Practices
474
+
475
+ ### 1. Set Appropriate History Limits
476
+
477
+ ```typescript
478
+ // For user-facing undo/redo: smaller limit
479
+ const userHistory = new HistoryStateManager<Context>(20);
480
+
481
+ // For debugging: larger limit
482
+ const debugHistory = new HistoryStateManager<Context>(100);
483
+
484
+ // For critical audit trails: very large or unlimited
485
+ const auditHistory = new HistoryStateManager<Context>(10000);
486
+ ```
487
+
488
+ ### 2. Label Important States
489
+
490
+ ```typescript
491
+ historyEngine.dispatch(
492
+ [PlaceOrder.create({ orderId: '123' })],
493
+ 'Order Placed' // Clear label for debugging
494
+ );
495
+ ```
496
+
497
+ ### 3. Clear History When Appropriate
498
+
499
+ ```typescript
500
+ // Clear on logout to protect privacy
501
+ function handleLogout() {
502
+ historyEngine.dispatch([Logout.create({})]);
503
+ historyEngine.clearHistory();
504
+ }
505
+
506
+ // Clear when starting new workflow
507
+ function startNewProject() {
508
+ historyEngine.clearHistory();
509
+ historyEngine.dispatch([CreateProject.create({})],'New Project');
510
+ }
511
+ ```
512
+
513
+ ### 4. Combine with Context State
514
+
515
+ ```typescript
516
+ interface AppContext {
517
+ // Application state
518
+ user: User | null;
519
+
520
+ // Built-in history tracking in context
521
+ navigationHistory: string[];
522
+
523
+ // Previous state for quick back
524
+ previousView: string | null;
525
+ }
526
+
527
+ // You can use both engine history and context-based history
528
+ const navigationRule = defineRule<AppContext>({
529
+ id: 'nav.track',
530
+ impl: (state, events) => {
531
+ const nav = findEvent(events, Navigate);
532
+ if (!nav) return [];
533
+
534
+ state.context.previousView = state.context.currentView;
535
+ state.context.navigationHistory.push(nav.payload.page);
536
+ state.context.currentView = nav.payload.page;
537
+
538
+ return [];
539
+ },
540
+ });
541
+ ```
542
+
543
+ ### 5. Persist Critical History
544
+
545
+ ```typescript
546
+ import { HistoryStateManager } from '@plures/praxis/svelte';
547
+
548
+ // Save to localStorage
549
+ function saveHistory(history: HistoryStateManager<Context>) {
550
+ const entries = history.getHistory();
551
+ localStorage.setItem('app-history', JSON.stringify(entries));
552
+ }
553
+
554
+ // Restore from localStorage
555
+ function loadHistory(history: HistoryStateManager<Context>) {
556
+ const saved = localStorage.getItem('app-history');
557
+ if (saved) {
558
+ const entries = JSON.parse(saved);
559
+ entries.forEach((entry: any) => {
560
+ history.record(entry.state, entry.events, entry.label);
561
+ });
562
+ }
563
+ }
564
+ ```
565
+
566
+ ### 6. Use History for Error Recovery
567
+
568
+ ```typescript
569
+ const errorRecoveryRule = defineRule<AppContext>({
570
+ id: 'error.recover',
571
+ impl: (state, events) => {
572
+ const error = findEvent(events, ErrorOccurred);
573
+ if (!error) return [];
574
+
575
+ // Log error with context
576
+ console.error('Error at state:', state.context);
577
+
578
+ // You can use history to diagnose or recover
579
+ // historyEngine.undo() to revert to last good state
580
+
581
+ return [ErrorLogged.create({ error: error.payload })];
582
+ },
583
+ });
584
+ ```
585
+
586
+ ## Comparison with XState
587
+
588
+ | Feature | XState | Praxis |
589
+ |---------|--------|--------|
590
+ | **Built-in History States** | ✅ Native support | ✅ Pattern-based implementation |
591
+ | **History Types** | Shallow/Deep | Configurable via HistoryStateManager |
592
+ | **Time-Travel** | Via DevTools | ✅ Built into usePraxisEngine |
593
+ | **Undo/Redo** | Custom implementation | ✅ Built-in with createHistoryEngine |
594
+ | **Snapshot Support** | ✅ Via snapshot() | ✅ Via usePraxisEngine snapshots |
595
+ | **History Size Limits** | Manual | ✅ Automatic with maxHistorySize |
596
+
597
+ ## Summary
598
+
599
+ The history state pattern in Praxis provides:
600
+
601
+ - ✅ **Flexible History Tracking**: Use `HistoryStateManager` for full control
602
+ - ✅ **Automatic History**: Wrap engines with `createHistoryEngine`
603
+ - ✅ **Svelte 5 Integration**: Native runes support with `usePraxisEngine`
604
+ - ✅ **Time-Travel Debugging**: Navigate and inspect state history
605
+ - ✅ **Undo/Redo**: Built-in support for user actions
606
+ - ✅ **Auth Flow Support**: Return to previous states after authentication
607
+
608
+ The pattern is designed to be:
609
+ - **Simple**: Easy to understand and use
610
+ - **Flexible**: Works with any Praxis engine
611
+ - **Performant**: Configurable history limits
612
+ - **Type-Safe**: Full TypeScript support
613
+ - **Framework-Agnostic**: Use with or without Svelte
614
+
615
+ For more examples, see:
616
+ - [Svelte Counter Example](/src/examples/svelte-counter/index.ts)
617
+ - [Auth Basic Example](/src/examples/auth-basic/index.ts)
618
+ - [Svelte Integration Tests](/src/__tests__/svelte-integration.test.ts)