@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,657 @@
1
+ /**
2
+ * Hero Example: E-Commerce Platform
3
+ *
4
+ * A comprehensive example demonstrating:
5
+ * - Authentication with session management
6
+ * - Shopping cart with discount rules
7
+ * - Feature flags for A/B testing
8
+ * - Constraints for business rules
9
+ * - Actors for side effects
10
+ * - Full integration of all Praxis features
11
+ */
12
+
13
+ import {
14
+ createPraxisEngine,
15
+ PraxisRegistry,
16
+ defineFact,
17
+ defineEvent,
18
+ defineRule,
19
+ defineConstraint,
20
+ defineModule,
21
+ findEvent,
22
+ filterFacts,
23
+ ActorManager,
24
+ type Actor,
25
+ } from "../../index.js";
26
+
27
+ // ============================================================================
28
+ // CONTEXT TYPE
29
+ // ============================================================================
30
+
31
+ interface ECommerceContext {
32
+ // Auth
33
+ currentUser: string | null;
34
+ sessionStartTime: number | null;
35
+
36
+ // Cart
37
+ cart: {
38
+ items: Array<{ productId: string; quantity: number; price: number }>;
39
+ total: number;
40
+ discountApplied: number;
41
+ };
42
+
43
+ // Feature Flags
44
+ features: {
45
+ freeShippingEnabled: boolean;
46
+ loyaltyProgramEnabled: boolean;
47
+ newCheckoutFlowEnabled: boolean;
48
+ };
49
+
50
+ // Business State
51
+ loyaltyPoints: number;
52
+ orderHistory: string[];
53
+ }
54
+
55
+ // ============================================================================
56
+ // FACTS
57
+ // ============================================================================
58
+
59
+ // Auth Facts
60
+ const UserLoggedIn = defineFact<"UserLoggedIn", { userId: string; timestamp: number }>(
61
+ "UserLoggedIn"
62
+ );
63
+ const UserLoggedOut = defineFact<"UserLoggedOut", { userId: string }>(
64
+ "UserLoggedOut"
65
+ );
66
+ const SessionExpired = defineFact<"SessionExpired", { userId: string }>(
67
+ "SessionExpired"
68
+ );
69
+
70
+ // Cart Facts
71
+ const ItemAdded = defineFact<"ItemAdded", { productId: string; quantity: number; price: number }>(
72
+ "ItemAdded"
73
+ );
74
+ const ItemRemoved = defineFact<"ItemRemoved", { productId: string }>(
75
+ "ItemRemoved"
76
+ );
77
+ const DiscountApplied = defineFact<"DiscountApplied", { amount: number; reason: string }>(
78
+ "DiscountApplied"
79
+ );
80
+ const CartCleared = defineFact<"CartCleared", {}>("CartCleared");
81
+
82
+ // Feature Flag Facts
83
+ const FeatureEnabled = defineFact<"FeatureEnabled", { feature: string }>(
84
+ "FeatureEnabled"
85
+ );
86
+ const FeatureDisabled = defineFact<"FeatureDisabled", { feature: string }>(
87
+ "FeatureDisabled"
88
+ );
89
+
90
+ // Business Facts
91
+ const LoyaltyPointsAwarded = defineFact<"LoyaltyPointsAwarded", { points: number }>(
92
+ "LoyaltyPointsAwarded"
93
+ );
94
+ const OrderPlaced = defineFact<"OrderPlaced", { orderId: string; total: number }>(
95
+ "OrderPlaced"
96
+ );
97
+
98
+ // ============================================================================
99
+ // EVENTS
100
+ // ============================================================================
101
+
102
+ // Auth Events
103
+ const Login = defineEvent<"LOGIN", { username: string }>("LOGIN");
104
+ const Logout = defineEvent<"LOGOUT", {}>("LOGOUT");
105
+ const CheckSession = defineEvent<"CHECK_SESSION", {}>("CHECK_SESSION");
106
+
107
+ // Cart Events
108
+ const AddToCart = defineEvent<"ADD_TO_CART", { productId: string; quantity: number; price: number }>(
109
+ "ADD_TO_CART"
110
+ );
111
+ const RemoveFromCart = defineEvent<"REMOVE_FROM_CART", { productId: string }>(
112
+ "REMOVE_FROM_CART"
113
+ );
114
+ const ApplyDiscount = defineEvent<"APPLY_DISCOUNT", { code: string }>(
115
+ "APPLY_DISCOUNT"
116
+ );
117
+ const Checkout = defineEvent<"CHECKOUT", {}>("CHECKOUT");
118
+
119
+ // Feature Flag Events
120
+ const EnableFeature = defineEvent<"ENABLE_FEATURE", { feature: string }>(
121
+ "ENABLE_FEATURE"
122
+ );
123
+ const DisableFeature = defineEvent<"DISABLE_FEATURE", { feature: string }>(
124
+ "DISABLE_FEATURE"
125
+ );
126
+
127
+ // ============================================================================
128
+ // AUTH MODULE
129
+ // ============================================================================
130
+
131
+ const loginRule = defineRule<ECommerceContext>({
132
+ id: "auth.login",
133
+ description: "Process login event",
134
+ impl: (state, events) => {
135
+ const loginEvent = findEvent(events, Login);
136
+ if (!loginEvent) {
137
+ return [];
138
+ }
139
+
140
+ state.context.currentUser = loginEvent.payload.username;
141
+ state.context.sessionStartTime = Date.now();
142
+
143
+ return [
144
+ UserLoggedIn.create({
145
+ userId: loginEvent.payload.username,
146
+ timestamp: Date.now(),
147
+ }),
148
+ ];
149
+ },
150
+ });
151
+
152
+ const logoutRule = defineRule<ECommerceContext>({
153
+ id: "auth.logout",
154
+ description: "Process logout event",
155
+ impl: (state, events) => {
156
+ const logoutEvent = findEvent(events, Logout);
157
+ if (!logoutEvent || !state.context.currentUser) {
158
+ return [];
159
+ }
160
+
161
+ const userId = state.context.currentUser;
162
+ state.context.currentUser = null;
163
+ state.context.sessionStartTime = null;
164
+
165
+ // Clear cart on logout
166
+ state.context.cart = {
167
+ items: [],
168
+ total: 0,
169
+ discountApplied: 0,
170
+ };
171
+
172
+ return [
173
+ UserLoggedOut.create({ userId }),
174
+ CartCleared.create({}),
175
+ ];
176
+ },
177
+ });
178
+
179
+ const sessionCheckRule = defineRule<ECommerceContext>({
180
+ id: "auth.checkSession",
181
+ description: "Check for session expiration (30 minute timeout)",
182
+ impl: (state, events) => {
183
+ const checkEvent = findEvent(events, CheckSession);
184
+ if (!checkEvent || !state.context.currentUser || !state.context.sessionStartTime) {
185
+ return [];
186
+ }
187
+
188
+ const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
189
+ const elapsed = Date.now() - state.context.sessionStartTime;
190
+
191
+ if (elapsed > SESSION_TIMEOUT_MS) {
192
+ const userId = state.context.currentUser;
193
+ state.context.currentUser = null;
194
+ state.context.sessionStartTime = null;
195
+ state.context.cart = { items: [], total: 0, discountApplied: 0 };
196
+
197
+ return [
198
+ SessionExpired.create({ userId }),
199
+ CartCleared.create({}),
200
+ ];
201
+ }
202
+
203
+ return [];
204
+ },
205
+ });
206
+
207
+ const authConstraints = [
208
+ defineConstraint<ECommerceContext>({
209
+ id: "auth.singleSession",
210
+ description: "Only one user can be logged in at a time",
211
+ impl: () => {
212
+ // This is always valid - just enforced by business logic
213
+ return true;
214
+ },
215
+ }),
216
+ ];
217
+
218
+ const authModule = defineModule<ECommerceContext>({
219
+ rules: [loginRule, logoutRule, sessionCheckRule],
220
+ constraints: authConstraints,
221
+ meta: { module: "auth", version: "1.0.0" },
222
+ });
223
+
224
+ // ============================================================================
225
+ // CART MODULE
226
+ // ============================================================================
227
+
228
+ const addToCartRule = defineRule<ECommerceContext>({
229
+ id: "cart.addItem",
230
+ description: "Add item to cart",
231
+ impl: (state, events) => {
232
+ const addEvents = events.filter(AddToCart.is);
233
+ if (!state.context.currentUser || addEvents.length === 0) {
234
+ return [];
235
+ }
236
+
237
+ return addEvents.map((event) =>
238
+ ItemAdded.create({
239
+ productId: event.payload.productId,
240
+ quantity: event.payload.quantity,
241
+ price: event.payload.price,
242
+ })
243
+ );
244
+ },
245
+ meta: { dependsOn: "auth.login" },
246
+ });
247
+
248
+ const removeFromCartRule = defineRule<ECommerceContext>({
249
+ id: "cart.removeItem",
250
+ description: "Remove item from cart",
251
+ impl: (state, events) => {
252
+ const removeEvent = findEvent(events, RemoveFromCart);
253
+ if (!removeEvent || !state.context.currentUser) {
254
+ return [];
255
+ }
256
+
257
+ return [ItemRemoved.create({ productId: removeEvent.payload.productId })];
258
+ },
259
+ });
260
+
261
+ const applyDiscountRule = defineRule<ECommerceContext>({
262
+ id: "cart.applyDiscount",
263
+ description: "Apply discount codes",
264
+ impl: (state, events) => {
265
+ const discountEvent = findEvent(events, ApplyDiscount);
266
+ if (!discountEvent || !state.context.currentUser) {
267
+ return [];
268
+ }
269
+
270
+ const code = discountEvent.payload.code;
271
+ let discount = 0;
272
+ let reason = "";
273
+
274
+ // Discount logic
275
+ if (code === "SAVE10") {
276
+ discount = 0.10;
277
+ reason = "10% off with code SAVE10";
278
+ } else if (code === "SAVE20" && state.context.loyaltyPoints > 100) {
279
+ discount = 0.20;
280
+ reason = "20% off for loyal customers";
281
+ } else if (code === "FREESHIP" && state.context.features.freeShippingEnabled) {
282
+ discount = 0.05;
283
+ reason = "5% off with free shipping";
284
+ }
285
+
286
+ if (discount > 0) {
287
+ return [DiscountApplied.create({ amount: discount, reason })];
288
+ }
289
+
290
+ return [];
291
+ },
292
+ });
293
+
294
+ const updateCartContextRule = defineRule<ECommerceContext>({
295
+ id: "cart.updateContext",
296
+ description: "Update cart context from facts",
297
+ impl: (state) => {
298
+ const addedItems = filterFacts(state.facts, ItemAdded);
299
+ const removedItems = filterFacts(state.facts, ItemRemoved);
300
+ const discounts = filterFacts(state.facts, DiscountApplied);
301
+ const cleared = filterFacts(state.facts, CartCleared);
302
+
303
+ if (cleared.length > 0) {
304
+ state.context.cart = { items: [], total: 0, discountApplied: 0 };
305
+ return [];
306
+ }
307
+
308
+ // Build cart items
309
+ const itemMap = new Map<string, { quantity: number; price: number }>();
310
+
311
+ for (const fact of addedItems) {
312
+ const existing = itemMap.get(fact.payload.productId);
313
+ if (existing) {
314
+ itemMap.set(fact.payload.productId, {
315
+ quantity: existing.quantity + fact.payload.quantity,
316
+ price: fact.payload.price,
317
+ });
318
+ } else {
319
+ itemMap.set(fact.payload.productId, {
320
+ quantity: fact.payload.quantity,
321
+ price: fact.payload.price,
322
+ });
323
+ }
324
+ }
325
+
326
+ for (const fact of removedItems) {
327
+ itemMap.delete(fact.payload.productId);
328
+ }
329
+
330
+ state.context.cart.items = Array.from(itemMap.entries()).map(([productId, data]) => ({
331
+ productId,
332
+ quantity: data.quantity,
333
+ price: data.price,
334
+ }));
335
+
336
+ // Calculate total
337
+ let total = state.context.cart.items.reduce(
338
+ (sum, item) => sum + item.quantity * item.price,
339
+ 0
340
+ );
341
+
342
+ // Apply discounts
343
+ let totalDiscount = 0;
344
+ for (const discount of discounts) {
345
+ totalDiscount = Math.max(totalDiscount, discount.payload.amount);
346
+ }
347
+
348
+ state.context.cart.discountApplied = totalDiscount;
349
+ state.context.cart.total = total * (1 - totalDiscount);
350
+
351
+ return [];
352
+ },
353
+ });
354
+
355
+ const checkoutRule = defineRule<ECommerceContext>({
356
+ id: "cart.checkout",
357
+ description: "Process checkout",
358
+ impl: (state, events) => {
359
+ const checkoutEvent = findEvent(events, Checkout);
360
+ if (!checkoutEvent || !state.context.currentUser || state.context.cart.items.length === 0) {
361
+ return [];
362
+ }
363
+
364
+ const orderId = `order-${Date.now()}`;
365
+ const total = state.context.cart.total;
366
+
367
+ // Award loyalty points (1 point per dollar)
368
+ const pointsEarned = Math.floor(total);
369
+
370
+ state.context.orderHistory.push(orderId);
371
+ state.context.loyaltyPoints += pointsEarned;
372
+ state.context.cart = { items: [], total: 0, discountApplied: 0 };
373
+
374
+ return [
375
+ OrderPlaced.create({ orderId, total }),
376
+ LoyaltyPointsAwarded.create({ points: pointsEarned }),
377
+ CartCleared.create({}),
378
+ ];
379
+ },
380
+ });
381
+
382
+ const cartConstraints = [
383
+ defineConstraint<ECommerceContext>({
384
+ id: "cart.maxItems",
385
+ description: "Cart cannot exceed 100 items",
386
+ impl: (state) => {
387
+ const totalQuantity = state.context.cart.items.reduce(
388
+ (sum, item) => sum + item.quantity,
389
+ 0
390
+ );
391
+ return totalQuantity <= 100 || `Cart has ${totalQuantity} items, max is 100`;
392
+ },
393
+ }),
394
+ defineConstraint<ECommerceContext>({
395
+ id: "cart.requiresAuth",
396
+ description: "Cart operations require authentication",
397
+ impl: (state) => {
398
+ if (state.context.cart.items.length > 0 && !state.context.currentUser) {
399
+ return "Cart operations require authentication";
400
+ }
401
+ return true;
402
+ },
403
+ }),
404
+ ];
405
+
406
+ const cartModule = defineModule<ECommerceContext>({
407
+ rules: [
408
+ addToCartRule,
409
+ removeFromCartRule,
410
+ applyDiscountRule,
411
+ updateCartContextRule,
412
+ checkoutRule,
413
+ ],
414
+ constraints: cartConstraints,
415
+ meta: { module: "cart", version: "1.0.0", dependsOn: ["auth"] },
416
+ });
417
+
418
+ // ============================================================================
419
+ // FEATURE FLAGS MODULE
420
+ // ============================================================================
421
+
422
+ const enableFeatureRule = defineRule<ECommerceContext>({
423
+ id: "features.enable",
424
+ description: "Enable a feature flag",
425
+ impl: (state, events) => {
426
+ const enableEvent = findEvent(events, EnableFeature);
427
+ if (!enableEvent) {
428
+ return [];
429
+ }
430
+
431
+ const feature = enableEvent.payload.feature;
432
+ if (feature === "freeShipping") {
433
+ state.context.features.freeShippingEnabled = true;
434
+ } else if (feature === "loyaltyProgram") {
435
+ state.context.features.loyaltyProgramEnabled = true;
436
+ } else if (feature === "newCheckoutFlow") {
437
+ state.context.features.newCheckoutFlowEnabled = true;
438
+ }
439
+
440
+ return [FeatureEnabled.create({ feature })];
441
+ },
442
+ });
443
+
444
+ const disableFeatureRule = defineRule<ECommerceContext>({
445
+ id: "features.disable",
446
+ description: "Disable a feature flag",
447
+ impl: (state, events) => {
448
+ const disableEvent = findEvent(events, DisableFeature);
449
+ if (!disableEvent) {
450
+ return [];
451
+ }
452
+
453
+ const feature = disableEvent.payload.feature;
454
+ if (feature === "freeShipping") {
455
+ state.context.features.freeShippingEnabled = false;
456
+ } else if (feature === "loyaltyProgram") {
457
+ state.context.features.loyaltyProgramEnabled = false;
458
+ } else if (feature === "newCheckoutFlow") {
459
+ state.context.features.newCheckoutFlowEnabled = false;
460
+ }
461
+
462
+ return [FeatureDisabled.create({ feature })];
463
+ },
464
+ });
465
+
466
+ const featureFlagsModule = defineModule<ECommerceContext>({
467
+ rules: [enableFeatureRule, disableFeatureRule],
468
+ constraints: [],
469
+ meta: { module: "featureFlags", version: "1.0.0" },
470
+ });
471
+
472
+ // ============================================================================
473
+ // ACTORS
474
+ // ============================================================================
475
+
476
+ /**
477
+ * Logging actor - logs important events to console
478
+ */
479
+ const loggingActor: Actor<ECommerceContext> = {
480
+ id: "logging",
481
+ description: "Logs important events",
482
+ onStateChange: (state) => {
483
+ const recentFacts = state.facts.slice(-3); // Last 3 facts
484
+ for (const fact of recentFacts) {
485
+ if (fact.tag === "UserLoggedIn" && UserLoggedIn.is(fact)) {
486
+ console.log(` [LOG] User ${fact.payload.userId} logged in`);
487
+ } else if (fact.tag === "OrderPlaced" && OrderPlaced.is(fact)) {
488
+ console.log(` [LOG] Order ${fact.payload.orderId} placed for $${fact.payload.total.toFixed(2)}`);
489
+ } else if (fact.tag === "SessionExpired" && SessionExpired.is(fact)) {
490
+ console.log(` [LOG] Session expired for user ${fact.payload.userId}`);
491
+ }
492
+ }
493
+ },
494
+ };
495
+
496
+ /**
497
+ * Analytics actor - tracks metrics
498
+ */
499
+ const analyticsActor: Actor<ECommerceContext> = {
500
+ id: "analytics",
501
+ description: "Tracks analytics events",
502
+ onStateChange: (state) => {
503
+ // In a real app, this would send to an analytics service
504
+ const recentFacts = state.facts.slice(-1);
505
+ for (const fact of recentFacts) {
506
+ if (fact.tag === "OrderPlaced" && OrderPlaced.is(fact)) {
507
+ console.log(` [ANALYTICS] Revenue: $${fact.payload.total.toFixed(2)}`);
508
+ } else if (fact.tag === "LoyaltyPointsAwarded" && LoyaltyPointsAwarded.is(fact)) {
509
+ console.log(` [ANALYTICS] Loyalty engagement: ${fact.payload.points} points`);
510
+ }
511
+ }
512
+ },
513
+ };
514
+
515
+ // ============================================================================
516
+ // ENGINE SETUP
517
+ // ============================================================================
518
+
519
+ function createECommerceEngine() {
520
+ const registry = new PraxisRegistry<ECommerceContext>();
521
+
522
+ // Register all modules
523
+ registry.registerModule(authModule);
524
+ registry.registerModule(cartModule);
525
+ registry.registerModule(featureFlagsModule);
526
+
527
+ const engine = createPraxisEngine<ECommerceContext>({
528
+ initialContext: {
529
+ currentUser: null,
530
+ sessionStartTime: null,
531
+ cart: {
532
+ items: [],
533
+ total: 0,
534
+ discountApplied: 0,
535
+ },
536
+ features: {
537
+ freeShippingEnabled: false,
538
+ loyaltyProgramEnabled: true,
539
+ newCheckoutFlowEnabled: false,
540
+ },
541
+ loyaltyPoints: 0,
542
+ orderHistory: [],
543
+ },
544
+ registry,
545
+ });
546
+
547
+ // Setup actors
548
+ const actorManager = new ActorManager<ECommerceContext>();
549
+ actorManager.attachEngine(engine);
550
+ actorManager.register(loggingActor);
551
+ actorManager.register(analyticsActor);
552
+
553
+ return { engine, actorManager };
554
+ }
555
+
556
+ // ============================================================================
557
+ // DEMO SCENARIO
558
+ // ============================================================================
559
+
560
+ async function runDemo() {
561
+ console.log("=".repeat(70));
562
+ console.log("E-COMMERCE PLATFORM DEMO");
563
+ console.log("Demonstrating: Auth + Cart + Feature Flags + Actors + Constraints");
564
+ console.log("=".repeat(70));
565
+ console.log();
566
+
567
+ const { engine, actorManager } = createECommerceEngine();
568
+ await actorManager.startAll();
569
+
570
+ // Scenario 1: User Login
571
+ console.log("1. User Login");
572
+ console.log("-".repeat(70));
573
+ let result = engine.step([Login.create({ username: "alice" })]);
574
+ await actorManager.notifyStateChange(engine.getState());
575
+ console.log(` User: ${engine.getContext().currentUser}`);
576
+ console.log(` Diagnostics: ${result.diagnostics.length} issue(s)`);
577
+ console.log();
578
+
579
+ // Scenario 2: Enable Feature Flag
580
+ console.log("2. Enable Free Shipping Feature");
581
+ console.log("-".repeat(70));
582
+ result = engine.step([EnableFeature.create({ feature: "freeShipping" })]);
583
+ await actorManager.notifyStateChange(engine.getState());
584
+ console.log(` Free Shipping Enabled: ${engine.getContext().features.freeShippingEnabled}`);
585
+ console.log();
586
+
587
+ // Scenario 3: Add Items to Cart
588
+ console.log("3. Add Items to Cart");
589
+ console.log("-".repeat(70));
590
+ result = engine.step([
591
+ AddToCart.create({ productId: "laptop-1", quantity: 1, price: 999.99 }),
592
+ AddToCart.create({ productId: "mouse-1", quantity: 2, price: 29.99 }),
593
+ ]);
594
+ await actorManager.notifyStateChange(engine.getState());
595
+ const cart = engine.getContext().cart;
596
+ console.log(` Items: ${cart.items.length}`);
597
+ console.log(` Total: $${cart.total.toFixed(2)}`);
598
+ console.log();
599
+
600
+ // Scenario 4: Apply Discount
601
+ console.log("4. Apply Discount Code");
602
+ console.log("-".repeat(70));
603
+ result = engine.step([ApplyDiscount.create({ code: "SAVE10" })]);
604
+ await actorManager.notifyStateChange(engine.getState());
605
+ console.log(` Discount: ${(engine.getContext().cart.discountApplied * 100).toFixed(0)}%`);
606
+ console.log(` New Total: $${engine.getContext().cart.total.toFixed(2)}`);
607
+ console.log();
608
+
609
+ // Scenario 5: Checkout
610
+ console.log("5. Checkout");
611
+ console.log("-".repeat(70));
612
+ result = engine.step([Checkout.create({})]);
613
+ await actorManager.notifyStateChange(engine.getState());
614
+ console.log(` Orders Placed: ${engine.getContext().orderHistory.length}`);
615
+ console.log(` Loyalty Points: ${engine.getContext().loyaltyPoints}`);
616
+ console.log(` Cart Items: ${engine.getContext().cart.items.length}`);
617
+ console.log();
618
+
619
+ // Scenario 6: Add More Items and Checkout Again
620
+ console.log("6. Shop Again with Loyalty Discount");
621
+ console.log("-".repeat(70));
622
+ engine.step([
623
+ AddToCart.create({ productId: "keyboard-1", quantity: 1, price: 129.99 }),
624
+ ApplyDiscount.create({ code: "SAVE20" }), // Requires 100+ loyalty points
625
+ ]);
626
+ await actorManager.notifyStateChange(engine.getState());
627
+ console.log(` Total: $${engine.getContext().cart.total.toFixed(2)}`);
628
+ console.log(` Discount Applied: ${(engine.getContext().cart.discountApplied * 100).toFixed(0)}%`);
629
+
630
+ result = engine.step([Checkout.create({})]);
631
+ await actorManager.notifyStateChange(engine.getState());
632
+ console.log(` Total Orders: ${engine.getContext().orderHistory.length}`);
633
+ console.log(` Total Loyalty Points: ${engine.getContext().loyaltyPoints}`);
634
+ console.log();
635
+
636
+ // Scenario 7: Logout
637
+ console.log("7. User Logout");
638
+ console.log("-".repeat(70));
639
+ result = engine.step([Logout.create({})]);
640
+ await actorManager.notifyStateChange(engine.getState());
641
+ console.log(` User: ${engine.getContext().currentUser ?? "(none)"}`);
642
+ console.log(` Session Cleared: ${engine.getContext().sessionStartTime === null}`);
643
+ console.log();
644
+
645
+ await actorManager.stopAll();
646
+
647
+ console.log("=".repeat(70));
648
+ console.log("DEMO COMPLETE");
649
+ console.log("=".repeat(70));
650
+ }
651
+
652
+ // Run demo if executed directly
653
+ if (import.meta.url === `file://${process.argv[1]}`) {
654
+ runDemo().catch(console.error);
655
+ }
656
+
657
+ export { createECommerceEngine, runDemo };