@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,767 @@
|
|
|
1
|
+
# Parallel State Pattern in Praxis
|
|
2
|
+
|
|
3
|
+
This guide explains how to implement parallel states in Praxis using multiple engines, similar to XState's parallel states.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Parallel states allow different parts of your application to maintain independent state machines that run concurrently. This is useful for:
|
|
8
|
+
|
|
9
|
+
- **Multi-region UIs**: Different panels operating independently
|
|
10
|
+
- **Concurrent Workflows**: Multiple processes running simultaneously
|
|
11
|
+
- **Complex Orchestration**: Coordinating independent subsystems
|
|
12
|
+
- **Feature Isolation**: Separate state machines for different features
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
1. [Multiple Engine Pattern](#multiple-engine-pattern)
|
|
17
|
+
2. [Coordinated Engines](#coordinated-engines)
|
|
18
|
+
3. [Parent-Child Hierarchy](#parent-child-hierarchy)
|
|
19
|
+
4. [Cross-Engine Communication](#cross-engine-communication)
|
|
20
|
+
5. [Svelte Integration](#svelte-integration)
|
|
21
|
+
6. [Real-World Examples](#real-world-examples)
|
|
22
|
+
|
|
23
|
+
## Multiple Engine Pattern
|
|
24
|
+
|
|
25
|
+
The simplest way to achieve parallel states is to create multiple independent engines.
|
|
26
|
+
|
|
27
|
+
### Basic Example
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createPraxisEngine, PraxisRegistry } from '@plures/praxis';
|
|
31
|
+
|
|
32
|
+
// Engine 1: Authentication
|
|
33
|
+
interface AuthContext {
|
|
34
|
+
user: User | null;
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const authRegistry = new PraxisRegistry<AuthContext>();
|
|
39
|
+
// ... register auth rules
|
|
40
|
+
const authEngine = createPraxisEngine({
|
|
41
|
+
initialContext: { user: null, isLoading: false },
|
|
42
|
+
registry: authRegistry,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Engine 2: Shopping Cart
|
|
46
|
+
interface CartContext {
|
|
47
|
+
items: CartItem[];
|
|
48
|
+
total: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const cartRegistry = new PraxisRegistry<CartContext>();
|
|
52
|
+
// ... register cart rules
|
|
53
|
+
const cartEngine = createPraxisEngine({
|
|
54
|
+
initialContext: { items: [], total: 0 },
|
|
55
|
+
registry: cartRegistry,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Engine 3: Notifications
|
|
59
|
+
interface NotificationContext {
|
|
60
|
+
notifications: Notification[];
|
|
61
|
+
unreadCount: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const notificationRegistry = new PraxisRegistry<NotificationContext>();
|
|
65
|
+
// ... register notification rules
|
|
66
|
+
const notificationEngine = createPraxisEngine({
|
|
67
|
+
initialContext: { notifications: [], unreadCount: 0 },
|
|
68
|
+
registry: notificationRegistry,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Use all engines in parallel
|
|
72
|
+
authEngine.step([Login.create({ username: 'alice' })]);
|
|
73
|
+
cartEngine.step([AddItem.create({ itemId: '123' })]);
|
|
74
|
+
notificationEngine.step([ShowNotification.create({ message: 'Welcome!' })]);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Independent Operation
|
|
78
|
+
|
|
79
|
+
Each engine operates completely independently:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Each engine maintains its own state
|
|
83
|
+
console.log('Auth:', authEngine.getContext());
|
|
84
|
+
console.log('Cart:', cartEngine.getContext());
|
|
85
|
+
console.log('Notifications:', notificationEngine.getContext());
|
|
86
|
+
|
|
87
|
+
// Events are processed independently
|
|
88
|
+
authEngine.step([Login.create({})]); // Only affects auth
|
|
89
|
+
cartEngine.step([Checkout.create({})]); // Only affects cart
|
|
90
|
+
notificationEngine.step([Clear.create({})]); // Only affects notifications
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Coordinated Engines
|
|
94
|
+
|
|
95
|
+
When engines need to coordinate, you can create a coordinator layer:
|
|
96
|
+
|
|
97
|
+
### Coordinator Pattern
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface CoordinatorEvents {
|
|
101
|
+
auth: PraxisEvent[];
|
|
102
|
+
cart: PraxisEvent[];
|
|
103
|
+
notifications: PraxisEvent[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class AppCoordinator {
|
|
107
|
+
constructor(
|
|
108
|
+
private authEngine: LogicEngine<AuthContext>,
|
|
109
|
+
private cartEngine: LogicEngine<CartContext>,
|
|
110
|
+
private notificationEngine: LogicEngine<NotificationContext>
|
|
111
|
+
) {}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Dispatch events to appropriate engines
|
|
115
|
+
*/
|
|
116
|
+
dispatch(events: CoordinatorEvents) {
|
|
117
|
+
const results = {
|
|
118
|
+
auth: this.authEngine.step(events.auth),
|
|
119
|
+
cart: this.cartEngine.step(events.cart),
|
|
120
|
+
notifications: this.notificationEngine.step(events.notifications),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Cross-engine reactions
|
|
124
|
+
this.handleCrossEngineEffects(results);
|
|
125
|
+
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handle cross-engine effects
|
|
131
|
+
*/
|
|
132
|
+
private handleCrossEngineEffects(results: any) {
|
|
133
|
+
// When user logs out, clear cart
|
|
134
|
+
if (results.auth.state.facts.some((f: any) => f.tag === 'UserLoggedOut')) {
|
|
135
|
+
this.cartEngine.step([ClearCart.create({})]);
|
|
136
|
+
this.notificationEngine.step([
|
|
137
|
+
ShowNotification.create({ message: 'Cart cleared' })
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// When order is placed, show notification
|
|
142
|
+
if (results.cart.state.facts.some((f: any) => f.tag === 'OrderPlaced')) {
|
|
143
|
+
this.notificationEngine.step([
|
|
144
|
+
ShowNotification.create({ message: 'Order placed successfully!' })
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// When cart is updated, check auth
|
|
149
|
+
if (results.cart.state.facts.some((f: any) => f.tag === 'CartUpdated')) {
|
|
150
|
+
const authContext = this.authEngine.getContext();
|
|
151
|
+
if (!authContext.user) {
|
|
152
|
+
this.notificationEngine.step([
|
|
153
|
+
ShowNotification.create({ message: 'Please login to continue' })
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get all contexts
|
|
161
|
+
*/
|
|
162
|
+
getState() {
|
|
163
|
+
return {
|
|
164
|
+
auth: this.authEngine.getContext(),
|
|
165
|
+
cart: this.cartEngine.getContext(),
|
|
166
|
+
notifications: this.notificationEngine.getContext(),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Usage
|
|
172
|
+
const coordinator = new AppCoordinator(
|
|
173
|
+
authEngine,
|
|
174
|
+
cartEngine,
|
|
175
|
+
notificationEngine
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
coordinator.dispatch({
|
|
179
|
+
auth: [Login.create({ username: 'alice' })],
|
|
180
|
+
cart: [AddItem.create({ itemId: '123' })],
|
|
181
|
+
notifications: [],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const state = coordinator.getState();
|
|
185
|
+
console.log('Current state:', state);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Event Bus Pattern
|
|
189
|
+
|
|
190
|
+
For more complex coordination, use an event bus:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
type EventHandler = (event: PraxisEvent) => void;
|
|
194
|
+
|
|
195
|
+
class EventBus {
|
|
196
|
+
private handlers = new Map<string, Set<EventHandler>>();
|
|
197
|
+
|
|
198
|
+
subscribe(eventType: string, handler: EventHandler) {
|
|
199
|
+
if (!this.handlers.has(eventType)) {
|
|
200
|
+
this.handlers.set(eventType, new Set());
|
|
201
|
+
}
|
|
202
|
+
this.handlers.get(eventType)!.add(handler);
|
|
203
|
+
|
|
204
|
+
// Return unsubscribe function
|
|
205
|
+
return () => {
|
|
206
|
+
this.handlers.get(eventType)?.delete(handler);
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
publish(event: PraxisEvent) {
|
|
211
|
+
const handlers = this.handlers.get(event.tag);
|
|
212
|
+
if (handlers) {
|
|
213
|
+
handlers.forEach(handler => handler(event));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Create shared event bus
|
|
219
|
+
const eventBus = new EventBus();
|
|
220
|
+
|
|
221
|
+
// Connect engines to event bus
|
|
222
|
+
eventBus.subscribe('USER_LOGGED_OUT', () => {
|
|
223
|
+
cartEngine.step([ClearCart.create({})]);
|
|
224
|
+
notificationEngine.step([
|
|
225
|
+
ShowNotification.create({ message: 'Cart cleared' })
|
|
226
|
+
]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
eventBus.subscribe('ORDER_PLACED', (event) => {
|
|
230
|
+
notificationEngine.step([
|
|
231
|
+
ShowNotification.create({ message: 'Order placed!' })
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
eventBus.subscribe('CART_ITEM_ADDED', (event) => {
|
|
236
|
+
const authContext = authEngine.getContext();
|
|
237
|
+
if (!authContext.user) {
|
|
238
|
+
notificationEngine.step([
|
|
239
|
+
ShowNotification.create({ message: 'Login to save your cart' })
|
|
240
|
+
]);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Dispatch events through bus
|
|
245
|
+
function dispatchWithBroadcast(engine: LogicEngine<any>, events: PraxisEvent[]) {
|
|
246
|
+
const result = engine.step(events);
|
|
247
|
+
|
|
248
|
+
// Broadcast facts as events
|
|
249
|
+
result.state.facts.forEach(fact => {
|
|
250
|
+
eventBus.publish({
|
|
251
|
+
tag: fact.tag,
|
|
252
|
+
payload: fact.payload,
|
|
253
|
+
timestamp: Date.now(),
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Parent-Child Hierarchy
|
|
262
|
+
|
|
263
|
+
For hierarchical state management:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
interface ParentContext {
|
|
267
|
+
mode: 'idle' | 'working' | 'error';
|
|
268
|
+
children: Map<string, any>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
class HierarchicalEngine {
|
|
272
|
+
private parentEngine: LogicEngine<ParentContext>;
|
|
273
|
+
private childEngines = new Map<string, LogicEngine<any>>();
|
|
274
|
+
|
|
275
|
+
constructor(parentEngine: LogicEngine<ParentContext>) {
|
|
276
|
+
this.parentEngine = parentEngine;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
addChild<T>(id: string, engine: LogicEngine<T>) {
|
|
280
|
+
this.childEngines.set(id, engine);
|
|
281
|
+
|
|
282
|
+
// Update parent context
|
|
283
|
+
const parentContext = this.parentEngine.getContext();
|
|
284
|
+
parentContext.children.set(id, engine.getContext());
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
removeChild(id: string) {
|
|
288
|
+
this.childEngines.delete(id);
|
|
289
|
+
|
|
290
|
+
const parentContext = this.parentEngine.getContext();
|
|
291
|
+
parentContext.children.delete(id);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
dispatch(parentEvents: PraxisEvent[], childEvents: Map<string, PraxisEvent[]>) {
|
|
295
|
+
// Process parent events
|
|
296
|
+
this.parentEngine.step(parentEvents);
|
|
297
|
+
|
|
298
|
+
// Process child events
|
|
299
|
+
for (const [childId, events] of childEvents) {
|
|
300
|
+
const child = this.childEngines.get(childId);
|
|
301
|
+
if (child) {
|
|
302
|
+
child.step(events);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Update parent's view of children
|
|
307
|
+
const parentContext = this.parentEngine.getContext();
|
|
308
|
+
for (const [id, child] of this.childEngines) {
|
|
309
|
+
parentContext.children.set(id, child.getContext());
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
getState() {
|
|
314
|
+
return {
|
|
315
|
+
parent: this.parentEngine.getContext(),
|
|
316
|
+
children: Array.from(this.childEngines.entries()).map(([id, engine]) => ({
|
|
317
|
+
id,
|
|
318
|
+
context: engine.getContext(),
|
|
319
|
+
})),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Usage
|
|
325
|
+
const parentEngine = createPraxisEngine<ParentContext>({
|
|
326
|
+
initialContext: {
|
|
327
|
+
mode: 'idle',
|
|
328
|
+
children: new Map(),
|
|
329
|
+
},
|
|
330
|
+
registry: parentRegistry,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const hierarchical = new HierarchicalEngine(parentEngine);
|
|
334
|
+
|
|
335
|
+
// Add child engines
|
|
336
|
+
hierarchical.addChild('auth', authEngine);
|
|
337
|
+
hierarchical.addChild('cart', cartEngine);
|
|
338
|
+
hierarchical.addChild('notifications', notificationEngine);
|
|
339
|
+
|
|
340
|
+
// Dispatch to multiple levels
|
|
341
|
+
hierarchical.dispatch(
|
|
342
|
+
[ParentModeChange.create({ mode: 'working' })],
|
|
343
|
+
new Map([
|
|
344
|
+
['auth', [Login.create({})]],
|
|
345
|
+
['cart', [AddItem.create({ itemId: '123' })]],
|
|
346
|
+
])
|
|
347
|
+
);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Cross-Engine Communication
|
|
351
|
+
|
|
352
|
+
### Shared Context Pattern
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
interface SharedState {
|
|
356
|
+
userId: string | null;
|
|
357
|
+
cartTotal: number;
|
|
358
|
+
unreadCount: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
class SharedStateManager {
|
|
362
|
+
private state: SharedState = {
|
|
363
|
+
userId: null,
|
|
364
|
+
cartTotal: 0,
|
|
365
|
+
unreadCount: 0,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
private subscribers = new Set<(state: SharedState) => void>();
|
|
369
|
+
|
|
370
|
+
getState(): SharedState {
|
|
371
|
+
return { ...this.state };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
update(partial: Partial<SharedState>) {
|
|
375
|
+
this.state = { ...this.state, ...partial };
|
|
376
|
+
this.notify();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
subscribe(callback: (state: SharedState) => void) {
|
|
380
|
+
this.subscribers.add(callback);
|
|
381
|
+
callback(this.state);
|
|
382
|
+
|
|
383
|
+
return () => {
|
|
384
|
+
this.subscribers.delete(callback);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private notify() {
|
|
389
|
+
this.subscribers.forEach(sub => sub(this.state));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Create shared state
|
|
394
|
+
const sharedState = new SharedStateManager();
|
|
395
|
+
|
|
396
|
+
// Connect engines to shared state
|
|
397
|
+
sharedState.subscribe((state) => {
|
|
398
|
+
// Update cart engine when user changes
|
|
399
|
+
if (state.userId) {
|
|
400
|
+
cartEngine.step([LoadUserCart.create({ userId: state.userId })]);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
sharedState.subscribe((state) => {
|
|
405
|
+
// Update notification badge
|
|
406
|
+
notificationEngine.step([
|
|
407
|
+
UpdateBadge.create({ count: state.unreadCount })
|
|
408
|
+
]);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Engines update shared state
|
|
412
|
+
authEngine.step([Login.create({})]);
|
|
413
|
+
sharedState.update({ userId: authEngine.getContext().user?.id || null });
|
|
414
|
+
|
|
415
|
+
cartEngine.step([UpdateCart.create({})]);
|
|
416
|
+
sharedState.update({ cartTotal: cartEngine.getContext().total });
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Svelte Integration
|
|
420
|
+
|
|
421
|
+
Using parallel engines with Svelte 5:
|
|
422
|
+
|
|
423
|
+
### Multiple Engines in Component
|
|
424
|
+
|
|
425
|
+
```svelte
|
|
426
|
+
<script lang="ts">
|
|
427
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
428
|
+
import {
|
|
429
|
+
createAuthEngine,
|
|
430
|
+
createCartEngine,
|
|
431
|
+
createNotificationEngine
|
|
432
|
+
} from './engines';
|
|
433
|
+
|
|
434
|
+
// Create independent engines
|
|
435
|
+
const authEngine = createAuthEngine();
|
|
436
|
+
const cartEngine = createCartEngine();
|
|
437
|
+
const notificationEngine = createNotificationEngine();
|
|
438
|
+
|
|
439
|
+
// Bind to Svelte
|
|
440
|
+
const auth = usePraxisEngine(authEngine);
|
|
441
|
+
const cart = usePraxisEngine(cartEngine);
|
|
442
|
+
const notifications = usePraxisEngine(notificationEngine);
|
|
443
|
+
|
|
444
|
+
// Cross-engine reactions
|
|
445
|
+
$: if (auth.context.user) {
|
|
446
|
+
cart.dispatch([LoadUserCart.create({
|
|
447
|
+
userId: auth.context.user.id
|
|
448
|
+
})]);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
$: if (cart.context.items.length > 0 && !auth.context.user) {
|
|
452
|
+
notifications.dispatch([ShowNotification.create({
|
|
453
|
+
message: 'Please login to continue'
|
|
454
|
+
})]);
|
|
455
|
+
}
|
|
456
|
+
</script>
|
|
457
|
+
|
|
458
|
+
<div class="app">
|
|
459
|
+
<!-- Auth UI -->
|
|
460
|
+
<header>
|
|
461
|
+
{#if auth.context.user}
|
|
462
|
+
<p>Welcome, {auth.context.user.name}!</p>
|
|
463
|
+
<button onclick={() => auth.dispatch([Logout.create({})])}>
|
|
464
|
+
Logout
|
|
465
|
+
</button>
|
|
466
|
+
{:else}
|
|
467
|
+
<button onclick={() => auth.dispatch([Login.create({})])}>
|
|
468
|
+
Login
|
|
469
|
+
</button>
|
|
470
|
+
{/if}
|
|
471
|
+
</header>
|
|
472
|
+
|
|
473
|
+
<!-- Cart UI -->
|
|
474
|
+
<main>
|
|
475
|
+
<h2>Shopping Cart ({cart.context.items.length})</h2>
|
|
476
|
+
<ul>
|
|
477
|
+
{#each cart.context.items as item}
|
|
478
|
+
<li>{item.name} - ${item.price}</li>
|
|
479
|
+
{/each}
|
|
480
|
+
</ul>
|
|
481
|
+
<p>Total: ${cart.context.total}</p>
|
|
482
|
+
</main>
|
|
483
|
+
|
|
484
|
+
<!-- Notifications UI -->
|
|
485
|
+
<aside>
|
|
486
|
+
{#each notifications.context.notifications as notification}
|
|
487
|
+
<div class="notification">
|
|
488
|
+
{notification.message}
|
|
489
|
+
</div>
|
|
490
|
+
{/each}
|
|
491
|
+
</aside>
|
|
492
|
+
</div>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Coordinator Component
|
|
496
|
+
|
|
497
|
+
```svelte
|
|
498
|
+
<script lang="ts">
|
|
499
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
500
|
+
import { AppCoordinator } from './coordinator';
|
|
501
|
+
import {
|
|
502
|
+
createAuthEngine,
|
|
503
|
+
createCartEngine,
|
|
504
|
+
createNotificationEngine
|
|
505
|
+
} from './engines';
|
|
506
|
+
|
|
507
|
+
// Create coordinator
|
|
508
|
+
const coordinator = new AppCoordinator(
|
|
509
|
+
createAuthEngine(),
|
|
510
|
+
createCartEngine(),
|
|
511
|
+
createNotificationEngine()
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// Track overall state
|
|
515
|
+
let state = $state(coordinator.getState());
|
|
516
|
+
|
|
517
|
+
function dispatch(events: CoordinatorEvents) {
|
|
518
|
+
coordinator.dispatch(events);
|
|
519
|
+
state = coordinator.getState();
|
|
520
|
+
}
|
|
521
|
+
</script>
|
|
522
|
+
|
|
523
|
+
<div class="app">
|
|
524
|
+
<header>
|
|
525
|
+
<p>User: {state.auth.user?.name || 'Guest'}</p>
|
|
526
|
+
<p>Cart: {state.cart.items.length} items</p>
|
|
527
|
+
<p>Notifications: {state.notifications.unreadCount}</p>
|
|
528
|
+
</header>
|
|
529
|
+
|
|
530
|
+
<main>
|
|
531
|
+
<button onclick={() => dispatch({
|
|
532
|
+
auth: [Login.create({})],
|
|
533
|
+
cart: [],
|
|
534
|
+
notifications: []
|
|
535
|
+
})}>
|
|
536
|
+
Login
|
|
537
|
+
</button>
|
|
538
|
+
|
|
539
|
+
<button onclick={() => dispatch({
|
|
540
|
+
auth: [],
|
|
541
|
+
cart: [AddItem.create({ itemId: '123' })],
|
|
542
|
+
notifications: []
|
|
543
|
+
})}>
|
|
544
|
+
Add to Cart
|
|
545
|
+
</button>
|
|
546
|
+
</main>
|
|
547
|
+
</div>
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Real-World Examples
|
|
551
|
+
|
|
552
|
+
### E-Commerce Application
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// Create engines for different concerns
|
|
556
|
+
const engines = {
|
|
557
|
+
auth: createAuthEngine(),
|
|
558
|
+
catalog: createCatalogEngine(),
|
|
559
|
+
cart: createCartEngine(),
|
|
560
|
+
checkout: createCheckoutEngine(),
|
|
561
|
+
notifications: createNotificationEngine(),
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// Set up cross-engine coordination
|
|
565
|
+
function setupCoordination() {
|
|
566
|
+
// When user logs in, load their cart
|
|
567
|
+
authEngine.subscribe((state) => {
|
|
568
|
+
if (state.context.user) {
|
|
569
|
+
cartEngine.step([
|
|
570
|
+
LoadCart.create({ userId: state.context.user.id })
|
|
571
|
+
]);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// When cart updates, update checkout
|
|
576
|
+
cartEngine.subscribe((state) => {
|
|
577
|
+
checkoutEngine.step([
|
|
578
|
+
UpdateTotal.create({ total: state.context.total })
|
|
579
|
+
]);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// When checkout completes, clear cart and show notification
|
|
583
|
+
checkoutEngine.subscribe((state) => {
|
|
584
|
+
if (state.facts.some(f => f.tag === 'OrderPlaced')) {
|
|
585
|
+
cartEngine.step([ClearCart.create({})]);
|
|
586
|
+
notificationEngine.step([
|
|
587
|
+
ShowNotification.create({ message: 'Order placed!' })
|
|
588
|
+
]);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Multi-Panel Dashboard
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// Create engine for each dashboard panel
|
|
598
|
+
const panels = {
|
|
599
|
+
metrics: createMetricsEngine(),
|
|
600
|
+
logs: createLogsEngine(),
|
|
601
|
+
alerts: createAlertsEngine(),
|
|
602
|
+
settings: createSettingsEngine(),
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// Each panel operates independently
|
|
606
|
+
function updatePanel(panelId: keyof typeof panels, events: PraxisEvent[]) {
|
|
607
|
+
panels[panelId].step(events);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Shared coordination for common actions
|
|
611
|
+
function refreshAll() {
|
|
612
|
+
Object.values(panels).forEach(panel => {
|
|
613
|
+
panel.step([Refresh.create({})]);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function exportAll() {
|
|
618
|
+
const data = Object.entries(panels).map(([id, panel]) => ({
|
|
619
|
+
panel: id,
|
|
620
|
+
data: panel.getContext(),
|
|
621
|
+
}));
|
|
622
|
+
|
|
623
|
+
downloadJSON(data, 'dashboard-export.json');
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### Multiplayer Game
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
// Engine for each game aspect
|
|
631
|
+
const game = {
|
|
632
|
+
player: createPlayerEngine(),
|
|
633
|
+
inventory: createInventoryEngine(),
|
|
634
|
+
chat: createChatEngine(),
|
|
635
|
+
world: createWorldEngine(),
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// Process game tick
|
|
639
|
+
function gameTick(deltaTime: number) {
|
|
640
|
+
// Update all engines
|
|
641
|
+
game.player.step([Tick.create({ deltaTime })]);
|
|
642
|
+
game.inventory.step([Tick.create({ deltaTime })]);
|
|
643
|
+
game.world.step([Tick.create({ deltaTime })]);
|
|
644
|
+
|
|
645
|
+
// Sync states
|
|
646
|
+
const playerPos = game.player.getContext().position;
|
|
647
|
+
game.world.step([UpdatePlayerPosition.create({ position: playerPos })]);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Handle player actions
|
|
651
|
+
function handlePlayerAction(action: string) {
|
|
652
|
+
switch (action) {
|
|
653
|
+
case 'USE_ITEM':
|
|
654
|
+
const item = game.inventory.getContext().selectedItem;
|
|
655
|
+
game.player.step([UseItem.create({ item })]);
|
|
656
|
+
game.inventory.step([RemoveItem.create({ item })]);
|
|
657
|
+
break;
|
|
658
|
+
|
|
659
|
+
case 'SEND_MESSAGE':
|
|
660
|
+
const player = game.player.getContext();
|
|
661
|
+
game.chat.step([SendMessage.create({
|
|
662
|
+
player: player.name,
|
|
663
|
+
message: player.currentMessage
|
|
664
|
+
})]);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Best Practices
|
|
671
|
+
|
|
672
|
+
### 1. Clear Boundaries
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
// ✅ Good: Clear separation of concerns
|
|
676
|
+
const userEngine = createUserEngine(); // User state only
|
|
677
|
+
const ordersEngine = createOrdersEngine(); // Orders only
|
|
678
|
+
const cartEngine = createCartEngine(); // Cart only
|
|
679
|
+
|
|
680
|
+
// ❌ Bad: Mixing concerns
|
|
681
|
+
const everythingEngine = createEverythingEngine(); // Too much
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### 2. Minimal Communication
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
// ✅ Good: Explicit coordination points
|
|
688
|
+
eventBus.subscribe('USER_LOGGED_OUT', () => {
|
|
689
|
+
cartEngine.step([ClearCart.create({})]);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// ❌ Bad: Tight coupling
|
|
693
|
+
cartEngine.setAuthEngine(authEngine); // Don't do this
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### 3. Independent Testing
|
|
697
|
+
|
|
698
|
+
```typescript
|
|
699
|
+
// ✅ Each engine can be tested independently
|
|
700
|
+
describe('Cart Engine', () => {
|
|
701
|
+
it('should add items', () => {
|
|
702
|
+
const engine = createCartEngine();
|
|
703
|
+
engine.step([AddItem.create({ itemId: '123' })]);
|
|
704
|
+
expect(engine.getContext().items.length).toBe(1);
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
describe('Auth Engine', () => {
|
|
709
|
+
it('should login users', () => {
|
|
710
|
+
const engine = createAuthEngine();
|
|
711
|
+
engine.step([Login.create({ username: 'alice' })]);
|
|
712
|
+
expect(engine.getContext().user).toBeTruthy();
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### 4. Document Dependencies
|
|
718
|
+
|
|
719
|
+
```typescript
|
|
720
|
+
/**
|
|
721
|
+
* Shopping Cart Engine
|
|
722
|
+
*
|
|
723
|
+
* Dependencies:
|
|
724
|
+
* - Auth engine: Requires user ID to load cart
|
|
725
|
+
* - Checkout engine: Sends total for checkout
|
|
726
|
+
*
|
|
727
|
+
* Emits:
|
|
728
|
+
* - CartUpdated: When items change
|
|
729
|
+
* - CartCleared: When cart is emptied
|
|
730
|
+
*/
|
|
731
|
+
export function createCartEngine() {
|
|
732
|
+
// ...
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Comparison with XState
|
|
737
|
+
|
|
738
|
+
| Feature | XState | Praxis |
|
|
739
|
+
|---------|--------|--------|
|
|
740
|
+
| **Parallel States** | `type: 'parallel'` | Multiple engines |
|
|
741
|
+
| **Coordination** | Parent state machine | Coordinator pattern |
|
|
742
|
+
| **Communication** | Event forwarding | Event bus or shared state |
|
|
743
|
+
| **Hierarchy** | Nested states | Parent-child engines |
|
|
744
|
+
| **Testing** | Test parent machine | Test each engine independently |
|
|
745
|
+
|
|
746
|
+
## Summary
|
|
747
|
+
|
|
748
|
+
The parallel state pattern in Praxis provides:
|
|
749
|
+
|
|
750
|
+
- ✅ **Independent Engines**: Each subsystem has its own engine
|
|
751
|
+
- ✅ **Flexible Coordination**: Event bus, coordinator, or shared state
|
|
752
|
+
- ✅ **Clear Boundaries**: Separation of concerns
|
|
753
|
+
- ✅ **Easy Testing**: Test engines independently
|
|
754
|
+
- ✅ **Svelte Integration**: Use multiple engines in components
|
|
755
|
+
- ✅ **Scalable**: Add or remove engines as needed
|
|
756
|
+
|
|
757
|
+
Choose the right pattern based on your needs:
|
|
758
|
+
- **Independent engines**: When subsystems don't interact
|
|
759
|
+
- **Event bus**: When you need loose coupling with pub/sub
|
|
760
|
+
- **Coordinator**: When you need centralized orchestration
|
|
761
|
+
- **Shared state**: When engines need to share data
|
|
762
|
+
- **Hierarchy**: When you have parent-child relationships
|
|
763
|
+
|
|
764
|
+
For more examples, see:
|
|
765
|
+
- [E-Commerce Example](/src/examples/hero-ecommerce/)
|
|
766
|
+
- [Auth Example](/src/examples/auth-basic/)
|
|
767
|
+
- [Multiple Engine Tests](/src/__tests__/edge-cases.test.ts)
|