@plures/praxis 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/FRAMEWORK.md +106 -15
  2. package/README.md +275 -53
  3. package/dist/browser/adapter-TM4IS5KT.js +12 -0
  4. package/dist/browser/chunk-JQ64KMLN.js +141 -0
  5. package/dist/browser/chunk-LE2ZJYFC.js +154 -0
  6. package/dist/browser/chunk-VOMLVI6V.js +197 -0
  7. package/dist/browser/engine-YJZV4SLD.js +8 -0
  8. package/dist/browser/index.d.ts +300 -11
  9. package/dist/browser/index.js +334 -325
  10. package/dist/browser/integrations/svelte.d.ts +3 -1
  11. package/dist/browser/integrations/svelte.js +8 -0
  12. package/dist/browser/{engine-BjdqxeXG.d.ts → reactive-engine.svelte-C9OpcTHf.d.ts} +87 -1
  13. package/dist/node/adapter-K6DOX6XS.js +13 -0
  14. package/dist/node/chunk-JQ64KMLN.js +141 -0
  15. package/dist/node/chunk-LE2ZJYFC.js +154 -0
  16. package/dist/node/chunk-S54337I5.js +446 -0
  17. package/dist/node/chunk-VOMLVI6V.js +197 -0
  18. package/dist/node/cli/index.cjs +1444 -889
  19. package/dist/node/cli/index.js +9 -0
  20. package/dist/node/components/index.d.cts +2 -2
  21. package/dist/node/components/index.d.ts +2 -2
  22. package/dist/node/docs-JFNYTOJA.js +102 -0
  23. package/dist/node/engine-2DQBKBJC.js +9 -0
  24. package/dist/node/index.cjs +747 -234
  25. package/dist/node/index.d.cts +237 -15
  26. package/dist/node/index.d.ts +237 -15
  27. package/dist/node/index.js +339 -767
  28. package/dist/node/integrations/svelte.cjs +357 -2
  29. package/dist/node/integrations/svelte.d.cts +3 -1
  30. package/dist/node/integrations/svelte.d.ts +3 -1
  31. package/dist/node/integrations/svelte.js +7 -0
  32. package/dist/node/{engine-CVJobhHm.d.cts → reactive-engine.svelte-1M4m_C_v.d.cts} +87 -1
  33. package/dist/node/{engine-1iqLe6_P.d.ts → reactive-engine.svelte-ChNFn4Hj.d.ts} +87 -1
  34. package/dist/node/{terminal-adapter-XLtCjjb_.d.cts → terminal-adapter-CDzxoLKR.d.cts} +68 -1
  35. package/dist/node/{terminal-adapter-07HGftGQ.d.ts → terminal-adapter-CWka-yL8.d.ts} +68 -1
  36. package/package.json +3 -2
  37. package/src/__tests__/reactive-engine.test.ts +516 -0
  38. package/src/cli/commands/docs.ts +147 -0
  39. package/src/cli/index.ts +21 -0
  40. package/src/core/pluresdb/README.md +156 -0
  41. package/src/core/pluresdb/adapter.ts +165 -0
  42. package/src/core/pluresdb/index.ts +3 -3
  43. package/src/core/reactive-engine.svelte.ts +93 -19
  44. package/src/core/reactive-engine.ts +284 -22
  45. package/src/index.browser.ts +16 -0
  46. package/src/index.ts +16 -0
  47. package/src/integrations/pluresdb.ts +2 -2
  48. package/src/integrations/svelte.ts +8 -0
  49. package/src/integrations/unified.ts +350 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Docs Command
3
+ *
4
+ * Generate documentation from Praxis schemas using State-Docs integration.
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { createStateDocsGenerator } from '../../integrations/state-docs.js';
10
+ import { loadSchemaFromFile } from '../../core/schema/loader.js';
11
+ import type { PraxisRegistry } from '../../core/rules.js';
12
+
13
+ /**
14
+ * Docs command options
15
+ */
16
+ export interface DocsOptions {
17
+ /** Output directory for generated docs */
18
+ output?: string;
19
+ /** Documentation title */
20
+ title?: string;
21
+ /** Include table of contents */
22
+ toc?: boolean;
23
+ /** Include timestamp */
24
+ timestamp?: boolean;
25
+ /** Visualization format */
26
+ format?: 'mermaid' | 'dot';
27
+ /** Custom header content */
28
+ header?: string;
29
+ /** Custom footer content */
30
+ footer?: string;
31
+ /** Generate from registry instead of schema */
32
+ fromRegistry?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Generate documentation from schema or registry
37
+ */
38
+ export async function docs(
39
+ schemaOrRegistryPath: string | undefined,
40
+ options: DocsOptions
41
+ ): Promise<void> {
42
+ console.log('\n╔═══════════════════════════════════════════════════╗');
43
+ console.log('║ Praxis Documentation Generator ║');
44
+ console.log('╚═══════════════════════════════════════════════════╝\n');
45
+
46
+ if (!schemaOrRegistryPath || !fs.existsSync(schemaOrRegistryPath)) {
47
+ console.error('Error: Schema or registry file required');
48
+ console.log('Usage: praxis docs <schema-file> [options]');
49
+ console.log('\nOptions:');
50
+ console.log(' --output <dir> Output directory (default: ./docs)');
51
+ console.log(' --title <title> Documentation title');
52
+ console.log(' --format <format> Diagram format: mermaid (default) or dot');
53
+ console.log(' --no-toc Disable table of contents');
54
+ console.log(' --no-timestamp Disable timestamp');
55
+ console.log(' --from-registry Generate from registry instead of schema');
56
+ process.exit(1);
57
+ }
58
+
59
+ const outputDir = options.output || './docs';
60
+ const title = options.title || 'Praxis Application';
61
+
62
+ // Create generator
63
+ const generator = createStateDocsGenerator({
64
+ projectTitle: title,
65
+ target: outputDir,
66
+ visualization: {
67
+ format: options.format || 'mermaid',
68
+ exportPng: false,
69
+ },
70
+ template: {
71
+ toc: options.toc !== false,
72
+ timestamp: options.timestamp !== false,
73
+ header: options.header,
74
+ footer: options.footer,
75
+ },
76
+ });
77
+
78
+ console.log(`Source: ${schemaOrRegistryPath}`);
79
+ console.log(`Output: ${outputDir}\n`);
80
+
81
+ try {
82
+ let generatedDocs;
83
+
84
+ if (options.fromRegistry) {
85
+ // Load registry module
86
+ console.log('Loading registry module...');
87
+ const module = await import(path.resolve(schemaOrRegistryPath));
88
+ const registry: PraxisRegistry<unknown> =
89
+ module.registry || module.default || module;
90
+
91
+ if (!registry || typeof registry.getAllRules !== 'function') {
92
+ console.error('Error: Invalid registry module');
93
+ console.log('Expected: export const registry = new PraxisRegistry()');
94
+ process.exit(1);
95
+ }
96
+
97
+ console.log('Generating documentation from registry...');
98
+ // Create module object from registry
99
+ const praxisModule = {
100
+ rules: registry.getAllRules(),
101
+ constraints: registry.getAllConstraints(),
102
+ };
103
+ generatedDocs = generator.generateFromModule(praxisModule);
104
+ } else {
105
+ // Load schema
106
+ console.log('Loading schema...');
107
+ const result = await loadSchemaFromFile(schemaOrRegistryPath);
108
+
109
+ if (result.errors.length > 0 || !result.schema) {
110
+ console.error(`Error loading schema: ${result.errors.join(', ')}`);
111
+ process.exit(1);
112
+ }
113
+
114
+ console.log('Generating documentation from schema...');
115
+ generatedDocs = generator.generateFromSchema(result.schema);
116
+ }
117
+
118
+ // Ensure output directory exists
119
+ if (!fs.existsSync(outputDir)) {
120
+ fs.mkdirSync(outputDir, { recursive: true });
121
+ }
122
+
123
+ // Write all generated docs
124
+ console.log('\nWriting documentation files:\n');
125
+ for (const doc of generatedDocs) {
126
+ const fullPath = path.resolve(doc.path);
127
+ const dir = path.dirname(fullPath);
128
+
129
+ // Ensure directory exists
130
+ if (!fs.existsSync(dir)) {
131
+ fs.mkdirSync(dir, { recursive: true });
132
+ }
133
+
134
+ fs.writeFileSync(fullPath, doc.content);
135
+ console.log(` ✓ ${doc.path} (${doc.type})`);
136
+ }
137
+
138
+ console.log(`\n✓ Generated ${generatedDocs.length} documentation file(s)`);
139
+ console.log(`\nView your documentation: ${path.resolve(outputDir, 'README.md')}`);
140
+ } catch (error) {
141
+ console.error(`Error generating documentation: ${error}`);
142
+ if (error instanceof Error) {
143
+ console.error(error.stack);
144
+ }
145
+ process.exit(1);
146
+ }
147
+ }
package/src/cli/index.ts CHANGED
@@ -90,6 +90,27 @@ program
90
90
  await generate(options);
91
91
  });
92
92
 
93
+ program
94
+ .command('docs [schema]')
95
+ .description('Generate documentation from schemas or registries')
96
+ .option('-o, --output <dir>', 'Output directory', './docs')
97
+ .option('--title <title>', 'Documentation title')
98
+ .option('--format <format>', 'Diagram format (mermaid, dot)', 'mermaid')
99
+ .option('--no-toc', 'Disable table of contents')
100
+ .option('--no-timestamp', 'Disable timestamp')
101
+ .option('--from-registry', 'Generate from registry instead of schema')
102
+ .option('--header <content>', 'Custom header content')
103
+ .option('--footer <content>', 'Custom footer content')
104
+ .action(async (schema, options) => {
105
+ try {
106
+ const { docs } = await import('./commands/docs.js');
107
+ await docs(schema, options);
108
+ } catch (error) {
109
+ console.error('Error generating documentation:', error);
110
+ process.exit(1);
111
+ }
112
+ });
113
+
93
114
  program
94
115
  .command('canvas [schema]')
95
116
  .description('Open CodeCanvas for visual editing')
@@ -0,0 +1,156 @@
1
+ # PluresDB Integration
2
+
3
+ This module provides integration between Praxis and PluresDB for local-first, reactive data storage.
4
+
5
+ ## Overview
6
+
7
+ Praxis supports PluresDB through a simple adapter interface (`PraxisDB`) that can be backed by:
8
+
9
+ 1. **In-memory storage** (for development/testing) - `InMemoryPraxisDB`
10
+ 2. **Official PluresDB** (for production) - `PluresDBPraxisAdapter`
11
+
12
+ ## Using the Official PluresDB
13
+
14
+ The official PluresDB package from NPM provides a complete local-first database with P2P sync, CRDT conflict resolution, and more.
15
+
16
+ ### Installation
17
+
18
+ PluresDB is included as a dependency in Praxis:
19
+
20
+ ```bash
21
+ npm install @plures/praxis
22
+ # PluresDB is automatically installed as a dependency
23
+ ```
24
+
25
+ ### Basic Usage
26
+
27
+ ```typescript
28
+ import { PluresNode } from 'pluresdb';
29
+ import { createPluresDB, createPraxisDBStore } from '@plures/praxis';
30
+ import { PraxisRegistry } from '@plures/praxis';
31
+
32
+ // Initialize PluresDB
33
+ const pluresdb = new PluresNode({
34
+ config: {
35
+ port: 34567,
36
+ host: 'localhost',
37
+ dataDir: './data',
38
+ },
39
+ autoStart: true,
40
+ });
41
+
42
+ // Wrap it with the Praxis adapter
43
+ const db = createPluresDB(pluresdb);
44
+
45
+ // Use with Praxis store
46
+ const registry = new PraxisRegistry();
47
+ const store = createPraxisDBStore(db, registry);
48
+
49
+ // Now you can use the store with Praxis engine
50
+ ```
51
+
52
+ ### Using with In-Memory Storage (Development)
53
+
54
+ For testing and development, use the in-memory implementation:
55
+
56
+ ```typescript
57
+ import { createInMemoryDB, createPraxisDBStore } from '@plures/praxis';
58
+ import { PraxisRegistry } from '@plures/praxis';
59
+
60
+ // Create in-memory database
61
+ const db = createInMemoryDB();
62
+
63
+ // Use with Praxis store
64
+ const registry = new PraxisRegistry();
65
+ const store = createPraxisDBStore(db, registry);
66
+ ```
67
+
68
+ ## API Reference
69
+
70
+ ### PraxisDB Interface
71
+
72
+ All database adapters implement this interface:
73
+
74
+ ```typescript
75
+ interface PraxisDB {
76
+ // Get a value by key
77
+ get<T>(key: string): Promise<T | undefined>;
78
+
79
+ // Set a value by key
80
+ set<T>(key: string, value: T): Promise<void>;
81
+
82
+ // Watch a key for changes
83
+ watch<T>(key: string, callback: (val: T) => void): UnsubscribeFn;
84
+ }
85
+ ```
86
+
87
+ ### createPluresDB(db)
88
+
89
+ Creates a Praxis adapter for the official PluresDB package.
90
+
91
+ **Parameters:**
92
+ - `db`: PluresDB instance (PluresNode or SQLiteCompatibleAPI)
93
+
94
+ **Returns:** `PluresDBPraxisAdapter` instance
95
+
96
+ ### createInMemoryDB()
97
+
98
+ Creates an in-memory database for testing/development.
99
+
100
+ **Returns:** `InMemoryPraxisDB` instance
101
+
102
+ ## Features
103
+
104
+ ### Event Sourcing
105
+
106
+ Store facts and events in PluresDB:
107
+
108
+ ```typescript
109
+ import { createPraxisEngine, createPluresDBAdapter } from '@plures/praxis';
110
+
111
+ const adapter = createPluresDBAdapter({ db, registry });
112
+ const engine = createPraxisEngine({ initialContext: {}, registry });
113
+ adapter.attachEngine(engine);
114
+
115
+ // Events are now persisted to PluresDB
116
+ ```
117
+
118
+ ### Reactive Queries
119
+
120
+ Watch for changes in the database:
121
+
122
+ ```typescript
123
+ const unsubscribe = db.watch('user:123', (user) => {
124
+ console.log('User updated:', user);
125
+ });
126
+
127
+ // Later: unsubscribe
128
+ unsubscribe();
129
+ ```
130
+
131
+ ## PluresDB Features
132
+
133
+ The official PluresDB package provides:
134
+
135
+ - **P2P Graph Database**: Distributed, peer-to-peer data storage
136
+ - **SQLite Compatibility**: 95% SQLite API compatibility
137
+ - **CRDT Conflict Resolution**: Automatic conflict resolution
138
+ - **Vector Search**: Built-in vector embeddings and similarity search
139
+ - **Local-First**: Offline-first data storage with sync when online
140
+ - **Cross-Device Sync**: Automatic synchronization across devices
141
+
142
+ For more information, see the [PluresDB documentation](https://github.com/plures/pluresdb).
143
+
144
+ ## Migration
145
+
146
+ If you're using the in-memory implementation and want to migrate to PluresDB:
147
+
148
+ 1. Install PluresDB (already included as dependency)
149
+ 2. Replace `createInMemoryDB()` with `createPluresDB(pluresdb)`
150
+ 3. The API remains the same - no other code changes needed!
151
+
152
+ ## Notes
153
+
154
+ - The `PluresDBPraxisAdapter` uses polling to watch for changes (since PluresDB doesn't natively support reactive queries in the current API)
155
+ - For production use, consider the polling interval and adjust if needed
156
+ - The adapter automatically handles cleanup of polling intervals when watchers are removed
@@ -115,3 +115,168 @@ export class InMemoryPraxisDB implements PraxisDB {
115
115
  export function createInMemoryDB(): InMemoryPraxisDB {
116
116
  return new InMemoryPraxisDB();
117
117
  }
118
+
119
+ /**
120
+ * PluresDB instance type - represents either PluresNode or SQLiteCompatibleAPI
121
+ */
122
+ export type PluresDBInstance = {
123
+ get(key: string): Promise<any>;
124
+ put(key: string, value: any): Promise<void>;
125
+ };
126
+
127
+ /**
128
+ * Configuration options for PluresDBPraxisAdapter
129
+ */
130
+ export interface PluresDBAdapterConfig {
131
+ /** PluresDB instance */
132
+ db: PluresDBInstance;
133
+ /** Polling interval in milliseconds for watch functionality (default: 1000ms) */
134
+ pollInterval?: number;
135
+ }
136
+
137
+ /**
138
+ * PluresDB-backed implementation of PraxisDB
139
+ *
140
+ * Wraps the official PluresDB package from NPM to provide
141
+ * the PraxisDB interface for production use.
142
+ */
143
+ export class PluresDBPraxisAdapter implements PraxisDB {
144
+ private db: PluresDBInstance;
145
+ private watchers = new Map<string, Set<(val: unknown) => void>>();
146
+ private pollIntervals = new Map<string, ReturnType<typeof setInterval>>();
147
+ private lastValues = new Map<string, unknown>();
148
+ private pollInterval: number;
149
+
150
+ constructor(config: PluresDBAdapterConfig | PluresDBInstance) {
151
+ // Support both old API (direct db instance) and new config API
152
+ if ('get' in config && 'put' in config) {
153
+ this.db = config;
154
+ this.pollInterval = 1000;
155
+ } else {
156
+ this.db = config.db;
157
+ this.pollInterval = config.pollInterval ?? 1000;
158
+ }
159
+ }
160
+
161
+ async get<T>(key: string): Promise<T | undefined> {
162
+ try {
163
+ const value = await this.db.get(key);
164
+ return value as T | undefined;
165
+ } catch (error) {
166
+ // PluresDB returns undefined/null for missing keys
167
+ return undefined;
168
+ }
169
+ }
170
+
171
+ async set<T>(key: string, value: T): Promise<void> {
172
+ await this.db.put(key, value);
173
+
174
+ // Update last known value
175
+ this.lastValues.set(key, value);
176
+
177
+ // Notify watchers
178
+ const keyWatchers = this.watchers.get(key);
179
+ if (keyWatchers) {
180
+ for (const callback of keyWatchers) {
181
+ callback(value);
182
+ }
183
+ }
184
+ }
185
+
186
+ watch<T>(key: string, callback: (val: T) => void): UnsubscribeFn {
187
+ if (!this.watchers.has(key)) {
188
+ this.watchers.set(key, new Set());
189
+ }
190
+
191
+ const watchers = this.watchers.get(key)!;
192
+ const wrappedCallback = (val: unknown) => callback(val as T);
193
+ watchers.add(wrappedCallback);
194
+
195
+ // Set up polling for this key if not already set up
196
+ if (!this.pollIntervals.has(key)) {
197
+ const interval = setInterval(async () => {
198
+ try {
199
+ const value = await this.db.get(key);
200
+ const lastValue = this.lastValues.get(key);
201
+
202
+ // Only notify if value has actually changed
203
+ if (JSON.stringify(value) !== JSON.stringify(lastValue)) {
204
+ this.lastValues.set(key, value);
205
+ const currentWatchers = this.watchers.get(key);
206
+ if (currentWatchers) {
207
+ for (const cb of currentWatchers) {
208
+ cb(value);
209
+ }
210
+ }
211
+ }
212
+ } catch (error) {
213
+ // Ignore errors in polling
214
+ }
215
+ }, this.pollInterval);
216
+
217
+ this.pollIntervals.set(key, interval);
218
+ }
219
+
220
+ // Return unsubscribe function
221
+ return () => {
222
+ watchers.delete(wrappedCallback);
223
+ if (watchers.size === 0) {
224
+ this.watchers.delete(key);
225
+ // Clean up polling interval
226
+ const interval = this.pollIntervals.get(key);
227
+ if (interval) {
228
+ clearInterval(interval);
229
+ this.pollIntervals.delete(key);
230
+ }
231
+ // Clean up last value cache
232
+ this.lastValues.delete(key);
233
+ }
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Clean up all resources
239
+ */
240
+ dispose(): void {
241
+ // Clear all polling intervals
242
+ for (const interval of this.pollIntervals.values()) {
243
+ clearInterval(interval);
244
+ }
245
+ this.pollIntervals.clear();
246
+ this.watchers.clear();
247
+ this.lastValues.clear();
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Create a PluresDB-backed PraxisDB instance
253
+ *
254
+ * Wraps the official PluresDB package from NPM.
255
+ *
256
+ * @param config PluresDB instance or configuration object
257
+ * @returns PluresDBPraxisAdapter instance
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * import { PluresNode } from 'pluresdb';
262
+ * import { createPluresDB } from '@plures/praxis';
263
+ *
264
+ * const pluresdb = new PluresNode({ autoStart: true });
265
+ * const db = createPluresDB(pluresdb);
266
+ *
267
+ * await db.set('user:1', { name: 'Alice' });
268
+ * const user = await db.get('user:1');
269
+ * ```
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * // With custom polling interval
274
+ * const db = createPluresDB({
275
+ * db: pluresdb,
276
+ * pollInterval: 500, // Poll every 500ms
277
+ * });
278
+ * ```
279
+ */
280
+ export function createPluresDB(config: PluresDBAdapterConfig | PluresDBInstance): PluresDBPraxisAdapter {
281
+ return new PluresDBPraxisAdapter(config);
282
+ }
@@ -5,9 +5,9 @@
5
5
  * It provides the core adapter layer, store, and schema registry.
6
6
  */
7
7
 
8
- // Adapter - Core interface and in-memory implementation
9
- export type { PraxisDB, UnsubscribeFn } from './adapter.js';
10
- export { InMemoryPraxisDB, createInMemoryDB } from './adapter.js';
8
+ // Adapter - Core interface and implementations (in-memory + PluresDB)
9
+ export type { PraxisDB, UnsubscribeFn, PluresDBInstance, PluresDBAdapterConfig } from './adapter.js';
10
+ export { InMemoryPraxisDB, createInMemoryDB, PluresDBPraxisAdapter, createPluresDB } from './adapter.js';
11
11
 
12
12
  // Store - Manages facts, events, and reactive updates
13
13
  export type { EventStreamEntry, PraxisDBStoreOptions, RuleErrorHandler } from './store.js';
@@ -1,39 +1,65 @@
1
1
  /**
2
- * Praxis Reactive Logic Engine
2
+ * Praxis Reactive Logic Engine - Svelte 5 Implementation
3
3
  *
4
- * A Svelte 5 native implementation of the Praxis Logic Engine.
5
- * Uses Runes ($state, $derived, $effect) for fine-grained reactivity.
4
+ * This version uses Svelte 5 runes ($state) for built-in reactivity.
5
+ * The state object is automatically reactive when used in Svelte components.
6
6
  */
7
7
 
8
+ import { PraxisRegistry } from '../core/rules.js';
9
+ import type { PraxisEvent } from '../core/protocol.js';
10
+ import { LogicEngine, createPraxisEngine } from '../core/engine.js';
11
+
12
+ // Type declaration for Svelte 5 $state rune
13
+ // This is needed for TypeScript compilation; the actual implementation
14
+ // is provided by the Svelte compiler when processing .svelte.ts files
15
+ declare function $state<T>(initial: T): T;
16
+
8
17
  export interface ReactiveEngineOptions<TContext> {
9
18
  initialContext: TContext;
10
19
  initialFacts?: any[];
11
20
  initialMeta?: Record<string, unknown>;
21
+ registry?: PraxisRegistry<TContext>;
12
22
  }
13
23
 
24
+ /**
25
+ * Reactive Logic Engine using Svelte 5 runes.
26
+ * Combines the standard LogicEngine with reactive state management.
27
+ */
14
28
  export class ReactiveLogicEngine<TContext extends object> {
15
- // The single source of truth, reactive by default
16
- // We use $state.raw for things that shouldn't be deeply reactive if needed,
17
- // but for context we usually want deep reactivity.
18
- state: { context: TContext; facts: any[]; meta: Record<string, unknown> } = $state<{
19
- context: TContext;
20
- facts: any[];
21
- meta: Record<string, unknown>;
22
- }>({
29
+ // Use Svelte's $state rune for automatic reactivity
30
+ state: { context: TContext; facts: any[]; meta: Record<string, unknown> } = $state({
23
31
  context: {} as TContext,
24
- facts: [],
25
- meta: {}
32
+ facts: [] as any[],
33
+ meta: {} as Record<string, unknown>
26
34
  });
27
35
 
36
+ // Internal engine for logic processing
37
+ private _engine: LogicEngine<TContext>;
38
+
28
39
  constructor(options: ReactiveEngineOptions<TContext>) {
40
+ // Initialize reactive state
29
41
  this.state.context = options.initialContext;
30
42
  this.state.facts = options.initialFacts ?? [];
31
43
  this.state.meta = options.initialMeta ?? {};
44
+
45
+ // Create internal engine if registry is provided
46
+ if (options.registry) {
47
+ this._engine = createPraxisEngine({
48
+ initialContext: options.initialContext,
49
+ registry: options.registry,
50
+ });
51
+ } else {
52
+ // Create a basic engine without registry
53
+ this._engine = createPraxisEngine({
54
+ initialContext: options.initialContext,
55
+ registry: new PraxisRegistry<TContext>(),
56
+ });
57
+ }
32
58
  }
33
59
 
34
60
  /**
35
- * Access the reactive context directly.
36
- * Consumers can use this in $derived() or $effect().
61
+ * Access the reactive context.
62
+ * In Svelte 5 components, changes to this object will automatically trigger updates.
37
63
  */
38
64
  get context(): TContext {
39
65
  return this.state.context;
@@ -46,9 +72,16 @@ export class ReactiveLogicEngine<TContext extends object> {
46
72
  return this.state.facts;
47
73
  }
48
74
 
75
+ /**
76
+ * Access the reactive metadata.
77
+ */
78
+ get meta(): Record<string, unknown> {
79
+ return this.state.meta;
80
+ }
81
+
49
82
  /**
50
83
  * Apply a mutation to the state.
51
- * This is the "Action" or "Rule" equivalent.
84
+ * Changes will automatically trigger Svelte reactivity.
52
85
  *
53
86
  * @param mutator A function that receives the state and modifies it.
54
87
  */
@@ -57,9 +90,50 @@ export class ReactiveLogicEngine<TContext extends object> {
57
90
  }
58
91
 
59
92
  /**
60
- * Access the reactive meta.
93
+ * Process events through the logic engine and update reactive state.
94
+ *
95
+ * @param events Events to process
61
96
  */
62
- get meta(): Record<string, unknown> {
63
- return this.state.meta;
97
+ step(events: PraxisEvent[]): void {
98
+ const result = this._engine.step(events);
99
+
100
+ // Update reactive state with engine results
101
+ this.state.context = result.state.context as TContext;
102
+ this.state.facts = result.state.facts;
103
+ this.state.meta = result.state.meta ?? {};
64
104
  }
65
105
  }
106
+
107
+ /**
108
+ * Create a reactive logic engine with Svelte 5 runes.
109
+ *
110
+ * @param options Configuration options
111
+ * @returns A reactive logic engine instance
112
+ *
113
+ * @example
114
+ * ```svelte
115
+ * <script lang="ts">
116
+ * import { createReactiveEngine } from '@plures/praxis/svelte';
117
+ *
118
+ * const engine = createReactiveEngine({
119
+ * initialContext: { count: 0 },
120
+ * registry
121
+ * });
122
+ *
123
+ * // Use $derived for computed values
124
+ * const count = $derived(engine.context.count);
125
+ * const doubled = $derived(engine.context.count * 2);
126
+ *
127
+ * function increment() {
128
+ * engine.step([Increment.create({ amount: 1 })]);
129
+ * }
130
+ * </script>
131
+ *
132
+ * <button on:click={increment}>Count: {count}, Doubled: {doubled}</button>
133
+ * ```
134
+ */
135
+ export function createReactiveEngine<TContext extends object>(
136
+ options: ReactiveEngineOptions<TContext>
137
+ ): ReactiveLogicEngine<TContext> {
138
+ return new ReactiveLogicEngine(options);
139
+ }