@plures/praxis 1.1.2 → 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 +67 -6
  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 +284 -22
  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
@@ -1,8 +1,14 @@
1
1
  /**
2
2
  * Praxis Reactive Logic Engine
3
3
  *
4
- * A minimal TypeScript implementation of the Praxis Logic Engine.
5
- * This variant avoids Svelte-specific reactivity primitives to remain framework-agnostic.
4
+ * A framework-agnostic reactive implementation of the Praxis Logic Engine.
5
+ * Uses JavaScript Proxies to provide reactivity without Svelte-specific primitives.
6
+ *
7
+ * This implementation provides:
8
+ * - Proxy-based state tracking for automatic reactivity
9
+ * - Subscription-based change notifications
10
+ * - Computed/derived values support
11
+ * - Compatible API with Svelte-based implementation
6
12
  */
7
13
 
8
14
  export interface ReactiveEngineOptions<TContext> {
@@ -11,48 +17,304 @@ export interface ReactiveEngineOptions<TContext> {
11
17
  initialMeta?: Record<string, unknown>;
12
18
  }
13
19
 
20
+ /**
21
+ * Callback type for state change subscribers
22
+ */
23
+ export type StateChangeCallback<TContext> = (state: {
24
+ context: TContext;
25
+ facts: any[];
26
+ meta: Record<string, unknown>;
27
+ }) => void;
28
+
29
+ /**
30
+ * Callback type for unsubscribe function
31
+ */
32
+ export type UnsubscribeFn = () => void;
33
+
34
+ /**
35
+ * Framework-agnostic reactive logic engine using JavaScript Proxies
36
+ */
14
37
  export class ReactiveLogicEngine<TContext extends object> {
15
- state: { context: TContext; facts: any[]; meta: Record<string, unknown> } = {
16
- context: {} as TContext,
17
- facts: [],
18
- meta: {}
19
- };
38
+ private _state: { context: TContext; facts: any[]; meta: Record<string, unknown> };
39
+ private _subscribers = new Set<StateChangeCallback<TContext>>();
40
+ private _contextProxy: TContext;
41
+ private _factsProxy: any[];
42
+ private _metaProxy: Record<string, unknown>;
43
+ private _batchDepth = 0;
44
+ private _pendingNotification = false;
45
+ private _proxyCache = new WeakMap<object, any>();
46
+
47
+ // Array methods that mutate the array
48
+ private static readonly ARRAY_MUTATORS = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
20
49
 
21
50
  constructor(options: ReactiveEngineOptions<TContext>) {
22
- this.state.context = options.initialContext;
23
- this.state.facts = options.initialFacts ?? [];
24
- this.state.meta = options.initialMeta ?? {};
51
+ // Initialize raw state
52
+ this._state = {
53
+ context: options.initialContext,
54
+ facts: options.initialFacts ?? [],
55
+ meta: options.initialMeta ?? {},
56
+ };
57
+
58
+ // Create reactive proxies
59
+ this._contextProxy = this._createReactiveProxy(this._state.context);
60
+ this._factsProxy = this._createReactiveProxy(this._state.facts);
61
+ this._metaProxy = this._createReactiveProxy(this._state.meta);
62
+ }
63
+
64
+ /**
65
+ * Create a reactive proxy that notifies subscribers on changes.
66
+ * Uses a WeakMap cache to avoid creating multiple proxies for the same object.
67
+ */
68
+ private _createReactiveProxy<T extends object>(target: T): T {
69
+ // Check cache first
70
+ const cached = this._proxyCache.get(target);
71
+ if (cached) {
72
+ return cached;
73
+ }
74
+
75
+ const self = this;
76
+
77
+ const handler: ProxyHandler<T> = {
78
+ get(obj, prop) {
79
+ const value = Reflect.get(obj, prop);
80
+
81
+ // If the value is an object or array, wrap it in a proxy too
82
+ if (value && typeof value === 'object') {
83
+ return self._createReactiveProxy(value);
84
+ }
85
+
86
+ // Bind array methods to notify on mutations
87
+ if (Array.isArray(obj) && typeof value === 'function') {
88
+ if (ReactiveLogicEngine.ARRAY_MUTATORS.includes(prop as string)) {
89
+ return function(...args: any[]) {
90
+ const result = (value as Function).apply(obj, args);
91
+ self._notify();
92
+ return result;
93
+ };
94
+ }
95
+ }
96
+
97
+ return value;
98
+ },
99
+ set(obj, prop, value) {
100
+ const oldValue = (obj as any)[prop];
101
+ const result = Reflect.set(obj, prop, value);
102
+
103
+ // Only notify if value actually changed
104
+ if (oldValue !== value) {
105
+ self._notify();
106
+ }
107
+
108
+ return result;
109
+ },
110
+ deleteProperty(obj, prop) {
111
+ const result = Reflect.deleteProperty(obj, prop);
112
+ self._notify();
113
+ return result;
114
+ },
115
+ };
116
+
117
+ const proxy = new Proxy(target, handler);
118
+
119
+ // Cache the proxy
120
+ this._proxyCache.set(target, proxy);
121
+
122
+ return proxy;
123
+ }
124
+
125
+ /**
126
+ * Notify all subscribers of state changes
127
+ */
128
+ private _notify() {
129
+ // If we're in a batch, just mark that we need to notify
130
+ if (this._batchDepth > 0) {
131
+ this._pendingNotification = true;
132
+ return;
133
+ }
134
+
135
+ // Pass proxy versions to subscribers so they can't bypass reactivity
136
+ const currentState = {
137
+ context: this._contextProxy,
138
+ facts: this._factsProxy,
139
+ meta: this._metaProxy,
140
+ };
141
+
142
+ this._subscribers.forEach((callback) => {
143
+ try {
144
+ callback(currentState);
145
+ } catch (error) {
146
+ console.error('Error in reactive engine subscriber:', error);
147
+ }
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Get the full state object
153
+ */
154
+ get state() {
155
+ return {
156
+ context: this._contextProxy,
157
+ facts: this._factsProxy,
158
+ meta: this._metaProxy,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Access the reactive context.
164
+ * Changes to this object will trigger subscriber notifications.
165
+ */
166
+ get context(): TContext {
167
+ return this._contextProxy;
25
168
  }
26
169
 
27
170
  /**
28
- * Access the context directly.
29
- * Framework-specific wrappers (e.g., Svelte runes) can build on top of this value.
171
+ * Access the reactive facts list.
172
+ * Changes to this array will trigger subscriber notifications.
30
173
  */
31
- get context() {
32
- return this.state.context;
174
+ get facts(): any[] {
175
+ return this._factsProxy;
33
176
  }
34
177
 
35
178
  /**
36
- * Access the facts list.
179
+ * Access the reactive metadata.
180
+ * Changes to this object will trigger subscriber notifications.
37
181
  */
38
- get facts() {
39
- return this.state.facts;
182
+ get meta(): Record<string, unknown> {
183
+ return this._metaProxy;
40
184
  }
41
185
 
42
186
  /**
43
187
  * Apply a mutation to the state.
44
188
  * This is the "Action" or "Rule" equivalent.
189
+ * Mutations are batched - notifications only happen once per apply call.
45
190
  *
46
191
  * @param mutator A function that receives the state and modifies it.
47
192
  */
48
- apply(mutator: (state: { context: TContext; facts: any[]; meta: Record<string, unknown> }) => void) {
49
- mutator(this.state);
193
+ apply(mutator: (state: { context: TContext; facts: any[]; meta: Record<string, unknown> }) => void): void {
194
+ this._batchDepth++;
195
+ try {
196
+ mutator({
197
+ context: this._contextProxy,
198
+ facts: this._factsProxy,
199
+ meta: this._metaProxy,
200
+ });
201
+ } finally {
202
+ this._batchDepth--;
203
+ if (this._batchDepth === 0 && this._pendingNotification) {
204
+ this._pendingNotification = false;
205
+ this._notify();
206
+ }
207
+ }
50
208
  }
51
209
 
52
210
  /**
53
- * Access the metadata.
211
+ * Subscribe to state changes.
212
+ * Returns an unsubscribe function.
213
+ *
214
+ * @param callback Function to call when state changes
215
+ * @returns Unsubscribe function
54
216
  */
55
- get meta() {
56
- return this.state.meta;
217
+ subscribe(callback: StateChangeCallback<TContext>): UnsubscribeFn {
218
+ this._subscribers.add(callback);
219
+
220
+ // Immediately call with current state (using proxy versions)
221
+ try {
222
+ callback({
223
+ context: this._contextProxy,
224
+ facts: this._factsProxy,
225
+ meta: this._metaProxy,
226
+ });
227
+ } catch (error) {
228
+ console.error('Error in reactive engine subscriber:', error);
229
+ }
230
+
231
+ // Return unsubscribe function
232
+ return () => {
233
+ this._subscribers.delete(callback);
234
+ };
57
235
  }
236
+
237
+ /**
238
+ * Create a derived/computed value from the state.
239
+ * The selector function will be called whenever the state changes.
240
+ *
241
+ * @param selector Function to extract derived value from state
242
+ * @returns Object with subscribe method for reactive updates
243
+ */
244
+ $derived<TDerived>(
245
+ selector: (state: { context: TContext; facts: any[]; meta: Record<string, unknown> }) => TDerived
246
+ ): { subscribe: (callback: (value: TDerived) => void) => UnsubscribeFn } {
247
+ const subscribers = new Set<(value: TDerived) => void>();
248
+ let currentValue = selector({
249
+ context: this._contextProxy,
250
+ facts: this._factsProxy,
251
+ meta: this._metaProxy,
252
+ });
253
+
254
+ // Subscribe to state changes and recompute derived value
255
+ this.subscribe(() => {
256
+ const newValue = selector({
257
+ context: this._contextProxy,
258
+ facts: this._factsProxy,
259
+ meta: this._metaProxy,
260
+ });
261
+
262
+ // Only notify if value changed
263
+ if (newValue !== currentValue) {
264
+ currentValue = newValue;
265
+ subscribers.forEach((callback) => {
266
+ try {
267
+ callback(currentValue);
268
+ } catch (error) {
269
+ console.error('Error in derived value subscriber:', error);
270
+ }
271
+ });
272
+ }
273
+ });
274
+
275
+ return {
276
+ subscribe: (callback: (value: TDerived) => void) => {
277
+ subscribers.add(callback);
278
+ try {
279
+ callback(currentValue); // Immediately call with current value
280
+ } catch (error) {
281
+ console.error('Error in derived value subscriber:', error);
282
+ }
283
+ return () => {
284
+ subscribers.delete(callback);
285
+ };
286
+ },
287
+ };
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Create a new reactive logic engine instance.
293
+ *
294
+ * @param options Configuration options for the reactive engine
295
+ * @returns A new ReactiveLogicEngine instance
296
+ *
297
+ * @example
298
+ * ```typescript
299
+ * const engine = createReactiveEngine({
300
+ * initialContext: { count: 0 },
301
+ * initialFacts: [],
302
+ * initialMeta: {}
303
+ * });
304
+ *
305
+ * // Subscribe to changes
306
+ * engine.subscribe((state) => {
307
+ * console.log('State changed:', state);
308
+ * });
309
+ *
310
+ * // Mutate state (will trigger subscribers)
311
+ * engine.apply((state) => {
312
+ * state.context.count++;
313
+ * });
314
+ * ```
315
+ */
316
+ export function createReactiveEngine<TContext extends object>(
317
+ options: ReactiveEngineOptions<TContext>
318
+ ): ReactiveLogicEngine<TContext> {
319
+ return new ReactiveLogicEngine(options);
58
320
  }
@@ -33,6 +33,14 @@ export type { PraxisEngineOptions } from './core/engine.js';
33
33
  export { LogicEngine, createPraxisEngine } from './core/engine.js';
34
34
  export * from './core/reactive-engine.svelte.js';
35
35
 
36
+ // Framework-agnostic Reactive Engine
37
+ export {
38
+ ReactiveLogicEngine as FrameworkAgnosticReactiveEngine,
39
+ createReactiveEngine as createFrameworkAgnosticReactiveEngine,
40
+ type ReactiveEngineOptions as FrameworkAgnosticReactiveEngineOptions,
41
+ type StateChangeCallback,
42
+ } from './core/reactive-engine.js';
43
+
36
44
  // Actors
37
45
  export type { Actor } from './core/actors.js';
38
46
  export { ActorManager, createTimerActor } from './core/actors.js';
@@ -102,6 +110,8 @@ export {
102
110
  export type {
103
111
  PraxisDB,
104
112
  UnsubscribeFn,
113
+ PluresDBInstance,
114
+ PluresDBAdapterConfig,
105
115
  EventStreamEntry,
106
116
  PraxisDBStoreOptions,
107
117
  StoredSchema,
@@ -113,6 +123,8 @@ export type {
113
123
  export {
114
124
  InMemoryPraxisDB,
115
125
  createInMemoryDB,
126
+ PluresDBPraxisAdapter,
127
+ createPluresDB,
116
128
  PraxisDBStore,
117
129
  createPraxisDBStore,
118
130
  PRAXIS_PATHS,
package/src/index.ts CHANGED
@@ -76,6 +76,14 @@ export type { PraxisEngineOptions } from './core/engine.js';
76
76
  export { LogicEngine, createPraxisEngine } from './core/engine.js';
77
77
  export * from './core/reactive-engine.svelte.js';
78
78
 
79
+ // Framework-agnostic Reactive Engine
80
+ export {
81
+ ReactiveLogicEngine as FrameworkAgnosticReactiveEngine,
82
+ createReactiveEngine as createFrameworkAgnosticReactiveEngine,
83
+ type ReactiveEngineOptions as FrameworkAgnosticReactiveEngineOptions,
84
+ type StateChangeCallback,
85
+ } from './core/reactive-engine.js';
86
+
79
87
  // Actors
80
88
  export type { Actor } from './core/actors.js';
81
89
  export { ActorManager, createTimerActor } from './core/actors.js';
@@ -156,6 +164,8 @@ export {
156
164
  export type {
157
165
  PraxisDB,
158
166
  UnsubscribeFn,
167
+ PluresDBInstance,
168
+ PluresDBAdapterConfig,
159
169
  EventStreamEntry,
160
170
  PraxisDBStoreOptions,
161
171
  StoredSchema,
@@ -167,6 +177,8 @@ export type {
167
177
  export {
168
178
  InMemoryPraxisDB,
169
179
  createInMemoryDB,
180
+ PluresDBPraxisAdapter,
181
+ createPluresDB,
170
182
  PraxisDBStore,
171
183
  createPraxisDBStore,
172
184
  PRAXIS_PATHS,
@@ -17,8 +17,8 @@ import { PraxisDBStore, createPraxisDBStore } from '../core/pluresdb/store.js';
17
17
 
18
18
  // Re-export core pluresdb types and implementations
19
19
  // Note: Using explicit exports to avoid circular dependency issues
20
- export { InMemoryPraxisDB, createInMemoryDB } from '../core/pluresdb/adapter.js';
21
- export type { PraxisDB, UnsubscribeFn } from '../core/pluresdb/adapter.js';
20
+ export { InMemoryPraxisDB, createInMemoryDB, PluresDBPraxisAdapter, createPluresDB } from '../core/pluresdb/adapter.js';
21
+ export type { PraxisDB, UnsubscribeFn, PluresDBInstance, PluresDBAdapterConfig } from '../core/pluresdb/adapter.js';
22
22
  export {
23
23
  PraxisDBStore,
24
24
  createPraxisDBStore,
@@ -7,6 +7,7 @@
7
7
  * Features:
8
8
  * - Store-based API for backward compatibility
9
9
  * - Runes-based composables for Svelte 5
10
+ * - createReactiveEngine for easy reactive engine setup
10
11
  * - Snapshot support for time-travel debugging
11
12
  * - History state pattern implementation
12
13
  * - Automatic cleanup and subscription management
@@ -15,6 +16,13 @@
15
16
  import type { LogicEngine } from '../core/engine.js';
16
17
  import type { PraxisEvent, PraxisState } from '../core/protocol.js';
17
18
 
19
+ // Re-export the Svelte 5 reactive engine
20
+ export {
21
+ ReactiveLogicEngine,
22
+ createReactiveEngine,
23
+ type ReactiveEngineOptions,
24
+ } from '../core/reactive-engine.svelte.js';
25
+
18
26
  /**
19
27
  * Writable store interface (Svelte-compatible)
20
28
  */