@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.
- package/FRAMEWORK.md +106 -15
- package/README.md +275 -53
- package/dist/browser/adapter-TM4IS5KT.js +12 -0
- package/dist/browser/chunk-JQ64KMLN.js +141 -0
- package/dist/browser/chunk-LE2ZJYFC.js +154 -0
- package/dist/browser/chunk-VOMLVI6V.js +197 -0
- package/dist/browser/engine-YJZV4SLD.js +8 -0
- package/dist/browser/index.d.ts +300 -11
- package/dist/browser/index.js +334 -325
- package/dist/browser/integrations/svelte.d.ts +3 -1
- package/dist/browser/integrations/svelte.js +8 -0
- package/dist/browser/{engine-BjdqxeXG.d.ts → reactive-engine.svelte-C9OpcTHf.d.ts} +87 -1
- package/dist/node/adapter-K6DOX6XS.js +13 -0
- package/dist/node/chunk-JQ64KMLN.js +141 -0
- package/dist/node/chunk-LE2ZJYFC.js +154 -0
- package/dist/node/chunk-S54337I5.js +446 -0
- package/dist/node/chunk-VOMLVI6V.js +197 -0
- package/dist/node/cli/index.cjs +1444 -889
- package/dist/node/cli/index.js +9 -0
- package/dist/node/components/index.d.cts +2 -2
- package/dist/node/components/index.d.ts +2 -2
- package/dist/node/docs-JFNYTOJA.js +102 -0
- package/dist/node/engine-2DQBKBJC.js +9 -0
- package/dist/node/index.cjs +747 -234
- package/dist/node/index.d.cts +237 -15
- package/dist/node/index.d.ts +237 -15
- package/dist/node/index.js +339 -767
- package/dist/node/integrations/svelte.cjs +357 -2
- package/dist/node/integrations/svelte.d.cts +3 -1
- package/dist/node/integrations/svelte.d.ts +3 -1
- package/dist/node/integrations/svelte.js +7 -0
- package/dist/node/{engine-CVJobhHm.d.cts → reactive-engine.svelte-1M4m_C_v.d.cts} +87 -1
- package/dist/node/{engine-1iqLe6_P.d.ts → reactive-engine.svelte-ChNFn4Hj.d.ts} +87 -1
- package/dist/node/{terminal-adapter-XLtCjjb_.d.cts → terminal-adapter-CDzxoLKR.d.cts} +68 -1
- package/dist/node/{terminal-adapter-07HGftGQ.d.ts → terminal-adapter-CWka-yL8.d.ts} +68 -1
- package/package.json +3 -2
- package/src/__tests__/reactive-engine.test.ts +516 -0
- package/src/cli/commands/docs.ts +147 -0
- package/src/cli/index.ts +21 -0
- package/src/core/pluresdb/README.md +156 -0
- package/src/core/pluresdb/adapter.ts +165 -0
- package/src/core/pluresdb/index.ts +3 -3
- package/src/core/reactive-engine.svelte.ts +93 -19
- package/src/core/reactive-engine.ts +284 -22
- package/src/index.browser.ts +16 -0
- package/src/index.ts +16 -0
- package/src/integrations/pluresdb.ts +2 -2
- package/src/integrations/svelte.ts +8 -0
- 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
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
//
|
|
16
|
-
|
|
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
|
|
36
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
93
|
+
* Process events through the logic engine and update reactive state.
|
|
94
|
+
*
|
|
95
|
+
* @param events Events to process
|
|
61
96
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
}
|