@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,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 };
|