@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,163 @@
1
+ /**
2
+ * Auth Basic Example
3
+ *
4
+ * Demonstrates basic authentication logic with login/logout using Praxis.
5
+ * Shows how to define facts, events, rules, and constraints.
6
+ */
7
+
8
+ import {
9
+ createPraxisEngine,
10
+ PraxisRegistry,
11
+ defineFact,
12
+ defineEvent,
13
+ defineRule,
14
+ defineConstraint,
15
+ findEvent,
16
+ filterFacts,
17
+ } from "../../index.js";
18
+
19
+ // Define the context type
20
+ interface AuthContext {
21
+ currentUser: string | null;
22
+ sessions: Array<{ userId: string; timestamp: number }>;
23
+ }
24
+
25
+ // Define facts
26
+ const UserLoggedIn = defineFact<"UserLoggedIn", { userId: string; timestamp: number }>(
27
+ "UserLoggedIn"
28
+ );
29
+ const UserLoggedOut = defineFact<"UserLoggedOut", { userId: string; timestamp: number }>(
30
+ "UserLoggedOut"
31
+ );
32
+
33
+ // Define events
34
+ const Login = defineEvent<"LOGIN", { username: string; password: string }>("LOGIN");
35
+ const Logout = defineEvent<"LOGOUT", {}>("LOGOUT");
36
+
37
+ // Define rules
38
+ const loginRule = defineRule<AuthContext>({
39
+ id: "auth.login",
40
+ description: "Process login event and create UserLoggedIn fact",
41
+ impl: (_state, events) => {
42
+ const loginEvent = findEvent(events, Login);
43
+ if (!loginEvent) {
44
+ return [];
45
+ }
46
+
47
+ // In real app, validate password here
48
+ const userId = loginEvent.payload.username;
49
+ return [UserLoggedIn.create({ userId, timestamp: Date.now() })];
50
+ },
51
+ });
52
+
53
+ const logoutRule = defineRule<AuthContext>({
54
+ id: "auth.logout",
55
+ description: "Process logout event and create UserLoggedOut fact",
56
+ impl: (state, events) => {
57
+ const logoutEvent = findEvent(events, Logout);
58
+ if (!logoutEvent || !state.context.currentUser) {
59
+ return [];
60
+ }
61
+
62
+ return [
63
+ UserLoggedOut.create({
64
+ userId: state.context.currentUser,
65
+ timestamp: Date.now(),
66
+ }),
67
+ ];
68
+ },
69
+ });
70
+
71
+ const updateContextRule = defineRule<AuthContext>({
72
+ id: "auth.updateContext",
73
+ description: "Update context based on login/logout facts",
74
+ impl: (state, _events) => {
75
+ // This rule updates context based on facts (side effect on context)
76
+ const loginFacts = filterFacts(state.facts, UserLoggedIn);
77
+ const logoutFacts = filterFacts(state.facts, UserLoggedOut);
78
+
79
+ if (loginFacts.length > 0) {
80
+ const latestLogin = loginFacts[loginFacts.length - 1];
81
+ state.context.currentUser = latestLogin.payload.userId;
82
+ state.context.sessions.push({
83
+ userId: latestLogin.payload.userId,
84
+ timestamp: latestLogin.payload.timestamp,
85
+ });
86
+ }
87
+
88
+ if (logoutFacts.length > 0) {
89
+ state.context.currentUser = null;
90
+ }
91
+
92
+ return [];
93
+ },
94
+ });
95
+
96
+ // Define constraints
97
+ const singleSessionConstraint = defineConstraint<AuthContext>({
98
+ id: "auth.singleSession",
99
+ description: "Only one user can be logged in at a time",
100
+ impl: (state) => {
101
+ const loginFacts = filterFacts(state.facts, UserLoggedIn);
102
+ const logoutFacts = filterFacts(state.facts, UserLoggedOut);
103
+
104
+ const activeLogins = loginFacts.length - logoutFacts.length;
105
+ return activeLogins <= 1 || `Multiple active sessions detected: ${activeLogins}`;
106
+ },
107
+ });
108
+
109
+ // Create and configure the engine
110
+ function createAuthEngine() {
111
+ const registry = new PraxisRegistry<AuthContext>();
112
+ registry.registerRule(loginRule);
113
+ registry.registerRule(logoutRule);
114
+ registry.registerRule(updateContextRule);
115
+ registry.registerConstraint(singleSessionConstraint);
116
+
117
+ const engine = createPraxisEngine<AuthContext>({
118
+ initialContext: {
119
+ currentUser: null,
120
+ sessions: [],
121
+ },
122
+ registry,
123
+ });
124
+
125
+ return engine;
126
+ }
127
+
128
+ // Example usage
129
+ function runExample() {
130
+ console.log("=== Auth Basic Example ===\n");
131
+
132
+ const engine = createAuthEngine();
133
+
134
+ // Login
135
+ console.log("1. User logs in:");
136
+ let result = engine.step([Login.create({ username: "alice", password: "secret123" })]);
137
+ console.log(" Context:", engine.getContext());
138
+ console.log(" Facts:", result.state.facts);
139
+ console.log(" Diagnostics:", result.diagnostics);
140
+ console.log();
141
+
142
+ // Try to login again (should violate constraint)
143
+ console.log("2. Another user tries to log in:");
144
+ result = engine.step([Login.create({ username: "bob", password: "secret456" })]);
145
+ console.log(" Context:", engine.getContext());
146
+ console.log(" Diagnostics:", result.diagnostics);
147
+ console.log();
148
+
149
+ // Logout
150
+ console.log("3. User logs out:");
151
+ result = engine.step([Logout.create({})]);
152
+ console.log(" Context:", engine.getContext());
153
+ console.log(" Facts (last 3):", result.state.facts.slice(-3));
154
+ console.log(" Diagnostics:", result.diagnostics);
155
+ console.log();
156
+ }
157
+
158
+ // Run example if executed directly
159
+ if (import.meta.url === `file://${process.argv[1]}`) {
160
+ runExample();
161
+ }
162
+
163
+ export { createAuthEngine, runExample };
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Cart Example
3
+ *
4
+ * Demonstrates shopping cart logic with flows and derived state.
5
+ * Shows how to manage complex state with multiple rules and constraints.
6
+ */
7
+
8
+ import {
9
+ createPraxisEngine,
10
+ PraxisRegistry,
11
+ defineFact,
12
+ defineEvent,
13
+ defineRule,
14
+ defineConstraint,
15
+ defineModule,
16
+ findEvent,
17
+ filterFacts,
18
+ } from "../../index.js";
19
+
20
+ // Define the context type
21
+ interface CartContext {
22
+ items: Array<{ productId: string; quantity: number; price: number }>;
23
+ total: number;
24
+ discountApplied: boolean;
25
+ }
26
+
27
+ // Define facts
28
+ const ItemAdded = defineFact<"ItemAdded", { productId: string; quantity: number; price: number }>(
29
+ "ItemAdded"
30
+ );
31
+ const ItemRemoved = defineFact<"ItemRemoved", { productId: string }>("ItemRemoved");
32
+ const DiscountApplied = defineFact<"DiscountApplied", { percentage: number }>("DiscountApplied");
33
+ const CartCleared = defineFact<"CartCleared", {}>("CartCleared");
34
+
35
+ // Define events
36
+ const AddToCart = defineEvent<"ADD_TO_CART", { productId: string; quantity: number; price: number }>(
37
+ "ADD_TO_CART"
38
+ );
39
+ const RemoveFromCart = defineEvent<"REMOVE_FROM_CART", { productId: string }>("REMOVE_FROM_CART");
40
+ const ApplyDiscount = defineEvent<"APPLY_DISCOUNT", { code: string }>("APPLY_DISCOUNT");
41
+ const ClearCart = defineEvent<"CLEAR_CART", {}>("CLEAR_CART");
42
+
43
+ // Define rules
44
+ const addToCartRule = defineRule<CartContext>({
45
+ id: "cart.addItem",
46
+ description: "Add item to cart",
47
+ impl: (_state, events) => {
48
+ const addEvents = events.filter(AddToCart.is);
49
+ return addEvents.map((event) =>
50
+ ItemAdded.create({
51
+ productId: event.payload.productId,
52
+ quantity: event.payload.quantity,
53
+ price: event.payload.price,
54
+ })
55
+ );
56
+ },
57
+ });
58
+
59
+ const removeFromCartRule = defineRule<CartContext>({
60
+ id: "cart.removeItem",
61
+ description: "Remove item from cart",
62
+ impl: (_state, events) => {
63
+ const removeEvent = findEvent(events, RemoveFromCart);
64
+ if (!removeEvent) {
65
+ return [];
66
+ }
67
+ return [ItemRemoved.create({ productId: removeEvent.payload.productId })];
68
+ },
69
+ });
70
+
71
+ const applyDiscountRule = defineRule<CartContext>({
72
+ id: "cart.applyDiscount",
73
+ description: "Apply discount code",
74
+ impl: (state, events) => {
75
+ const discountEvent = findEvent(events, ApplyDiscount);
76
+ if (!discountEvent || state.context.discountApplied) {
77
+ return [];
78
+ }
79
+
80
+ // Simple discount logic: "SAVE10" = 10% off
81
+ const percentage = discountEvent.payload.code === "SAVE10" ? 10 : 0;
82
+ if (percentage > 0) {
83
+ return [DiscountApplied.create({ percentage })];
84
+ }
85
+ return [];
86
+ },
87
+ });
88
+
89
+ const clearCartRule = defineRule<CartContext>({
90
+ id: "cart.clear",
91
+ description: "Clear cart",
92
+ impl: (_state, events) => {
93
+ const clearEvent = findEvent(events, ClearCart);
94
+ if (!clearEvent) {
95
+ return [];
96
+ }
97
+ return [CartCleared.create({})];
98
+ },
99
+ });
100
+
101
+ const updateCartContextRule = defineRule<CartContext>({
102
+ id: "cart.updateContext",
103
+ description: "Update cart context based on facts",
104
+ impl: (state, _events) => {
105
+ const addedItems = filterFacts(state.facts, ItemAdded);
106
+ const removedItems = filterFacts(state.facts, ItemRemoved);
107
+ const clearedFacts = filterFacts(state.facts, CartCleared);
108
+ const discountFacts = filterFacts(state.facts, DiscountApplied);
109
+
110
+ // Check if cart was cleared
111
+ if (clearedFacts.length > 0) {
112
+ state.context.items = [];
113
+ state.context.total = 0;
114
+ state.context.discountApplied = false;
115
+ return [];
116
+ }
117
+
118
+ // Build current cart items
119
+ const itemMap = new Map<string, { quantity: number; price: number }>();
120
+
121
+ for (const fact of addedItems) {
122
+ const existing = itemMap.get(fact.payload.productId);
123
+ if (existing) {
124
+ itemMap.set(fact.payload.productId, {
125
+ quantity: existing.quantity + fact.payload.quantity,
126
+ price: fact.payload.price,
127
+ });
128
+ } else {
129
+ itemMap.set(fact.payload.productId, {
130
+ quantity: fact.payload.quantity,
131
+ price: fact.payload.price,
132
+ });
133
+ }
134
+ }
135
+
136
+ for (const fact of removedItems) {
137
+ itemMap.delete(fact.payload.productId);
138
+ }
139
+
140
+ // Update context
141
+ state.context.items = Array.from(itemMap.entries()).map(([productId, data]) => ({
142
+ productId,
143
+ quantity: data.quantity,
144
+ price: data.price,
145
+ }));
146
+
147
+ // Calculate total
148
+ let total = state.context.items.reduce(
149
+ (sum, item) => sum + item.quantity * item.price,
150
+ 0
151
+ );
152
+
153
+ // Apply discount if any
154
+ if (discountFacts.length > 0) {
155
+ const discount = discountFacts[discountFacts.length - 1];
156
+ total = total * (1 - discount.payload.percentage / 100);
157
+ state.context.discountApplied = true;
158
+ }
159
+
160
+ state.context.total = Math.round(total * 100) / 100; // Round to 2 decimals
161
+
162
+ return [];
163
+ },
164
+ });
165
+
166
+ // Define constraints
167
+ const maxItemsConstraint = defineConstraint<CartContext>({
168
+ id: "cart.maxItems",
169
+ description: "Cart cannot exceed 100 items",
170
+ impl: (state) => {
171
+ const totalQuantity = state.context.items.reduce((sum, item) => sum + item.quantity, 0);
172
+ return totalQuantity <= 100 || `Cart has ${totalQuantity} items, maximum is 100`;
173
+ },
174
+ });
175
+
176
+ const maxTotalConstraint = defineConstraint<CartContext>({
177
+ id: "cart.maxTotal",
178
+ description: "Cart total cannot exceed $10,000",
179
+ impl: (state) => {
180
+ return state.context.total <= 10000 || `Cart total $${state.context.total} exceeds maximum $10,000`;
181
+ },
182
+ });
183
+
184
+ // Create a module
185
+ const cartModule = defineModule<CartContext>({
186
+ rules: [
187
+ addToCartRule,
188
+ removeFromCartRule,
189
+ applyDiscountRule,
190
+ clearCartRule,
191
+ updateCartContextRule,
192
+ ],
193
+ constraints: [maxItemsConstraint, maxTotalConstraint],
194
+ meta: { version: "1.0.0" },
195
+ });
196
+
197
+ // Create and configure the engine
198
+ function createCartEngine() {
199
+ const registry = new PraxisRegistry<CartContext>();
200
+ registry.registerModule(cartModule);
201
+
202
+ const engine = createPraxisEngine<CartContext>({
203
+ initialContext: {
204
+ items: [],
205
+ total: 0,
206
+ discountApplied: false,
207
+ },
208
+ registry,
209
+ });
210
+
211
+ return engine;
212
+ }
213
+
214
+ // Example usage
215
+ function runExample() {
216
+ console.log("=== Cart Example ===\n");
217
+
218
+ const engine = createCartEngine();
219
+
220
+ // Add items
221
+ console.log("1. Add items to cart:");
222
+ engine.step([
223
+ AddToCart.create({ productId: "prod-1", quantity: 2, price: 29.99 }),
224
+ AddToCart.create({ productId: "prod-2", quantity: 1, price: 49.99 }),
225
+ ]);
226
+ console.log(" Cart:", engine.getContext());
227
+ console.log();
228
+
229
+ // Apply discount
230
+ console.log("2. Apply discount code:");
231
+ engine.step([ApplyDiscount.create({ code: "SAVE10" })]);
232
+ console.log(" Cart:", engine.getContext());
233
+ console.log();
234
+
235
+ // Remove item
236
+ console.log("3. Remove an item:");
237
+ engine.step([RemoveFromCart.create({ productId: "prod-1" })]);
238
+ console.log(" Cart:", engine.getContext());
239
+ console.log();
240
+
241
+ // Add more items
242
+ console.log("4. Add more items:");
243
+ engine.step([AddToCart.create({ productId: "prod-3", quantity: 3, price: 15.99 })]);
244
+ console.log(" Cart:", engine.getContext());
245
+ console.log();
246
+
247
+ // Clear cart
248
+ console.log("5. Clear cart:");
249
+ engine.step([ClearCart.create({})]);
250
+ console.log(" Cart:", engine.getContext());
251
+ console.log();
252
+ }
253
+
254
+ // Run example if executed directly
255
+ if (import.meta.url === `file://${process.argv[1]}`) {
256
+ runExample();
257
+ }
258
+
259
+ export { createCartEngine, runExample };