@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.
- package/FRAMEWORK.md +420 -0
- package/LICENSE +21 -0
- package/README.md +1310 -0
- package/dist/adapters/cli.d.ts +43 -0
- package/dist/adapters/cli.d.ts.map +1 -0
- package/dist/adapters/cli.js +126 -0
- package/dist/adapters/cli.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +26 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +233 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/cloud.d.ts +27 -0
- package/dist/cli/commands/cloud.d.ts.map +1 -0
- package/dist/cli/commands/cloud.js +232 -0
- package/dist/cli/commands/cloud.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +25 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +168 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +179 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cloud/auth.d.ts +51 -0
- package/dist/cloud/auth.d.ts.map +1 -0
- package/dist/cloud/auth.js +194 -0
- package/dist/cloud/auth.js.map +1 -0
- package/dist/cloud/billing.d.ts +184 -0
- package/dist/cloud/billing.d.ts.map +1 -0
- package/dist/cloud/billing.js +179 -0
- package/dist/cloud/billing.js.map +1 -0
- package/dist/cloud/client.d.ts +39 -0
- package/dist/cloud/client.d.ts.map +1 -0
- package/dist/cloud/client.js +176 -0
- package/dist/cloud/client.js.map +1 -0
- package/dist/cloud/index.d.ts +44 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +44 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/cloud/marketplace.d.ts +166 -0
- package/dist/cloud/marketplace.d.ts.map +1 -0
- package/dist/cloud/marketplace.js +159 -0
- package/dist/cloud/marketplace.js.map +1 -0
- package/dist/cloud/provisioning.d.ts +110 -0
- package/dist/cloud/provisioning.d.ts.map +1 -0
- package/dist/cloud/provisioning.js +148 -0
- package/dist/cloud/provisioning.js.map +1 -0
- package/dist/cloud/relay/endpoints.d.ts +62 -0
- package/dist/cloud/relay/endpoints.d.ts.map +1 -0
- package/dist/cloud/relay/endpoints.js +217 -0
- package/dist/cloud/relay/endpoints.js.map +1 -0
- package/dist/cloud/relay/health/index.d.ts +5 -0
- package/dist/cloud/relay/health/index.d.ts.map +1 -0
- package/dist/cloud/relay/health/index.js +9 -0
- package/dist/cloud/relay/health/index.js.map +1 -0
- package/dist/cloud/relay/stats/index.d.ts +5 -0
- package/dist/cloud/relay/stats/index.d.ts.map +1 -0
- package/dist/cloud/relay/stats/index.js +9 -0
- package/dist/cloud/relay/stats/index.js.map +1 -0
- package/dist/cloud/relay/sync/index.d.ts +5 -0
- package/dist/cloud/relay/sync/index.d.ts.map +1 -0
- package/dist/cloud/relay/sync/index.js +9 -0
- package/dist/cloud/relay/sync/index.js.map +1 -0
- package/dist/cloud/relay/usage/index.d.ts +5 -0
- package/dist/cloud/relay/usage/index.d.ts.map +1 -0
- package/dist/cloud/relay/usage/index.js +9 -0
- package/dist/cloud/relay/usage/index.js.map +1 -0
- package/dist/cloud/sponsors.d.ts +81 -0
- package/dist/cloud/sponsors.d.ts.map +1 -0
- package/dist/cloud/sponsors.js +130 -0
- package/dist/cloud/sponsors.js.map +1 -0
- package/dist/cloud/types.d.ts +169 -0
- package/dist/cloud/types.d.ts.map +1 -0
- package/dist/cloud/types.js +7 -0
- package/dist/cloud/types.js.map +1 -0
- package/dist/components/index.d.ts +43 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +17 -0
- package/dist/components/index.js.map +1 -0
- package/dist/core/actors.d.ts +95 -0
- package/dist/core/actors.d.ts.map +1 -0
- package/dist/core/actors.js +158 -0
- package/dist/core/actors.js.map +1 -0
- package/dist/core/component/generator.d.ts +122 -0
- package/dist/core/component/generator.d.ts.map +1 -0
- package/dist/core/component/generator.js +307 -0
- package/dist/core/component/generator.js.map +1 -0
- package/dist/core/engine.d.ts +92 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +199 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/introspection.d.ts +141 -0
- package/dist/core/introspection.d.ts.map +1 -0
- package/dist/core/introspection.js +208 -0
- package/dist/core/introspection.js.map +1 -0
- package/dist/core/logic/generator.d.ts +76 -0
- package/dist/core/logic/generator.d.ts.map +1 -0
- package/dist/core/logic/generator.js +339 -0
- package/dist/core/logic/generator.js.map +1 -0
- package/dist/core/pluresdb/generator.d.ts +58 -0
- package/dist/core/pluresdb/generator.d.ts.map +1 -0
- package/dist/core/pluresdb/generator.js +162 -0
- package/dist/core/pluresdb/generator.js.map +1 -0
- package/dist/core/protocol.d.ts +121 -0
- package/dist/core/protocol.d.ts.map +1 -0
- package/dist/core/protocol.js +46 -0
- package/dist/core/protocol.js.map +1 -0
- package/dist/core/rules.d.ts +120 -0
- package/dist/core/rules.d.ts.map +1 -0
- package/dist/core/rules.js +81 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/schema/loader.d.ts +47 -0
- package/dist/core/schema/loader.d.ts.map +1 -0
- package/dist/core/schema/loader.js +189 -0
- package/dist/core/schema/loader.js.map +1 -0
- package/dist/core/schema/normalize.d.ts +72 -0
- package/dist/core/schema/normalize.d.ts.map +1 -0
- package/dist/core/schema/normalize.js +190 -0
- package/dist/core/schema/normalize.js.map +1 -0
- package/dist/core/schema/types.d.ts +370 -0
- package/dist/core/schema/types.d.ts.map +1 -0
- package/dist/core/schema/types.js +161 -0
- package/dist/core/schema/types.js.map +1 -0
- package/dist/dsl/index.d.ts +152 -0
- package/dist/dsl/index.d.ts.map +1 -0
- package/dist/dsl/index.js +132 -0
- package/dist/dsl/index.js.map +1 -0
- package/dist/dsl.d.ts +124 -0
- package/dist/dsl.d.ts.map +1 -0
- package/dist/dsl.js +130 -0
- package/dist/dsl.js.map +1 -0
- package/dist/examples/advanced-todo/index.d.ts +55 -0
- package/dist/examples/advanced-todo/index.d.ts.map +1 -0
- package/dist/examples/advanced-todo/index.js +222 -0
- package/dist/examples/advanced-todo/index.js.map +1 -0
- package/dist/examples/auth-basic/index.d.ts +17 -0
- package/dist/examples/auth-basic/index.d.ts.map +1 -0
- package/dist/examples/auth-basic/index.js +122 -0
- package/dist/examples/auth-basic/index.js.map +1 -0
- package/dist/examples/cart/index.d.ts +19 -0
- package/dist/examples/cart/index.d.ts.map +1 -0
- package/dist/examples/cart/index.js +202 -0
- package/dist/examples/cart/index.js.map +1 -0
- package/dist/examples/hero-ecommerce/index.d.ts +39 -0
- package/dist/examples/hero-ecommerce/index.d.ts.map +1 -0
- package/dist/examples/hero-ecommerce/index.js +506 -0
- package/dist/examples/hero-ecommerce/index.js.map +1 -0
- package/dist/examples/svelte-counter/index.d.ts +31 -0
- package/dist/examples/svelte-counter/index.d.ts.map +1 -0
- package/dist/examples/svelte-counter/index.js +123 -0
- package/dist/examples/svelte-counter/index.js.map +1 -0
- package/dist/flows.d.ts +125 -0
- package/dist/flows.d.ts.map +1 -0
- package/dist/flows.js +160 -0
- package/dist/flows.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/pluresdb.d.ts +56 -0
- package/dist/integrations/pluresdb.d.ts.map +1 -0
- package/dist/integrations/pluresdb.js +46 -0
- package/dist/integrations/pluresdb.js.map +1 -0
- package/dist/integrations/svelte.d.ts +306 -0
- package/dist/integrations/svelte.d.ts.map +1 -0
- package/dist/integrations/svelte.js +447 -0
- package/dist/integrations/svelte.js.map +1 -0
- package/dist/registry.d.ts +94 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +181 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime/terminal-adapter.d.ts +105 -0
- package/dist/runtime/terminal-adapter.d.ts.map +1 -0
- package/dist/runtime/terminal-adapter.js +113 -0
- package/dist/runtime/terminal-adapter.js.map +1 -0
- package/dist/step.d.ts +34 -0
- package/dist/step.d.ts.map +1 -0
- package/dist/step.js +111 -0
- package/dist/step.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/MONETIZATION.md +394 -0
- package/docs/TERMINAL_NODE.md +588 -0
- package/docs/guides/canvas.md +389 -0
- package/docs/guides/getting-started.md +347 -0
- package/docs/guides/history-state-pattern.md +618 -0
- package/docs/guides/orchestration.md +617 -0
- package/docs/guides/parallel-state-pattern.md +767 -0
- package/docs/guides/svelte-integration.md +691 -0
- package/package.json +96 -0
- package/src/__tests__/actors.test.ts +270 -0
- package/src/__tests__/billing.test.ts +175 -0
- package/src/__tests__/cloud.test.ts +247 -0
- package/src/__tests__/dsl.test.ts +154 -0
- package/src/__tests__/edge-cases.test.ts +475 -0
- package/src/__tests__/engine.test.ts +137 -0
- package/src/__tests__/generators.test.ts +270 -0
- package/src/__tests__/introspection.test.ts +321 -0
- package/src/__tests__/protocol.test.ts +40 -0
- package/src/__tests__/provisioning.test.ts +162 -0
- package/src/__tests__/schema.test.ts +241 -0
- package/src/__tests__/svelte-integration.test.ts +431 -0
- package/src/__tests__/terminal-node.test.ts +352 -0
- package/src/adapters/cli.ts +175 -0
- package/src/cli/commands/auth.ts +271 -0
- package/src/cli/commands/cloud.ts +281 -0
- package/src/cli/commands/generate.ts +225 -0
- package/src/cli/index.ts +190 -0
- package/src/cloud/README.md +383 -0
- package/src/cloud/auth.ts +245 -0
- package/src/cloud/billing.ts +336 -0
- package/src/cloud/client.ts +221 -0
- package/src/cloud/index.ts +121 -0
- package/src/cloud/marketplace.ts +303 -0
- package/src/cloud/provisioning.ts +254 -0
- package/src/cloud/relay/endpoints.ts +307 -0
- package/src/cloud/relay/health/function.json +17 -0
- package/src/cloud/relay/health/index.ts +10 -0
- package/src/cloud/relay/host.json +15 -0
- package/src/cloud/relay/local.settings.json +8 -0
- package/src/cloud/relay/stats/function.json +17 -0
- package/src/cloud/relay/stats/index.ts +10 -0
- package/src/cloud/relay/sync/function.json +17 -0
- package/src/cloud/relay/sync/index.ts +10 -0
- package/src/cloud/relay/usage/function.json +17 -0
- package/src/cloud/relay/usage/index.ts +10 -0
- package/src/cloud/sponsors.ts +213 -0
- package/src/cloud/types.ts +198 -0
- package/src/components/README.md +125 -0
- package/src/components/TerminalNode.svelte +457 -0
- package/src/components/index.ts +46 -0
- package/src/core/actors.ts +205 -0
- package/src/core/component/generator.ts +432 -0
- package/src/core/engine.ts +243 -0
- package/src/core/introspection.ts +329 -0
- package/src/core/logic/generator.ts +420 -0
- package/src/core/pluresdb/generator.ts +229 -0
- package/src/core/protocol.ts +132 -0
- package/src/core/rules.ts +167 -0
- package/src/core/schema/loader.ts +247 -0
- package/src/core/schema/normalize.ts +322 -0
- package/src/core/schema/types.ts +557 -0
- package/src/dsl/index.ts +218 -0
- package/src/dsl.ts +214 -0
- package/src/examples/advanced-todo/App.svelte +506 -0
- package/src/examples/advanced-todo/README.md +371 -0
- package/src/examples/advanced-todo/index.ts +309 -0
- package/src/examples/auth-basic/index.ts +163 -0
- package/src/examples/cart/index.ts +259 -0
- package/src/examples/hero-ecommerce/index.ts +657 -0
- package/src/examples/svelte-counter/index.ts +168 -0
- package/src/flows.ts +268 -0
- package/src/index.ts +154 -0
- package/src/integrations/pluresdb.ts +93 -0
- package/src/integrations/svelte.ts +617 -0
- package/src/registry.ts +223 -0
- package/src/runtime/terminal-adapter.ts +175 -0
- package/src/step.ts +151 -0
- package/src/types.ts +70 -0
- package/templates/basic-app/README.md +147 -0
- 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 };
|