@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,691 @@
|
|
|
1
|
+
# Praxis Svelte 5 Integration Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to using Praxis with Svelte 5, including both traditional stores and modern runes.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Praxis provides first-class Svelte 5 support through the `@plures/praxis/svelte` export. The integration offers:
|
|
8
|
+
|
|
9
|
+
- ✅ **Traditional Stores**: Backward-compatible store API
|
|
10
|
+
- ✅ **Svelte 5 Runes**: Modern `$state`, `$derived`, `$effect` support
|
|
11
|
+
- ✅ **History & Time-Travel**: Built-in undo/redo and debugging
|
|
12
|
+
- ✅ **Type Safety**: Full TypeScript support
|
|
13
|
+
- ✅ **Automatic Cleanup**: No memory leaks
|
|
14
|
+
- ✅ **Zero Config**: Works out of the box
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @plures/praxis svelte@^5.0.0
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Option 1: Store API (Traditional)
|
|
25
|
+
|
|
26
|
+
```svelte
|
|
27
|
+
<script lang="ts">
|
|
28
|
+
import { createPraxisStore } from '@plures/praxis/svelte';
|
|
29
|
+
import { myEngine, Increment } from './my-engine';
|
|
30
|
+
|
|
31
|
+
const store = createPraxisStore(myEngine);
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div>
|
|
35
|
+
<p>Count: {$store.context.count}</p>
|
|
36
|
+
<button onclick={() => store.dispatch([Increment.create({})])}>
|
|
37
|
+
Increment
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Option 2: Runes API (Svelte 5)
|
|
43
|
+
|
|
44
|
+
```svelte
|
|
45
|
+
<script lang="ts">
|
|
46
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
47
|
+
import { myEngine, Increment } from './my-engine';
|
|
48
|
+
|
|
49
|
+
const { context, dispatch } = usePraxisEngine(myEngine);
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div>
|
|
53
|
+
<p>Count: {context.count}</p>
|
|
54
|
+
<button onclick={() => dispatch([Increment.create({})])}>
|
|
55
|
+
Increment
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Store API
|
|
61
|
+
|
|
62
|
+
### createPraxisStore
|
|
63
|
+
|
|
64
|
+
Creates a Svelte store that tracks the full engine state.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createPraxisStore } from '@plures/praxis/svelte';
|
|
68
|
+
import type { Readable } from '@plures/praxis/svelte';
|
|
69
|
+
|
|
70
|
+
const store = createPraxisStore(engine);
|
|
71
|
+
|
|
72
|
+
// Subscribe to changes
|
|
73
|
+
const unsubscribe = store.subscribe((state) => {
|
|
74
|
+
console.log('State:', state.context);
|
|
75
|
+
console.log('Facts:', state.facts);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Dispatch events
|
|
79
|
+
store.dispatch([MyEvent.create({ data: 'value' })]);
|
|
80
|
+
|
|
81
|
+
// Clean up
|
|
82
|
+
unsubscribe();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Use in Svelte:**
|
|
86
|
+
|
|
87
|
+
```svelte
|
|
88
|
+
<script>
|
|
89
|
+
import { createPraxisStore } from '@plures/praxis/svelte';
|
|
90
|
+
import { engine, Increment } from './engine';
|
|
91
|
+
|
|
92
|
+
const store = createPraxisStore(engine);
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<div>
|
|
96
|
+
<h2>Full State Access</h2>
|
|
97
|
+
<p>Count: {$store.context.count}</p>
|
|
98
|
+
<p>Facts: {$store.facts.length}</p>
|
|
99
|
+
<p>Protocol: {$store.protocolVersion}</p>
|
|
100
|
+
|
|
101
|
+
<button onclick={() => store.dispatch([Increment.create({})])}>
|
|
102
|
+
Increment
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### createContextStore
|
|
108
|
+
|
|
109
|
+
Creates a store that tracks only the engine context.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { createContextStore } from '@plures/praxis/svelte';
|
|
113
|
+
|
|
114
|
+
const contextStore = createContextStore(engine);
|
|
115
|
+
|
|
116
|
+
// Subscribe to context changes only
|
|
117
|
+
contextStore.subscribe((context) => {
|
|
118
|
+
console.log('Context updated:', context);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Dispatch events
|
|
122
|
+
contextStore.dispatch([MyEvent.create({})]);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Use in Svelte:**
|
|
126
|
+
|
|
127
|
+
```svelte
|
|
128
|
+
<script>
|
|
129
|
+
import { createContextStore } from '@plures/praxis/svelte';
|
|
130
|
+
import { engine, UpdateUser } from './engine';
|
|
131
|
+
|
|
132
|
+
const context = createContextStore(engine);
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<div>
|
|
136
|
+
<h2>User: {$context.user?.name || 'Guest'}</h2>
|
|
137
|
+
<p>Page: {$context.currentPage}</p>
|
|
138
|
+
|
|
139
|
+
<button onclick={() => context.dispatch([UpdateUser.create({ name: 'Alice' })])}>
|
|
140
|
+
Update User
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### createDerivedStore
|
|
146
|
+
|
|
147
|
+
Creates a store that extracts and tracks a specific value from the context.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createDerivedStore } from '@plures/praxis/svelte';
|
|
151
|
+
|
|
152
|
+
const countStore = createDerivedStore(
|
|
153
|
+
engine,
|
|
154
|
+
(context) => context.count
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Only updates when the derived value changes
|
|
158
|
+
countStore.subscribe((count) => {
|
|
159
|
+
console.log('Count:', count);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
countStore.dispatch([Increment.create({})]);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Use in Svelte:**
|
|
166
|
+
|
|
167
|
+
```svelte
|
|
168
|
+
<script>
|
|
169
|
+
import { createDerivedStore } from '@plures/praxis/svelte';
|
|
170
|
+
import { engine, Increment } from './engine';
|
|
171
|
+
|
|
172
|
+
const count = createDerivedStore(engine, (ctx) => ctx.count);
|
|
173
|
+
const userName = createDerivedStore(engine, (ctx) => ctx.user?.name);
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<div>
|
|
177
|
+
<p>Count: {$count}</p>
|
|
178
|
+
<p>User: {$userName || 'Anonymous'}</p>
|
|
179
|
+
|
|
180
|
+
<button onclick={() => count.dispatch([Increment.create({})])}>
|
|
181
|
+
Increment
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Performance Note:** `createDerivedStore` only notifies subscribers when the selected value actually changes, making it more efficient than subscribing to the full state.
|
|
187
|
+
|
|
188
|
+
## Runes API (Svelte 5)
|
|
189
|
+
|
|
190
|
+
### usePraxisEngine
|
|
191
|
+
|
|
192
|
+
The main composable for Svelte 5 runes. Provides reactive access to engine state with optional history tracking.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
196
|
+
|
|
197
|
+
const {
|
|
198
|
+
state, // Full state (reactive)
|
|
199
|
+
context, // Context (reactive)
|
|
200
|
+
facts, // Facts array (reactive)
|
|
201
|
+
dispatch, // Dispatch events
|
|
202
|
+
// History features (when enableHistory: true)
|
|
203
|
+
snapshots, // Array of state snapshots
|
|
204
|
+
undo, // Undo last action
|
|
205
|
+
redo, // Redo action
|
|
206
|
+
canUndo, // Boolean: can undo?
|
|
207
|
+
canRedo, // Boolean: can redo?
|
|
208
|
+
historyIndex, // Current position in history
|
|
209
|
+
goToSnapshot, // Jump to specific snapshot
|
|
210
|
+
} = usePraxisEngine(engine, {
|
|
211
|
+
enableHistory: true,
|
|
212
|
+
maxHistorySize: 50,
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Basic Example:**
|
|
217
|
+
|
|
218
|
+
```svelte
|
|
219
|
+
<script lang="ts">
|
|
220
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
221
|
+
import { createCounterEngine, Increment, Decrement } from './counter';
|
|
222
|
+
|
|
223
|
+
const engine = createCounterEngine();
|
|
224
|
+
const { context, dispatch } = usePraxisEngine(engine);
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<div>
|
|
228
|
+
<h1>Counter: {context.count}</h1>
|
|
229
|
+
<button onclick={() => dispatch([Increment.create({})])}>+</button>
|
|
230
|
+
<button onclick={() => dispatch([Decrement.create({})])}>-</button>
|
|
231
|
+
</div>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**With History:**
|
|
235
|
+
|
|
236
|
+
```svelte
|
|
237
|
+
<script lang="ts">
|
|
238
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
239
|
+
import { createCounterEngine, Increment } from './counter';
|
|
240
|
+
|
|
241
|
+
const engine = createCounterEngine();
|
|
242
|
+
const {
|
|
243
|
+
context,
|
|
244
|
+
dispatch,
|
|
245
|
+
undo,
|
|
246
|
+
redo,
|
|
247
|
+
canUndo,
|
|
248
|
+
canRedo,
|
|
249
|
+
snapshots,
|
|
250
|
+
historyIndex
|
|
251
|
+
} = usePraxisEngine(engine, {
|
|
252
|
+
enableHistory: true,
|
|
253
|
+
maxHistorySize: 50
|
|
254
|
+
});
|
|
255
|
+
</script>
|
|
256
|
+
|
|
257
|
+
<div class="counter-app">
|
|
258
|
+
<header>
|
|
259
|
+
<h1>Counter: {context.count}</h1>
|
|
260
|
+
<p>History: {historyIndex + 1} / {snapshots.length}</p>
|
|
261
|
+
</header>
|
|
262
|
+
|
|
263
|
+
<main>
|
|
264
|
+
<button onclick={() => dispatch([Increment.create({ amount: 1 })])}>
|
|
265
|
+
+1
|
|
266
|
+
</button>
|
|
267
|
+
<button onclick={() => dispatch([Increment.create({ amount: 5 })])}>
|
|
268
|
+
+5
|
|
269
|
+
</button>
|
|
270
|
+
</main>
|
|
271
|
+
|
|
272
|
+
<footer>
|
|
273
|
+
<button onclick={undo} disabled={!canUndo}>
|
|
274
|
+
⟲ Undo
|
|
275
|
+
</button>
|
|
276
|
+
<button onclick={redo} disabled={!canRedo}>
|
|
277
|
+
⟳ Redo
|
|
278
|
+
</button>
|
|
279
|
+
</footer>
|
|
280
|
+
</div>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### usePraxisContext
|
|
284
|
+
|
|
285
|
+
Extract a specific value from the engine context.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { usePraxisContext } from '@plures/praxis/svelte';
|
|
289
|
+
|
|
290
|
+
const count = usePraxisContext(engine, (ctx) => ctx.count);
|
|
291
|
+
const userName = usePraxisContext(engine, (ctx) => ctx.user?.name);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Example:**
|
|
295
|
+
|
|
296
|
+
```svelte
|
|
297
|
+
<script lang="ts">
|
|
298
|
+
import { usePraxisContext } from '@plures/praxis/svelte';
|
|
299
|
+
import { engine } from './engine';
|
|
300
|
+
|
|
301
|
+
const count = usePraxisContext(engine, (ctx) => ctx.count);
|
|
302
|
+
const isAuthenticated = usePraxisContext(engine, (ctx) => !!ctx.user);
|
|
303
|
+
</script>
|
|
304
|
+
|
|
305
|
+
<div>
|
|
306
|
+
<p>Count: {count}</p>
|
|
307
|
+
<p>Authenticated: {isAuthenticated}</p>
|
|
308
|
+
</div>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### usePraxisSubscription
|
|
312
|
+
|
|
313
|
+
Subscribe to engine state changes with automatic cleanup.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { usePraxisSubscription } from '@plures/praxis/svelte';
|
|
317
|
+
|
|
318
|
+
// Automatically cleaned up when component unmounts
|
|
319
|
+
usePraxisSubscription(engine, (state) => {
|
|
320
|
+
console.log('State changed:', state);
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Example:**
|
|
325
|
+
|
|
326
|
+
```svelte
|
|
327
|
+
<script lang="ts">
|
|
328
|
+
import { usePraxisSubscription } from '@plures/praxis/svelte';
|
|
329
|
+
import { engine } from './engine';
|
|
330
|
+
|
|
331
|
+
// Log all state changes
|
|
332
|
+
usePraxisSubscription(engine, (state) => {
|
|
333
|
+
console.log('Context:', state.context);
|
|
334
|
+
console.log('Facts:', state.facts.length);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Persist to localStorage
|
|
338
|
+
usePraxisSubscription(engine, (state) => {
|
|
339
|
+
localStorage.setItem('app-state', JSON.stringify(state.context));
|
|
340
|
+
});
|
|
341
|
+
</script>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## History & Time-Travel
|
|
345
|
+
|
|
346
|
+
### Time-Travel Debugging
|
|
347
|
+
|
|
348
|
+
```svelte
|
|
349
|
+
<script lang="ts">
|
|
350
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
351
|
+
import { createMyEngine } from './engine';
|
|
352
|
+
|
|
353
|
+
const engine = createMyEngine();
|
|
354
|
+
const {
|
|
355
|
+
context,
|
|
356
|
+
snapshots,
|
|
357
|
+
goToSnapshot,
|
|
358
|
+
historyIndex
|
|
359
|
+
} = usePraxisEngine(engine, { enableHistory: true });
|
|
360
|
+
</script>
|
|
361
|
+
|
|
362
|
+
<div class="debugger">
|
|
363
|
+
<h2>Time-Travel Debugger</h2>
|
|
364
|
+
|
|
365
|
+
<div class="timeline">
|
|
366
|
+
{#each snapshots as snapshot, index}
|
|
367
|
+
<button
|
|
368
|
+
class:active={index === historyIndex}
|
|
369
|
+
onclick={() => goToSnapshot(index)}
|
|
370
|
+
>
|
|
371
|
+
{new Date(snapshot.timestamp).toLocaleTimeString()}
|
|
372
|
+
<br />
|
|
373
|
+
{snapshot.events.length} events
|
|
374
|
+
</button>
|
|
375
|
+
{/each}
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
<div class="state-viewer">
|
|
379
|
+
<h3>State at {new Date(snapshots[historyIndex]?.timestamp).toLocaleString()}</h3>
|
|
380
|
+
<pre>{JSON.stringify(context, null, 2)}</pre>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<style>
|
|
385
|
+
.timeline {
|
|
386
|
+
display: flex;
|
|
387
|
+
gap: 0.5rem;
|
|
388
|
+
overflow-x: auto;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.timeline button {
|
|
392
|
+
padding: 0.5rem;
|
|
393
|
+
border: 2px solid #ccc;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.timeline button.active {
|
|
397
|
+
border-color: #007bff;
|
|
398
|
+
background: #e7f3ff;
|
|
399
|
+
}
|
|
400
|
+
</style>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Undo/Redo UI
|
|
404
|
+
|
|
405
|
+
```svelte
|
|
406
|
+
<script lang="ts">
|
|
407
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
408
|
+
import { createTextEngine, InsertText, DeleteText } from './text-engine';
|
|
409
|
+
|
|
410
|
+
const engine = createTextEngine();
|
|
411
|
+
const { context, dispatch, undo, redo, canUndo, canRedo } =
|
|
412
|
+
usePraxisEngine(engine, { enableHistory: true });
|
|
413
|
+
|
|
414
|
+
let input = '';
|
|
415
|
+
|
|
416
|
+
function handleInsert() {
|
|
417
|
+
if (input.trim()) {
|
|
418
|
+
dispatch([InsertText.create({ text: input })], 'Insert Text');
|
|
419
|
+
input = '';
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
</script>
|
|
423
|
+
|
|
424
|
+
<div class="text-editor">
|
|
425
|
+
<div class="toolbar">
|
|
426
|
+
<button onclick={undo} disabled={!canUndo} title="Undo (Ctrl+Z)">
|
|
427
|
+
⟲ Undo
|
|
428
|
+
</button>
|
|
429
|
+
<button onclick={redo} disabled={!canRedo} title="Redo (Ctrl+Y)">
|
|
430
|
+
⟳ Redo
|
|
431
|
+
</button>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<div class="editor">
|
|
435
|
+
<textarea bind:value={context.content} readonly></textarea>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<div class="input">
|
|
439
|
+
<input
|
|
440
|
+
type="text"
|
|
441
|
+
bind:value={input}
|
|
442
|
+
onkeypress={(e) => e.key === 'Enter' && handleInsert()}
|
|
443
|
+
placeholder="Type text..."
|
|
444
|
+
/>
|
|
445
|
+
<button onclick={handleInsert}>Insert</button>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Advanced Patterns
|
|
451
|
+
|
|
452
|
+
### Multiple Engines
|
|
453
|
+
|
|
454
|
+
```svelte
|
|
455
|
+
<script lang="ts">
|
|
456
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
457
|
+
import { createAuthEngine, createCartEngine } from './engines';
|
|
458
|
+
|
|
459
|
+
const authEngine = createAuthEngine();
|
|
460
|
+
const cartEngine = createCartEngine();
|
|
461
|
+
|
|
462
|
+
const auth = usePraxisEngine(authEngine);
|
|
463
|
+
const cart = usePraxisEngine(cartEngine);
|
|
464
|
+
</script>
|
|
465
|
+
|
|
466
|
+
<div>
|
|
467
|
+
<header>
|
|
468
|
+
{#if auth.context.user}
|
|
469
|
+
<p>Welcome, {auth.context.user.name}!</p>
|
|
470
|
+
<button onclick={() => auth.dispatch([Logout.create({})])}>
|
|
471
|
+
Logout
|
|
472
|
+
</button>
|
|
473
|
+
{:else}
|
|
474
|
+
<button onclick={() => auth.dispatch([Login.create({})])}>
|
|
475
|
+
Login
|
|
476
|
+
</button>
|
|
477
|
+
{/if}
|
|
478
|
+
</header>
|
|
479
|
+
|
|
480
|
+
<main>
|
|
481
|
+
<p>Cart Items: {cart.context.items.length}</p>
|
|
482
|
+
<button onclick={() => cart.dispatch([AddToCart.create({ id: '1' })])}>
|
|
483
|
+
Add Item
|
|
484
|
+
</button>
|
|
485
|
+
</main>
|
|
486
|
+
</div>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Computed Values
|
|
490
|
+
|
|
491
|
+
```svelte
|
|
492
|
+
<script lang="ts">
|
|
493
|
+
import { usePraxisEngine } from '@plures/praxis/svelte';
|
|
494
|
+
import { createCartEngine } from './cart-engine';
|
|
495
|
+
|
|
496
|
+
const engine = createCartEngine();
|
|
497
|
+
const { context, dispatch } = usePraxisEngine(engine);
|
|
498
|
+
|
|
499
|
+
// Derive computed values
|
|
500
|
+
$: total = context.items.reduce((sum, item) => sum + item.price, 0);
|
|
501
|
+
$: itemCount = context.items.length;
|
|
502
|
+
$: isEmpty = itemCount === 0;
|
|
503
|
+
</script>
|
|
504
|
+
|
|
505
|
+
<div class="cart">
|
|
506
|
+
<h2>Shopping Cart</h2>
|
|
507
|
+
|
|
508
|
+
{#if isEmpty}
|
|
509
|
+
<p>Your cart is empty</p>
|
|
510
|
+
{:else}
|
|
511
|
+
<ul>
|
|
512
|
+
{#each context.items as item}
|
|
513
|
+
<li>{item.name} - ${item.price}</li>
|
|
514
|
+
{/each}
|
|
515
|
+
</ul>
|
|
516
|
+
|
|
517
|
+
<div class="summary">
|
|
518
|
+
<p>Items: {itemCount}</p>
|
|
519
|
+
<p>Total: ${total.toFixed(2)}</p>
|
|
520
|
+
</div>
|
|
521
|
+
{/if}
|
|
522
|
+
</div>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Side Effects
|
|
526
|
+
|
|
527
|
+
```svelte
|
|
528
|
+
<script lang="ts">
|
|
529
|
+
import { usePraxisEngine, usePraxisSubscription } from '@plures/praxis/svelte';
|
|
530
|
+
import { createOrderEngine } from './order-engine';
|
|
531
|
+
|
|
532
|
+
const engine = createOrderEngine();
|
|
533
|
+
const { context, dispatch } = usePraxisEngine(engine);
|
|
534
|
+
|
|
535
|
+
// Persist to localStorage
|
|
536
|
+
usePraxisSubscription(engine, (state) => {
|
|
537
|
+
localStorage.setItem('orders', JSON.stringify(state.context.orders));
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Send analytics
|
|
541
|
+
usePraxisSubscription(engine, (state) => {
|
|
542
|
+
if (state.facts.some(f => f.tag === 'OrderPlaced')) {
|
|
543
|
+
analytics.track('order_placed', {
|
|
544
|
+
orderId: state.context.lastOrderId,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Show notifications
|
|
550
|
+
usePraxisSubscription(engine, (state) => {
|
|
551
|
+
const errors = state.facts.filter(f => f.tag === 'OrderFailed');
|
|
552
|
+
if (errors.length > 0) {
|
|
553
|
+
const error = errors[errors.length - 1];
|
|
554
|
+
showNotification('Order Failed', error.payload.reason);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
</script>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Performance Tips
|
|
561
|
+
|
|
562
|
+
### 1. Use Derived Stores for Specific Values
|
|
563
|
+
|
|
564
|
+
```svelte
|
|
565
|
+
<!-- ❌ Less efficient: subscribes to all state changes -->
|
|
566
|
+
<script>
|
|
567
|
+
const store = createPraxisStore(engine);
|
|
568
|
+
</script>
|
|
569
|
+
<p>Count: {$store.context.count}</p>
|
|
570
|
+
|
|
571
|
+
<!-- ✅ More efficient: only updates when count changes -->
|
|
572
|
+
<script>
|
|
573
|
+
const count = createDerivedStore(engine, ctx => ctx.count);
|
|
574
|
+
</script>
|
|
575
|
+
<p>Count: {$count}</p>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### 2. Limit History Size
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
// For user-facing features
|
|
582
|
+
usePraxisEngine(engine, {
|
|
583
|
+
enableHistory: true,
|
|
584
|
+
maxHistorySize: 20 // Smaller for better performance
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// For debugging/development
|
|
588
|
+
usePraxisEngine(engine, {
|
|
589
|
+
enableHistory: true,
|
|
590
|
+
maxHistorySize: 100 // Larger for more history
|
|
591
|
+
});
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### 3. Batch Events
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// ❌ Multiple dispatches
|
|
598
|
+
dispatch([Increment.create({})]);
|
|
599
|
+
dispatch([UpdateUser.create({ name: 'Alice' })]);
|
|
600
|
+
dispatch([SaveData.create({})]);
|
|
601
|
+
|
|
602
|
+
// ✅ Single dispatch
|
|
603
|
+
dispatch([
|
|
604
|
+
Increment.create({}),
|
|
605
|
+
UpdateUser.create({ name: 'Alice' }),
|
|
606
|
+
SaveData.create({})
|
|
607
|
+
]);
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### 4. Memoize Selectors
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
// ❌ New function on every render
|
|
614
|
+
const count = usePraxisContext(engine, (ctx) => ctx.count);
|
|
615
|
+
|
|
616
|
+
// ✅ Reuse selector function
|
|
617
|
+
const countSelector = (ctx: MyContext) => ctx.count;
|
|
618
|
+
const count = usePraxisContext(engine, countSelector);
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## TypeScript Support
|
|
622
|
+
|
|
623
|
+
All APIs are fully typed:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
import type {
|
|
627
|
+
LogicEngine,
|
|
628
|
+
Readable,
|
|
629
|
+
Writable,
|
|
630
|
+
StateSnapshot,
|
|
631
|
+
HistoryEntry,
|
|
632
|
+
PraxisEngineBinding,
|
|
633
|
+
} from '@plures/praxis/svelte';
|
|
634
|
+
|
|
635
|
+
interface MyContext {
|
|
636
|
+
count: number;
|
|
637
|
+
user: { id: string; name: string } | null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const engine: LogicEngine<MyContext> = createPraxisEngine({ /* ... */ });
|
|
641
|
+
|
|
642
|
+
// Store types are inferred
|
|
643
|
+
const store: Readable<PraxisState & { context: MyContext }> & {
|
|
644
|
+
dispatch: (events: PraxisEvent[]) => void;
|
|
645
|
+
} = createPraxisStore(engine);
|
|
646
|
+
|
|
647
|
+
// Runes types are inferred
|
|
648
|
+
const binding: PraxisEngineBinding<MyContext> = usePraxisEngine(engine, {
|
|
649
|
+
enableHistory: true
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Selector types are inferred
|
|
653
|
+
const count: number = usePraxisContext(
|
|
654
|
+
engine,
|
|
655
|
+
(ctx: MyContext) => ctx.count
|
|
656
|
+
);
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
## Migration from XState
|
|
660
|
+
|
|
661
|
+
If you're coming from XState:
|
|
662
|
+
|
|
663
|
+
| XState | Praxis |
|
|
664
|
+
|--------|--------|
|
|
665
|
+
| `const [state, send] = useMachine(machine)` | `const { context, dispatch } = usePraxisEngine(engine)` |
|
|
666
|
+
| `state.context.count` | `context.count` |
|
|
667
|
+
| `send({ type: 'INCREMENT' })` | `dispatch([Increment.create({})])` |
|
|
668
|
+
| `state.matches('idle')` | Check context: `context.status === 'idle'` |
|
|
669
|
+
| `state.history` | `usePraxisEngine(engine, { enableHistory: true })` |
|
|
670
|
+
| `const service = interpret(machine)` | `const store = createPraxisStore(engine)` |
|
|
671
|
+
|
|
672
|
+
## Examples
|
|
673
|
+
|
|
674
|
+
See the following examples for complete implementations:
|
|
675
|
+
|
|
676
|
+
- [Counter with Svelte 5](/src/examples/svelte-counter/index.ts)
|
|
677
|
+
- [Auth Flow with History](/docs/guides/history-state-pattern.md#authentication-flow-example)
|
|
678
|
+
- [Svelte Integration Tests](/src/__tests__/svelte-integration.test.ts)
|
|
679
|
+
|
|
680
|
+
## Summary
|
|
681
|
+
|
|
682
|
+
Praxis provides comprehensive Svelte 5 support through:
|
|
683
|
+
|
|
684
|
+
- ✅ **Traditional Stores**: Compatible with Svelte 4 and 5
|
|
685
|
+
- ✅ **Modern Runes**: First-class Svelte 5 runes support
|
|
686
|
+
- ✅ **History Tracking**: Built-in undo/redo and time-travel
|
|
687
|
+
- ✅ **Type Safety**: Full TypeScript support throughout
|
|
688
|
+
- ✅ **Performance**: Efficient updates with derived stores
|
|
689
|
+
- ✅ **Developer Experience**: Simple, intuitive API
|
|
690
|
+
|
|
691
|
+
Whether you prefer stores or runes, Praxis has you covered!
|