@plures/praxis 1.1.3 → 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.
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  ReactiveLogicEngine,
3
3
  createReactiveEngine
4
- } from "../chunk-R45WXWKH.js";
4
+ } from "../chunk-LE2ZJYFC.js";
5
+ import "../chunk-VOMLVI6V.js";
5
6
  import "../chunk-QGM4M3NI.js";
6
7
 
7
8
  // src/integrations/svelte.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plures/praxis",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "The Full Plures Application Framework - declarative schemas, logic engine, component generation, and local-first data",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -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')
@@ -143,7 +143,7 @@ export interface PluresDBAdapterConfig {
143
143
  export class PluresDBPraxisAdapter implements PraxisDB {
144
144
  private db: PluresDBInstance;
145
145
  private watchers = new Map<string, Set<(val: unknown) => void>>();
146
- private pollIntervals = new Map<string, NodeJS.Timeout>();
146
+ private pollIntervals = new Map<string, ReturnType<typeof setInterval>>();
147
147
  private lastValues = new Map<string, unknown>();
148
148
  private pollInterval: number;
149
149
 
@@ -9,6 +9,11 @@ import { PraxisRegistry } from '../core/rules.js';
9
9
  import type { PraxisEvent } from '../core/protocol.js';
10
10
  import { LogicEngine, createPraxisEngine } from '../core/engine.js';
11
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
+
12
17
  export interface ReactiveEngineOptions<TContext> {
13
18
  initialContext: TContext;
14
19
  initialFacts?: any[];
@@ -22,7 +27,7 @@ export interface ReactiveEngineOptions<TContext> {
22
27
  */
23
28
  export class ReactiveLogicEngine<TContext extends object> {
24
29
  // Use Svelte's $state rune for automatic reactivity
25
- state = $state({
30
+ state: { context: TContext; facts: any[]; meta: Record<string, unknown> } = $state({
26
31
  context: {} as TContext,
27
32
  facts: [] as any[],
28
33
  meta: {} as Record<string, unknown>
@@ -151,7 +151,7 @@ export class ReactiveLogicEngine<TContext extends object> {
151
151
  /**
152
152
  * Get the full state object
153
153
  */
154
- get state() {
154
+ get state(): { context: TContext; facts: any[]; meta: Record<string, unknown> } {
155
155
  return {
156
156
  context: this._contextProxy,
157
157
  facts: this._factsProxy,
@@ -214,3 +214,7 @@ export {
214
214
  attachTauriToEngine,
215
215
  generateTauriConfig,
216
216
  } from './integrations/tauri.js';
217
+
218
+ // Unified Integration Helpers
219
+ export type { UnifiedAppConfig, UnifiedApp } from './integrations/unified.js';
220
+ export { createUnifiedApp, attachAllIntegrations } from './integrations/unified.js';
package/src/index.ts CHANGED
@@ -268,3 +268,7 @@ export {
268
268
  attachTauriToEngine,
269
269
  generateTauriConfig,
270
270
  } from './integrations/tauri.js';
271
+
272
+ // Unified Integration Helpers
273
+ export type { UnifiedAppConfig, UnifiedApp } from './integrations/unified.js';
274
+ export { createUnifiedApp, attachAllIntegrations } from './integrations/unified.js';
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Unified Integration Helpers
3
+ *
4
+ * Convenience functions for setting up Praxis with all ecosystem integrations
5
+ * (PluresDB, Unum, State-Docs, CodeCanvas) in a single call.
6
+ */
7
+
8
+ import type { LogicEngine } from '../core/engine.js';
9
+ import type { PraxisRegistry, PraxisModule } from '../core/rules.js';
10
+ import type { PraxisSchema } from '../core/schema/types.js';
11
+ import type { PraxisDB, UnsubscribeFn } from '../core/pluresdb/adapter.js';
12
+ import type { UnumIdentity } from './unum.js';
13
+ import {
14
+ createPluresDBAdapter,
15
+ generateId,
16
+ type PluresDBAdapter,
17
+ } from './pluresdb.js';
18
+ import {
19
+ createUnumAdapter,
20
+ attachUnumToEngine,
21
+ type UnumAdapter,
22
+ type UnumChannel,
23
+ } from './unum.js';
24
+ import {
25
+ createStateDocsGenerator,
26
+ type StateDocsGenerator,
27
+ type GeneratedDoc,
28
+ } from './state-docs.js';
29
+ import {
30
+ schemaToCanvas,
31
+ type CanvasDocument,
32
+ } from './code-canvas.js';
33
+
34
+ /**
35
+ * Configuration for unified Praxis application
36
+ */
37
+ export interface UnifiedAppConfig<TContext = unknown> {
38
+ /** Praxis registry with rules and constraints */
39
+ registry: PraxisRegistry<TContext>;
40
+
41
+ /** Initial context for the engine */
42
+ initialContext: TContext;
43
+
44
+ /** PluresDB instance (if not provided, creates in-memory DB) */
45
+ db?: PraxisDB;
46
+
47
+ /** Enable Unum for distributed communication */
48
+ enableUnum?: boolean;
49
+
50
+ /** Unum identity configuration (without id and createdAt which are auto-generated) */
51
+ unumIdentity?: Omit<UnumIdentity, 'id' | 'createdAt'>;
52
+
53
+ /** Enable State-Docs documentation generation */
54
+ enableDocs?: boolean;
55
+
56
+ /** State-Docs configuration */
57
+ docsConfig?: {
58
+ projectTitle: string;
59
+ target?: string;
60
+ };
61
+
62
+ /** Praxis schema for CodeCanvas integration */
63
+ schema?: PraxisSchema;
64
+ }
65
+
66
+ /**
67
+ * Unified application instance with all integrations
68
+ */
69
+ export interface UnifiedApp<TContext = unknown> {
70
+ /** Praxis logic engine */
71
+ engine: LogicEngine<TContext>;
72
+
73
+ /** PluresDB adapter for persistence */
74
+ pluresdb: PluresDBAdapter<TContext>;
75
+
76
+ /** Unum adapter for distributed communication (if enabled) */
77
+ unum?: UnumAdapter;
78
+
79
+ /** Default Unum channel (if Unum enabled) */
80
+ channel?: UnumChannel;
81
+
82
+ /** State-Docs generator (if enabled) */
83
+ docs?: StateDocsGenerator;
84
+
85
+ /** CodeCanvas document (if schema provided) */
86
+ canvas?: CanvasDocument;
87
+
88
+ /** Generate documentation from current state */
89
+ generateDocs?: () => GeneratedDoc[];
90
+
91
+ /** Cleanup function to dispose all integrations */
92
+ dispose: () => void;
93
+ }
94
+
95
+ /**
96
+ * Create a unified Praxis application with all integrations
97
+ *
98
+ * This is a convenience function that sets up:
99
+ * - Praxis logic engine
100
+ * - PluresDB for persistence (auto-attaches to engine)
101
+ * - Unum for distributed communication (optional)
102
+ * - State-Docs for documentation generation (optional)
103
+ * - CodeCanvas for visual schema editing (optional)
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * import { createUnifiedApp } from '@plures/praxis';
108
+ *
109
+ * const app = await createUnifiedApp({
110
+ * registry: myRegistry,
111
+ * initialContext: { count: 0 },
112
+ * enableUnum: true,
113
+ * unumIdentity: { name: 'node-1' },
114
+ * enableDocs: true,
115
+ * docsConfig: { projectTitle: 'My App' },
116
+ * schema: mySchema,
117
+ * });
118
+ *
119
+ * // Use the engine
120
+ * app.engine.step([myEvent]);
121
+ *
122
+ * // Broadcast to other nodes
123
+ * if (app.channel) {
124
+ * await app.unum?.broadcastEvent(app.channel.id, myEvent);
125
+ * }
126
+ *
127
+ * // Generate documentation
128
+ * const docs = app.generateDocs?.();
129
+ *
130
+ * // Cleanup
131
+ * app.dispose();
132
+ * ```
133
+ */
134
+ export async function createUnifiedApp<TContext = unknown>(
135
+ config: UnifiedAppConfig<TContext>
136
+ ): Promise<UnifiedApp<TContext>> {
137
+ const { createPraxisEngine } = await import('../core/engine.js');
138
+ const { createInMemoryDB } = await import('../core/pluresdb/adapter.js');
139
+
140
+ // Create database if not provided
141
+ const db = config.db || createInMemoryDB();
142
+
143
+ // Create PluresDB adapter
144
+ const pluresdb = createPluresDBAdapter({
145
+ db,
146
+ registry: config.registry,
147
+ initialContext: config.initialContext,
148
+ });
149
+
150
+ // Create Praxis engine
151
+ const engine = createPraxisEngine({
152
+ initialContext: config.initialContext,
153
+ registry: config.registry,
154
+ });
155
+
156
+ // Attach PluresDB to engine
157
+ pluresdb.attachEngine(engine);
158
+
159
+ const disposers: UnsubscribeFn[] = [];
160
+
161
+ // Setup Unum if enabled
162
+ let unum: UnumAdapter | undefined;
163
+ let channel: UnumChannel | undefined;
164
+ if (config.enableUnum) {
165
+ // Convert partial identity to full identity if provided
166
+ const fullIdentity: UnumIdentity | undefined = config.unumIdentity
167
+ ? {
168
+ ...config.unumIdentity,
169
+ id: generateId(),
170
+ createdAt: Date.now(),
171
+ }
172
+ : undefined;
173
+
174
+ unum = await createUnumAdapter({
175
+ db,
176
+ identity: fullIdentity,
177
+ realtime: true,
178
+ });
179
+
180
+ // Create default channel
181
+ channel = await unum.createChannel(
182
+ config.unumIdentity?.name || 'praxis-app',
183
+ []
184
+ );
185
+
186
+ // Attach Unum to engine
187
+ const unumDisposer = attachUnumToEngine(engine, unum, channel.id);
188
+ disposers.push(unumDisposer);
189
+ }
190
+
191
+ // Setup State-Docs if enabled
192
+ let docs: StateDocsGenerator | undefined;
193
+ let generateDocs: (() => GeneratedDoc[]) | undefined;
194
+ if (config.enableDocs && config.docsConfig) {
195
+ docs = createStateDocsGenerator({
196
+ projectTitle: config.docsConfig.projectTitle,
197
+ target: config.docsConfig.target || './docs',
198
+ });
199
+
200
+ generateDocs = () => {
201
+ // Get rules and constraints from registry
202
+ const module: PraxisModule<TContext> = {
203
+ rules: config.registry.getAllRules(),
204
+ constraints: config.registry.getAllConstraints(),
205
+ };
206
+ return docs!.generateFromModule(module);
207
+ };
208
+ }
209
+
210
+ // Setup CodeCanvas if schema provided
211
+ let canvas: CanvasDocument | undefined;
212
+ if (config.schema) {
213
+ // Convert PraxisSchema to PSFSchema format expected by schemaToCanvas
214
+ // Both types are structurally compatible, so we can safely cast
215
+ canvas = schemaToCanvas(config.schema as unknown as import('../../core/schema-engine/psf.js').PSFSchema, { layout: 'hierarchical' });
216
+ }
217
+
218
+ return {
219
+ engine,
220
+ pluresdb,
221
+ unum,
222
+ channel,
223
+ docs,
224
+ canvas,
225
+ generateDocs,
226
+ dispose: () => {
227
+ pluresdb.dispose();
228
+ if (unum) {
229
+ // Disconnect from Unum, log errors during cleanup
230
+ unum.disconnect().catch((err) => {
231
+ console.warn('Warning: Error during Unum disconnect:', err);
232
+ });
233
+ }
234
+ for (const disposer of disposers) {
235
+ disposer();
236
+ }
237
+ },
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Attach all available integrations to an existing Praxis engine
243
+ *
244
+ * This is useful when you already have an engine and want to add integrations.
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * import { createPraxisEngine, attachAllIntegrations } from '@plures/praxis';
249
+ *
250
+ * const engine = createPraxisEngine({ initialContext: {}, registry });
251
+ *
252
+ * const integrations = await attachAllIntegrations(engine, registry, {
253
+ * enableUnum: true,
254
+ * enableDocs: true,
255
+ * });
256
+ *
257
+ * // Later cleanup
258
+ * integrations.dispose();
259
+ * ```
260
+ */
261
+ export async function attachAllIntegrations<TContext = unknown>(
262
+ engine: LogicEngine<TContext>,
263
+ registry: PraxisRegistry<TContext>,
264
+ options: {
265
+ db?: PraxisDB;
266
+ enableUnum?: boolean;
267
+ unumIdentity?: Omit<UnumIdentity, 'id' | 'createdAt'>;
268
+ enableDocs?: boolean;
269
+ docsConfig?: { projectTitle: string; target?: string };
270
+ } = {}
271
+ ): Promise<{
272
+ pluresdb: PluresDBAdapter<TContext>;
273
+ unum?: UnumAdapter;
274
+ channel?: UnumChannel;
275
+ docs?: StateDocsGenerator;
276
+ dispose: () => void;
277
+ }> {
278
+ const { createInMemoryDB } = await import('../core/pluresdb/adapter.js');
279
+
280
+ // Create database if not provided
281
+ const db = options.db || createInMemoryDB();
282
+
283
+ // Create PluresDB adapter
284
+ const pluresdb = createPluresDBAdapter({
285
+ db,
286
+ registry,
287
+ initialContext: engine.getContext(),
288
+ });
289
+
290
+ // Attach PluresDB to engine
291
+ pluresdb.attachEngine(engine);
292
+
293
+ const disposers: UnsubscribeFn[] = [];
294
+
295
+ // Setup Unum if enabled
296
+ let unum: UnumAdapter | undefined;
297
+ let channel: UnumChannel | undefined;
298
+ if (options.enableUnum) {
299
+ // Convert partial identity to full identity if provided
300
+ const fullIdentity: UnumIdentity | undefined = options.unumIdentity
301
+ ? {
302
+ ...options.unumIdentity,
303
+ id: generateId(),
304
+ createdAt: Date.now(),
305
+ }
306
+ : undefined;
307
+
308
+ unum = await createUnumAdapter({
309
+ db,
310
+ identity: fullIdentity,
311
+ realtime: true,
312
+ });
313
+
314
+ channel = await unum.createChannel(
315
+ options.unumIdentity?.name || 'praxis-app',
316
+ []
317
+ );
318
+
319
+ const unumDisposer = attachUnumToEngine(engine, unum, channel.id);
320
+ disposers.push(unumDisposer);
321
+ }
322
+
323
+ // Setup State-Docs if enabled
324
+ let docs: StateDocsGenerator | undefined;
325
+ if (options.enableDocs && options.docsConfig) {
326
+ docs = createStateDocsGenerator({
327
+ projectTitle: options.docsConfig.projectTitle,
328
+ target: options.docsConfig.target || './docs',
329
+ });
330
+ }
331
+
332
+ return {
333
+ pluresdb,
334
+ unum,
335
+ channel,
336
+ docs,
337
+ dispose: () => {
338
+ pluresdb.dispose();
339
+ if (unum) {
340
+ // Disconnect from Unum, log errors during cleanup
341
+ unum.disconnect().catch((err) => {
342
+ console.warn('Warning: Error during Unum disconnect:', err);
343
+ });
344
+ }
345
+ for (const disposer of disposers) {
346
+ disposer();
347
+ }
348
+ },
349
+ };
350
+ }