@plures/praxis 1.1.1 → 1.1.3

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 (33) hide show
  1. package/README.md +68 -7
  2. package/dist/browser/chunk-R45WXWKH.js +345 -0
  3. package/dist/browser/index.d.ts +171 -11
  4. package/dist/browser/index.js +279 -277
  5. package/dist/browser/integrations/svelte.d.ts +3 -1
  6. package/dist/browser/integrations/svelte.js +7 -0
  7. package/dist/browser/{engine-BjdqxeXG.d.ts → reactive-engine.svelte-C9OpcTHf.d.ts} +87 -1
  8. package/dist/node/chunk-R45WXWKH.js +345 -0
  9. package/dist/node/components/index.d.cts +2 -2
  10. package/dist/node/components/index.d.ts +2 -2
  11. package/dist/node/index.cjs +343 -8
  12. package/dist/node/index.d.cts +108 -15
  13. package/dist/node/index.d.ts +108 -15
  14. package/dist/node/index.js +279 -278
  15. package/dist/node/integrations/svelte.cjs +357 -2
  16. package/dist/node/integrations/svelte.d.cts +3 -1
  17. package/dist/node/integrations/svelte.d.ts +3 -1
  18. package/dist/node/integrations/svelte.js +6 -0
  19. package/dist/node/{engine-CVJobhHm.d.cts → reactive-engine.svelte-1M4m_C_v.d.cts} +87 -1
  20. package/dist/node/{engine-1iqLe6_P.d.ts → reactive-engine.svelte-ChNFn4Hj.d.ts} +87 -1
  21. package/dist/node/{terminal-adapter-XLtCjjb_.d.cts → terminal-adapter-CDzxoLKR.d.cts} +68 -1
  22. package/dist/node/{terminal-adapter-07HGftGQ.d.ts → terminal-adapter-CWka-yL8.d.ts} +68 -1
  23. package/package.json +3 -2
  24. package/src/__tests__/reactive-engine.test.ts +516 -0
  25. package/src/core/pluresdb/README.md +156 -0
  26. package/src/core/pluresdb/adapter.ts +165 -0
  27. package/src/core/pluresdb/index.ts +3 -3
  28. package/src/core/reactive-engine.svelte.ts +88 -19
  29. package/src/core/reactive-engine.ts +283 -30
  30. package/src/index.browser.ts +12 -0
  31. package/src/index.ts +12 -0
  32. package/src/integrations/pluresdb.ts +2 -2
  33. package/src/integrations/svelte.ts +8 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  dotnet build
2
2
  dotnet test
3
- # Praxis 1.1.0
3
+ # Praxis
4
4
 
5
5
  **Typed, visual-first application logic for Svelte, Node, and the browser.**
6
6
 
@@ -10,19 +10,20 @@ dotnet test
10
10
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org/)
11
11
  [![Deno Compatible](https://img.shields.io/badge/deno-compatible-brightgreen)](https://deno.land/)
12
12
 
13
- Praxis is a schema-driven, rule-based engine with first-class Svelte 5 integration, component generation, and optional cloud sync. Version **1.1.0** delivers a unified ESM/CJS build, curated subpath exports, Svelte runes support, and a slimmer, publish-ready package for npm and JSR.
13
+ Praxis is a schema-driven, rule-based engine with first-class Svelte 5 integration, component generation, and optional cloud sync. The library delivers a unified ESM/CJS build, curated subpath exports, Svelte runes support, and a slimmer, publish-ready package for npm and JSR.
14
14
 
15
15
  ---
16
16
 
17
- ## What’s new in 1.1.0
17
+ ## What’s new
18
18
  - **Unified builds & exports**: `./`, `./svelte`, `./schema`, `./component`, `./cloud`, `./components`, and CLI all ship with ESM, CJS, and type definitions.
19
19
  - **Svelte 5 runes native**: Runes-friendly stores and helpers; server+client builds for integrations.
20
+ - **Framework-agnostic reactivity**: Proxy-based reactive engine for use without Svelte, enabling reactive state management in Node.js, browsers, and any JavaScript environment.
20
21
  - **Logic engine refinements**: Typed registry, step diagnostics, and trace-friendly rule execution.
21
22
  - **Cloud relay & local-first**: Polished cloud connector alongside PluresDB-first workflows.
22
23
  - **Publish-ready**: npm public access + JSR exports aligned to source.
23
24
 
24
25
  ## Capabilities at a glance
25
- - **Logic Engine**: Facts, events, rules, constraints, registry, introspection, and reactive engine variant.
26
+ - **Logic Engine**: Facts, events, rules, constraints, registry, introspection, and reactive engine variants (Svelte 5 + framework-agnostic).
26
27
  - **Schema & Codegen**: PSF-style schema types plus component generator for Svelte UIs.
27
28
  - **Svelte Integration**: Typed helpers, runes-ready builds, and Svelte component typings.
28
29
  - **Local-First Data**: PluresDB integrations for offline-first, reactive state.
@@ -45,7 +46,7 @@ JSR (Deno):
45
46
  const result = engine.step([Login.create({ username: 'alice' })]);
46
47
  # or via import map pointing to npm:
47
48
  # {
48
- # "imports": { "@plures/praxis": "npm:@plures/praxis@^1.1.0" }
49
+ # "imports": { "@plures/praxis": "npm:@plures/praxis@^1.1.2" }
49
50
  # }
50
51
  ```
51
52
 
@@ -100,16 +101,47 @@ engine.step([Login.create({ username: 'alex' })]);
100
101
  registry.registerRule(counterRule);
101
102
 
102
103
  const engine = createReactiveEngine({ initialContext: { count: 0 }, registry });
103
- const count = engine.$derived((s) => s.context.count);
104
+
105
+ // Use Svelte's $derived with the reactive engine state
106
+ const count = $derived(engine.context.count);
104
107
 
105
108
  function addOne() {
106
109
  engine.step([Increment.create({ amount: 1 })]);
107
110
  }
108
111
  </script>
109
112
 
110
- <button on:click={addOne}>Count is {$count}</button>
113
+ <button on:click={addOne}>Count is {count}</button>
111
114
  ```
112
115
 
116
+ ## Framework-agnostic reactive engine
117
+ For non-Svelte environments, use the framework-agnostic reactive engine with Proxy-based reactivity:
118
+
119
+ ```typescript
120
+ import { createFrameworkAgnosticReactiveEngine } from '@plures/praxis';
121
+
122
+ const engine = createFrameworkAgnosticReactiveEngine({
123
+ initialContext: { count: 0 },
124
+ });
125
+
126
+ // Subscribe to state changes
127
+ engine.subscribe((state) => {
128
+ console.log('Count:', state.context.count);
129
+ });
130
+
131
+ // Create derived/computed values
132
+ const doubled = engine.$derived((state) => state.context.count * 2);
133
+ doubled.subscribe((value) => {
134
+ console.log('Doubled:', value);
135
+ });
136
+
137
+ // Apply mutations (batched for performance)
138
+ engine.apply((state) => {
139
+ state.context.count += 1;
140
+ });
141
+ ```
142
+
143
+ See the [reactive counter example](./examples/reactive-counter/README.md) for a complete demonstration.
144
+
113
145
  ## Cloud relay (optional)
114
146
  ```ts
115
147
  import { connectRelay } from '@plures/praxis/cloud';
@@ -129,6 +161,35 @@ await relay.sync({
129
161
  });
130
162
  ```
131
163
 
164
+ ## PluresDB integration
165
+ ```ts
166
+ import { PluresNode } from 'pluresdb';
167
+ import { createPluresDB, createPraxisDBStore } from '@plures/praxis';
168
+ import { PraxisRegistry } from '@plures/praxis';
169
+
170
+ // Initialize the official PluresDB from npm
171
+ const pluresdb = new PluresNode({
172
+ config: {
173
+ port: 34567,
174
+ dataDir: './data',
175
+ },
176
+ autoStart: true,
177
+ });
178
+
179
+ // Wrap it with the Praxis adapter
180
+ const db = createPluresDB(pluresdb);
181
+
182
+ // Use with Praxis store for local-first reactive data
183
+ const registry = new PraxisRegistry();
184
+ const store = createPraxisDBStore(db, registry);
185
+
186
+ // Or use in-memory database for development/testing
187
+ import { createInMemoryDB } from '@plures/praxis';
188
+ const devDb = createInMemoryDB();
189
+ ```
190
+
191
+ > **Note:** Praxis now uses the official [PluresDB package from NPM](https://www.npmjs.com/package/pluresdb), which provides P2P sync, CRDT conflict resolution, SQLite compatibility, and more. The `createPluresDB()` function wraps PluresDB to provide the `PraxisDB` interface used by Praxis.
192
+
132
193
  ## CLI (npx-friendly)
133
194
  ```bash
134
195
  npx praxis --help
@@ -0,0 +1,345 @@
1
+ // src/core/protocol.ts
2
+ var PRAXIS_PROTOCOL_VERSION = "1.0.0";
3
+
4
+ // src/core/rules.ts
5
+ var PraxisRegistry = class {
6
+ rules = /* @__PURE__ */ new Map();
7
+ constraints = /* @__PURE__ */ new Map();
8
+ /**
9
+ * Register a rule
10
+ */
11
+ registerRule(descriptor) {
12
+ if (this.rules.has(descriptor.id)) {
13
+ throw new Error(`Rule with id "${descriptor.id}" already registered`);
14
+ }
15
+ this.rules.set(descriptor.id, descriptor);
16
+ }
17
+ /**
18
+ * Register a constraint
19
+ */
20
+ registerConstraint(descriptor) {
21
+ if (this.constraints.has(descriptor.id)) {
22
+ throw new Error(`Constraint with id "${descriptor.id}" already registered`);
23
+ }
24
+ this.constraints.set(descriptor.id, descriptor);
25
+ }
26
+ /**
27
+ * Register a module (all its rules and constraints)
28
+ */
29
+ registerModule(module) {
30
+ for (const rule of module.rules) {
31
+ this.registerRule(rule);
32
+ }
33
+ for (const constraint of module.constraints) {
34
+ this.registerConstraint(constraint);
35
+ }
36
+ }
37
+ /**
38
+ * Get a rule by ID
39
+ */
40
+ getRule(id) {
41
+ return this.rules.get(id);
42
+ }
43
+ /**
44
+ * Get a constraint by ID
45
+ */
46
+ getConstraint(id) {
47
+ return this.constraints.get(id);
48
+ }
49
+ /**
50
+ * Get all registered rule IDs
51
+ */
52
+ getRuleIds() {
53
+ return Array.from(this.rules.keys());
54
+ }
55
+ /**
56
+ * Get all registered constraint IDs
57
+ */
58
+ getConstraintIds() {
59
+ return Array.from(this.constraints.keys());
60
+ }
61
+ /**
62
+ * Get all rules
63
+ */
64
+ getAllRules() {
65
+ return Array.from(this.rules.values());
66
+ }
67
+ /**
68
+ * Get all constraints
69
+ */
70
+ getAllConstraints() {
71
+ return Array.from(this.constraints.values());
72
+ }
73
+ };
74
+
75
+ // src/core/engine.ts
76
+ function safeClone(value) {
77
+ if (value === null || typeof value !== "object") {
78
+ return value;
79
+ }
80
+ if (typeof globalThis.structuredClone === "function") {
81
+ try {
82
+ return globalThis.structuredClone(value);
83
+ } catch {
84
+ }
85
+ }
86
+ if (Array.isArray(value)) {
87
+ return [...value];
88
+ }
89
+ return { ...value };
90
+ }
91
+ var LogicEngine = class {
92
+ state;
93
+ registry;
94
+ constructor(options) {
95
+ this.registry = options.registry;
96
+ this.state = {
97
+ context: options.initialContext,
98
+ facts: options.initialFacts ?? [],
99
+ meta: options.initialMeta ?? {},
100
+ protocolVersion: PRAXIS_PROTOCOL_VERSION
101
+ };
102
+ }
103
+ /**
104
+ * Get the current state (immutable copy)
105
+ */
106
+ getState() {
107
+ return {
108
+ context: safeClone(this.state.context),
109
+ facts: [...this.state.facts],
110
+ meta: this.state.meta ? safeClone(this.state.meta) : void 0,
111
+ protocolVersion: this.state.protocolVersion
112
+ };
113
+ }
114
+ /**
115
+ * Get the current context
116
+ */
117
+ getContext() {
118
+ return safeClone(this.state.context);
119
+ }
120
+ /**
121
+ * Get current facts
122
+ */
123
+ getFacts() {
124
+ return [...this.state.facts];
125
+ }
126
+ /**
127
+ * Process events through the engine.
128
+ * Applies all registered rules and checks all registered constraints.
129
+ *
130
+ * @param events Events to process
131
+ * @returns Result with new state and diagnostics
132
+ */
133
+ step(events) {
134
+ const config = {
135
+ ruleIds: this.registry.getRuleIds(),
136
+ constraintIds: this.registry.getConstraintIds()
137
+ };
138
+ return this.stepWithConfig(events, config);
139
+ }
140
+ /**
141
+ * Process events with specific rule and constraint configuration.
142
+ *
143
+ * @param events Events to process
144
+ * @param config Step configuration
145
+ * @returns Result with new state and diagnostics
146
+ */
147
+ stepWithConfig(events, config) {
148
+ const diagnostics = [];
149
+ let newState = { ...this.state };
150
+ const newFacts = [];
151
+ for (const ruleId of config.ruleIds) {
152
+ const rule = this.registry.getRule(ruleId);
153
+ if (!rule) {
154
+ diagnostics.push({
155
+ kind: "rule-error",
156
+ message: `Rule "${ruleId}" not found in registry`,
157
+ data: { ruleId }
158
+ });
159
+ continue;
160
+ }
161
+ try {
162
+ const ruleFacts = rule.impl(newState, events);
163
+ newFacts.push(...ruleFacts);
164
+ } catch (error) {
165
+ diagnostics.push({
166
+ kind: "rule-error",
167
+ message: `Error executing rule "${ruleId}": ${error instanceof Error ? error.message : String(error)}`,
168
+ data: { ruleId, error }
169
+ });
170
+ }
171
+ }
172
+ newState = {
173
+ ...newState,
174
+ facts: [...newState.facts, ...newFacts]
175
+ };
176
+ for (const constraintId of config.constraintIds) {
177
+ const constraint = this.registry.getConstraint(constraintId);
178
+ if (!constraint) {
179
+ diagnostics.push({
180
+ kind: "constraint-violation",
181
+ message: `Constraint "${constraintId}" not found in registry`,
182
+ data: { constraintId }
183
+ });
184
+ continue;
185
+ }
186
+ try {
187
+ const result = constraint.impl(newState);
188
+ if (result === false) {
189
+ diagnostics.push({
190
+ kind: "constraint-violation",
191
+ message: `Constraint "${constraintId}" violated`,
192
+ data: { constraintId, description: constraint.description }
193
+ });
194
+ } else if (typeof result === "string") {
195
+ diagnostics.push({
196
+ kind: "constraint-violation",
197
+ message: result,
198
+ data: { constraintId, description: constraint.description }
199
+ });
200
+ }
201
+ } catch (error) {
202
+ diagnostics.push({
203
+ kind: "constraint-violation",
204
+ message: `Error checking constraint "${constraintId}": ${error instanceof Error ? error.message : String(error)}`,
205
+ data: { constraintId, error }
206
+ });
207
+ }
208
+ }
209
+ this.state = newState;
210
+ return {
211
+ state: newState,
212
+ diagnostics
213
+ };
214
+ }
215
+ /**
216
+ * Update the context directly (for exceptional cases).
217
+ * Generally, context should be updated through rules.
218
+ *
219
+ * @param updater Function that produces new context from old context
220
+ */
221
+ updateContext(updater) {
222
+ this.state = {
223
+ ...this.state,
224
+ context: updater(this.state.context)
225
+ };
226
+ }
227
+ /**
228
+ * Add facts directly (for exceptional cases).
229
+ * Generally, facts should be added through rules.
230
+ *
231
+ * @param facts Facts to add
232
+ */
233
+ addFacts(facts) {
234
+ this.state = {
235
+ ...this.state,
236
+ facts: [...this.state.facts, ...facts]
237
+ };
238
+ }
239
+ /**
240
+ * Clear all facts
241
+ */
242
+ clearFacts() {
243
+ this.state = {
244
+ ...this.state,
245
+ facts: []
246
+ };
247
+ }
248
+ /**
249
+ * Reset the engine to initial state
250
+ */
251
+ reset(options) {
252
+ this.state = {
253
+ context: options.initialContext,
254
+ facts: options.initialFacts ?? [],
255
+ meta: options.initialMeta ?? {},
256
+ protocolVersion: PRAXIS_PROTOCOL_VERSION
257
+ };
258
+ }
259
+ };
260
+ function createPraxisEngine(options) {
261
+ return new LogicEngine(options);
262
+ }
263
+
264
+ // src/core/reactive-engine.svelte.ts
265
+ import * as $ from "svelte/internal/client";
266
+ var ReactiveLogicEngine = class {
267
+ #state = (
268
+ // Use Svelte's $state rune for automatic reactivity
269
+ $.state($.proxy({ context: {}, facts: [], meta: {} }))
270
+ );
271
+ get state() {
272
+ return $.get(this.#state);
273
+ }
274
+ set state(value) {
275
+ $.set(this.#state, value, true);
276
+ }
277
+ _engine;
278
+ constructor(options) {
279
+ this.state.context = options.initialContext;
280
+ this.state.facts = options.initialFacts ?? [];
281
+ this.state.meta = options.initialMeta ?? {};
282
+ if (options.registry) {
283
+ this._engine = createPraxisEngine({
284
+ initialContext: options.initialContext,
285
+ registry: options.registry
286
+ });
287
+ } else {
288
+ this._engine = createPraxisEngine({
289
+ initialContext: options.initialContext,
290
+ registry: new PraxisRegistry()
291
+ });
292
+ }
293
+ }
294
+ /**
295
+ * Access the reactive context.
296
+ * In Svelte 5 components, changes to this object will automatically trigger updates.
297
+ */
298
+ get context() {
299
+ return this.state.context;
300
+ }
301
+ /**
302
+ * Access the reactive facts list.
303
+ */
304
+ get facts() {
305
+ return this.state.facts;
306
+ }
307
+ /**
308
+ * Access the reactive metadata.
309
+ */
310
+ get meta() {
311
+ return this.state.meta;
312
+ }
313
+ /**
314
+ * Apply a mutation to the state.
315
+ * Changes will automatically trigger Svelte reactivity.
316
+ *
317
+ * @param mutator A function that receives the state and modifies it.
318
+ */
319
+ apply(mutator) {
320
+ mutator(this.state);
321
+ }
322
+ /**
323
+ * Process events through the logic engine and update reactive state.
324
+ *
325
+ * @param events Events to process
326
+ */
327
+ step(events) {
328
+ const result = this._engine.step(events);
329
+ this.state.context = result.state.context;
330
+ this.state.facts = result.state.facts;
331
+ this.state.meta = result.state.meta ?? {};
332
+ }
333
+ };
334
+ function createReactiveEngine(options) {
335
+ return new ReactiveLogicEngine(options);
336
+ }
337
+
338
+ export {
339
+ PRAXIS_PROTOCOL_VERSION,
340
+ PraxisRegistry,
341
+ LogicEngine,
342
+ createPraxisEngine,
343
+ ReactiveLogicEngine,
344
+ createReactiveEngine
345
+ };