diff --git a/CHANGELOG.md b/CHANGELOG.md index 1804dab..56ebcbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added - Praxis 1.2.x Support + +- **Enhanced Model Documentation**: + - Support for field validation rules (required, min, max, pattern) + - Support for model-level constraints (unique, check, foreign keys) + - Support for database indexes (btree, hash, fulltext) + - Support for relationships (one-to-one, one-to-many, many-to-many) + - Support for optional fields and default values + +- **Enhanced Logic Documentation**: + - Support for business rules with conditions (`when`) and actions (`then`) + - Support for runtime constraints with validation checks + - Support for rule priorities + - Support for event triggers in rules + - Documentation for rule execution flow + +- **Enhanced Component Documentation**: + - Support for component props with types and defaults + - Support for component events with payload types + - Support for layout configuration (stack, grid, flex, absolute) + - Support for styling (CSS classes, inline styles, theme tokens) + - Enhanced component type documentation + +- **Orchestration Support**: + - Support for orchestration node configuration + - Support for node bindings to PluresDB paths + - Support for synchronization configuration + - Support for health check configuration + - Documentation for distributed system architecture + +- **Schema Metadata Support**: + - Support for additional metadata in schemas + - Version information in generated documentation + +- **New Example**: Task Management v2 demonstrating all Praxis 1.2.x features + +### Changed + +- **Parser**: Updated to parse Praxis 1.2.x schema format +- **Templates**: Enhanced default templates to display new Praxis 1.2.x features +- **Type Definitions**: Updated `PraxisSchema` types to match Praxis 1.2.x +- **README**: Updated to highlight Praxis 1.2.x compatibility + +### Documentation + +- Added comprehensive example showing all Praxis 1.2.x features +- Updated documentation generation to include rules, constraints, and orchestration +- Enhanced Mermaid diagram generation for complex state machines + ## [2.0.0] - 2025-12-27 ### 🚨 Breaking Changes diff --git a/README.md b/README.md index 3e78607..5f6aa2c 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,19 @@ praxisdoc automatically generates human-readable documentation from your [Praxis](https://github.com/plures/praxis) application schemas. It transforms declarative schema definitions into clear documentation that anyone can understandβ€”perfect for product teams, QA engineers, and stakeholders. +**Now supports Praxis 1.2.x** with enhanced features including business rules, constraints, orchestration, and more. + ### About Praxis **Praxis** is a schema-driven application framework for building local-first, distributed applications. It uses declarative schemas to define: -- **Models**: Data structures with fields, types, and indexes -- **Logic**: Business rules, events, facts, and state transitions -- **Components**: UI elements auto-generated from schemas +- **Models**: Data structures with fields, types, indexes, constraints, and relationships +- **Logic**: Business rules, events, facts, state transitions, and runtime constraints +- **Components**: UI elements auto-generated from schemas with props, events, and layouts +- **Orchestration**: Distributed node configuration with PluresDB integration - **Documentation**: Automatically synchronized with your code -praxisdoc leverages Praxis's schema format to generate comprehensive documentation including state diagrams, event flows, and data models. +praxisdoc leverages Praxis's schema format to generate comprehensive documentation including state diagrams, event flows, data models, business rules, and orchestration diagrams. ## Installation @@ -118,14 +121,23 @@ praxisdoc gen --config=.praxisDoc.json ## Real-World Example -See the [Task Management Example](./examples/task-management/README.md) for a comprehensive demonstration: +See the examples directory for comprehensive demonstrations: + +- **[Task Management Example](./examples/task-management/README.md)**: Basic Praxis schema with state transitions +- **[Task Management v2 Example](./examples/task-management-v2/README.md)**: Enhanced Praxis 1.2.x features + - Business rules with conditions and actions + - Runtime constraints and validation + - Enhanced component definitions + - Orchestration configuration +- **[Shopping Cart Example](./examples/shopping-cart/README.md)**: Legacy XState machine (auto-converted) -- **Before**: Praxis schema definitions in TypeScript +Each example includes: +- **Before**: Praxis/XState schema definitions in TypeScript - **After**: Clear Markdown documentation with Mermaid diagrams - **Use Cases**: Product planning, QA testing, stakeholder communication - **CLI Output**: See exactly what the tool generates -[View the complete example β†’](./examples/task-management/README.md) +[View all examples β†’](./examples/) ## Supported Formats @@ -211,9 +223,13 @@ See the [Task Management Example](./examples/task-management/README.md) for a co ### What praxisdoc Does -- βœ… Parses Praxis schema definitions (models, logic, components) +- βœ… Parses Praxis schema definitions (models, logic, components, orchestration) +- βœ… Supports Praxis 1.2.x enhanced features (rules, constraints, relationships, orchestration) - βœ… Generates Markdown documentation from schemas - βœ… Creates Mermaid state diagrams from logic transitions +- βœ… Documents business rules and runtime constraints +- βœ… Documents model constraints, indexes, and relationships +- βœ… Documents component props, events, layouts, and styling - βœ… Supports customizable templates - βœ… Works with TypeScript and JavaScript - βœ… Converts legacy XState machines to Praxis format @@ -312,13 +328,15 @@ We welcome contributions! Whether you're fixing bugs, adding features, or improv 1. πŸ“– Read the [CONTRIBUTING.md](./CONTRIBUTING.md) guide 2. πŸ—ΊοΈ Check the [ROADMAP.md](./ROADMAP.md) for planned features -3. 🏷️ Look for issues tagged with **`good first issue`** -4. πŸ’¬ Join the discussion on open issues +3. πŸ“š Review [Praxis 1.2.x Support](./docs/PRAXIS_V2_SUPPORT.md) for latest features +4. 🏷️ Look for issues tagged with **`good first issue`** +5. πŸ’¬ Join the discussion on open issues ### Quick Links - [CONTRIBUTING.md](./CONTRIBUTING.md) - Contribution guidelines and development setup - [ROADMAP.md](./ROADMAP.md) - Future plans and feature requests +- [Praxis 1.2.x Support](./docs/PRAXIS_V2_SUPPORT.md) - Enhanced Praxis features documentation - [Issue Templates](./.github/ISSUE_TEMPLATE/) - Bug reports, feature requests, documentation - [ADR Process](./docs/adr/README.md) - Architectural decision records diff --git a/ROADMAP.md b/ROADMAP.md index fdba035..a395b50 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -26,11 +26,14 @@ This document outlines the planned features and improvements for praxisdoc. We u **Focus**: Deeper integration with Praxis framework features +- [x] βœ… **Support enhanced model definitions** (constraints, indexes, relationships) +- [x] βœ… **Support business rules in logic** (conditions, actions, priorities) +- [x] βœ… **Support runtime constraints** (validation checks and error messages) +- [x] βœ… **Enhanced component documentation** (props, events, layout, styling) +- [x] βœ… **Orchestration support** (nodes, bindings, sync, health) - [ ] Support YAML schema definitions - [ ] Support JSON schema definitions -- [ ] Parse Praxis facts and rules documentation -- [ ] Generate component documentation from schema -- [ ] Add support for Praxis constraints and validations +- [ ] Parse Praxis facts and rules documentation (partial - rules done) - [ ] Integrate with Praxis CLI for seamless workflow - [ ] Add watch mode for continuous documentation generation diff --git a/docs/PRAXIS_V2_SUPPORT.md b/docs/PRAXIS_V2_SUPPORT.md new file mode 100644 index 0000000..85ad483 --- /dev/null +++ b/docs/PRAXIS_V2_SUPPORT.md @@ -0,0 +1,344 @@ +# Praxis 1.2.x Support + +This document describes praxisdoc's support for Praxis 1.2.x enhanced features. + +## Overview + +As of version 2.1.0, praxisdoc fully supports the enhanced schema format introduced in Praxis 1.2.x. This includes: + +- Enhanced model definitions with constraints, indexes, and relationships +- Business rules in logic definitions with conditions and actions +- Runtime constraints with validation +- Enhanced component definitions with props, events, layout, and styling +- Orchestration configuration for distributed systems + +## Enhanced Model Definitions + +### Field Validation + +Models now support field-level validation rules: + +```typescript +{ + name: 'email', + type: 'string', + validation: [ + { type: 'required' }, + { type: 'pattern', value: '^[^@]+@[^@]+\\.[^@]+$', message: 'Invalid email' } + ] +} +``` + +### Model Constraints + +Define constraints at the model level: + +```typescript +{ + constraints: [ + { + id: 'unique_email', + description: 'Email must be unique', + type: 'unique', + fields: ['email'] + }, + { + id: 'valid_age', + description: 'Age must be positive', + type: 'check', + fields: ['age'] + } + ] +} +``` + +### Indexes + +Optimize queries with database indexes: + +```typescript +{ + indexes: [ + { + name: 'by_email', + fields: ['email'], + unique: true, + type: 'hash' + }, + { + name: 'by_name_created', + fields: ['name', 'createdAt'], + type: 'btree' + } + ] +} +``` + +### Relationships + +Define relationships between models: + +```typescript +{ + relationships: [ + { + name: 'posts', + type: 'one-to-many', + target: 'Post', + foreignKey: 'authorId', + cascadeDelete: true + } + ] +} +``` + +## Business Rules + +Logic definitions now support business rules with conditions and actions: + +```typescript +{ + rules: [ + { + id: 'notify_high_priority', + description: 'Notify admin when high priority item created', + on: ['ITEM_CREATE'], + when: 'event.payload.priority >= 4', + then: 'emit("NOTIFY_ADMIN", { itemId: event.payload.id })', + priority: 1 + } + ] +} +``` + +### Rule Components + +- **id**: Unique identifier for the rule +- **description**: Human-readable description +- **on**: Array of event triggers +- **when**: Optional condition (JavaScript expression) +- **then**: Action to execute (JavaScript expression) +- **priority**: Execution priority (lower number = higher priority) + +## Runtime Constraints + +Define validation that runs during state transitions: + +```typescript +{ + constraints: [ + { + id: 'cannot_complete_unassigned', + description: 'Cannot complete a task without an assignee', + check: 'state.tasks[taskId].assignee !== null', + message: 'Task must be assigned before completion' + } + ] +} +``` + +## Enhanced Components + +### Component Props + +Define component properties with types and defaults: + +```typescript +{ + props: [ + { + name: 'taskId', + type: 'string', + required: true, + description: 'ID of the task to display' + }, + { + name: 'editable', + type: 'boolean', + default: false, + description: 'Whether the task can be edited' + } + ] +} +``` + +### Component Events + +Define events with payload types: + +```typescript +{ + events: [ + { + name: 'submit', + payload: 'Task', + description: 'Fired when form is submitted' + }, + { + name: 'taskSelected', + payload: 'string', + description: 'Fired when a task is selected' + } + ] +} +``` + +### Layout Configuration + +Define component layout: + +```typescript +{ + layout: { + type: 'stack', + direction: 'vertical', + gap: 16, + padding: 24, + alignment: 'center' + } +} +``` + +Supported layout types: +- `stack`: Vertical or horizontal stack +- `grid`: CSS Grid layout +- `flex`: Flexbox layout +- `absolute`: Absolute positioning + +### Styling + +Define component styling: + +```typescript +{ + styling: { + classes: ['task-form', 'elevated'], + styles: { + 'background-color': '#f5f5f5', + 'border-radius': '8px' + }, + theme: { + primary: 'blue', + spacing: 'comfortable' + } + } +} +``` + +## Orchestration + +Define distributed system configuration: + +```typescript +{ + orchestration: { + type: 'mcp', + nodes: [ + { + id: 'processor', + type: 'terminal', + config: { command: 'node', args: ['process.js'] }, + x: 100, + y: 100, + bindings: { + output: '/processing/output', + input: '/processing/input' + } + } + ], + sync: { + interval: 5000, + conflictResolution: 'last-write-wins', + targets: ['local', 'cloud'] + }, + health: { + interval: 30000, + endpoints: ['/health', '/ready'], + timeout: 5000 + } + } +} +``` + +### Node Configuration + +- **id**: Unique node identifier +- **type**: Node type (terminal, webhook, custom) +- **config**: Type-specific configuration +- **x, y**: Position for canvas visualization +- **props**: Node-specific properties +- **bindings**: Connections to PluresDB paths + +### Synchronization + +- **interval**: Sync frequency in milliseconds +- **conflictResolution**: Strategy for conflicts (last-write-wins, merge, custom) +- **targets**: Sync targets (local, cloud, etc.) + +### Health Checks + +- **interval**: Check frequency in milliseconds +- **endpoints**: Health check endpoints +- **timeout**: Request timeout in milliseconds + +## Documentation Generation + +praxisdoc automatically generates documentation for all Praxis 1.2.x features: + +### Model Documentation + +- Field types and descriptions +- Validation rules +- Constraints +- Indexes +- Relationships + +### Logic Documentation + +- Events and facts +- Business rules with conditions and actions +- Runtime constraints +- State transitions + +### Component Documentation + +- Component type and model binding +- Props with types and defaults +- Events with payloads +- Layout configuration +- Styling information + +### Orchestration Documentation + +- Node configuration +- Bindings to data paths +- Synchronization settings +- Health check configuration + +## Examples + +See the `examples/task-management-v2` directory for a comprehensive example demonstrating all Praxis 1.2.x features. + +## Migration from Basic Schemas + +To upgrade existing schemas to Praxis 1.2.x: + +1. Add field validation to models +2. Define constraints and indexes +3. Add relationships between models +4. Convert state transitions to rules with conditions +5. Add runtime constraints for validation +6. Enhance component definitions with props and events +7. Add orchestration configuration if needed + +## Version Compatibility + +- praxisdoc 2.0.x: Supports basic Praxis schemas +- praxisdoc 2.1.0+: Full Praxis 1.2.x support +- Backward compatible with basic schemas + +## Resources + +- [Praxis Documentation](https://github.com/plures/praxis) +- [Praxis 1.2.x Changelog](https://github.com/plures/praxis/blob/main/CHANGELOG.md) +- [Example Schemas](../examples/task-management-v2) diff --git a/examples/task-management-v2/.praxisDoc.json b/examples/task-management-v2/.praxisDoc.json new file mode 100644 index 0000000..610ef93 --- /dev/null +++ b/examples/task-management-v2/.praxisDoc.json @@ -0,0 +1,10 @@ +{ + "projectTitle": "Task Management System v2 (Praxis 1.2.x)", + "source": ".", + "target": "./docs", + "globs": ["**/*.schema.ts"], + "visualization": { + "format": "mermaid", + "exportPng": false + } +} diff --git a/examples/task-management-v2/README.md b/examples/task-management-v2/README.md new file mode 100644 index 0000000..ea62ade --- /dev/null +++ b/examples/task-management-v2/README.md @@ -0,0 +1,100 @@ +# Task Management v2 Example (Praxis 1.2.x) + +This example demonstrates the enhanced features of Praxis 1.2.x supported by praxisdoc. + +## New Features Demonstrated + +### Enhanced Model Definitions +- **Validation rules** on fields (required, min, max) +- **Constraints** at model level (unique, check, foreign keys) +- **Indexes** for optimized queries (btree, hash, fulltext) +- **Relationships** between models (one-to-one, one-to-many, many-to-many) +- **Optional fields** and default values + +### Enhanced Logic Definitions +- **Rules**: Business logic with conditions (when) and actions (then) +- **Constraints**: Runtime validation with custom error messages +- **Rule priorities**: Control execution order +- **Event triggers**: Rules that respond to specific events + +### Enhanced Component Definitions +- **Props**: Component properties with types and defaults +- **Events**: Component events with payload types +- **Layout**: Flexible layout configuration (stack, grid, flex) +- **Styling**: CSS classes, inline styles, and theme tokens + +### Orchestration Support +- **Node configurations**: Terminal, webhook, and custom nodes +- **Bindings**: Connect nodes to PluresDB paths +- **Synchronization**: State sync with conflict resolution +- **Health checks**: Monitoring configuration + +### Metadata +- Additional schema metadata for documentation and tracking + +## Files in This Example + +- `task-enhanced.schema.ts` - Praxis 1.2.x enhanced schema definition +- `.praxisDoc.json` - Configuration file for praxisdoc +- `docs/` - Generated documentation (created when you run praxisdoc) + +## Running the Example + +Install Deno if you haven't already, then from this directory: + +```bash +# Using Deno directly +deno run -A ../../cli.ts --config=.praxisDoc.json + +# Or if praxisdoc is installed globally +praxisdoc gen --config=.praxisDoc.json +``` + +## What Gets Generated + +After running praxisdoc, you'll find: + +- `docs/index.md` - Overview of all schemas +- `docs/schemas/taskmanagementv2/README.md` - Enhanced schema documentation with: + - Models with constraints, indexes, and relationships + - Components with props, events, layout, and styling + - Orchestration configuration +- `docs/schemas/taskmanagementv2/logic/task-state-machine.md` - Logic documentation with: + - Events and facts + - **NEW**: Business rules with conditions and actions + - **NEW**: Runtime constraints with validation + - State transitions +- `docs/schemas/taskmanagementv2/logic/task-state-machine.mmd` - Mermaid state diagram + +## Comparison with Basic Example + +The basic `task-management` example uses the simpler Praxis schema format, while this example demonstrates: + +1. **Richer Models**: Fields with validation, constraints, indexes, and relationships +2. **Business Rules**: Not just state transitions, but conditions and actions +3. **Runtime Constraints**: Validation that executes during state transitions +4. **Enhanced Components**: Full component definitions with props, events, and layout +5. **Orchestration**: Node configuration for distributed systems +6. **Metadata**: Additional schema information for tooling + +## Praxis 1.2.x Features Supported + +This example and the updated praxisdoc support: + +βœ… Enhanced model definitions (constraints, indexes, relationships) +βœ… Business rules in logic definitions +βœ… Runtime constraints with validation +βœ… Enhanced component definitions (props, events, layout, styling) +βœ… Orchestration configuration (nodes, sync, health) +βœ… Schema metadata +βœ… Field validation rules +βœ… Optional fields and defaults + +## Next Steps + +1. Explore the generated documentation in `docs/` +2. Modify the schema to add new features +3. Re-run praxisdoc to see updated documentation +4. Try creating your own Praxis 1.2.x schema + +For more information about Praxis features, see the [Praxis documentation](https://github.com/plures/praxis). diff --git a/examples/task-management-v2/task-enhanced.schema.ts b/examples/task-management-v2/task-enhanced.schema.ts new file mode 100644 index 0000000..c7e8797 --- /dev/null +++ b/examples/task-management-v2/task-enhanced.schema.ts @@ -0,0 +1,311 @@ +/** + * Example Praxis Schema - Task Management (Praxis 1.2.x) + * + * This demonstrates enhanced Praxis 1.2.x features including: + * - Enhanced model definitions with constraints, indexes, and relationships + * - Rules and constraints in logic definitions + * - Enhanced component definitions with props, events, layout, and styling + * - Orchestration configuration + */ + +export const taskSchemaV2 = { + version: '1.2.0', + name: 'TaskManagementV2', + description: 'Enhanced task management application with Praxis 1.2.x features', + + models: [ + { + name: 'Task', + description: 'A task item in the system', + fields: [ + { name: 'id', type: 'string', description: 'Unique identifier', optional: false }, + { name: 'title', type: 'string', description: 'Task title', validation: [{ type: 'required' }, { type: 'min', value: 3 }] }, + { name: 'description', type: 'string', description: 'Detailed description', optional: true }, + { name: 'status', type: 'string', default: 'new', description: 'Current status' }, + { name: 'assignee', type: 'string', description: 'Assigned user', optional: true }, + { name: 'priority', type: 'number', default: 0, description: 'Task priority (0-5)' }, + { name: 'createdAt', type: 'date', description: 'Creation timestamp' }, + { name: 'completedAt', type: 'date', description: 'Completion timestamp', optional: true }, + { name: 'tags', type: 'string[]', description: 'Task tags', optional: true }, + ], + constraints: [ + { + id: 'unique_title', + description: 'Task titles must be unique', + type: 'unique', + fields: ['title'] + }, + { + id: 'valid_priority', + description: 'Priority must be between 0 and 5', + type: 'check', + fields: ['priority'] + } + ], + indexes: [ + { name: 'by_status', fields: ['status'], type: 'btree' }, + { name: 'by_assignee', fields: ['assignee'], type: 'btree' }, + { name: 'by_created', fields: ['createdAt'], type: 'btree' }, + { name: 'by_priority', fields: ['priority', 'createdAt'], type: 'btree' }, + { name: 'search_title_desc', fields: ['title', 'description'], type: 'fulltext' }, + ], + relationships: [ + { + name: 'assignedTo', + type: 'one-to-one', + target: 'User', + foreignKey: 'assignee', + } + ] + }, + { + name: 'User', + description: 'User who can be assigned tasks', + fields: [ + { name: 'id', type: 'string', description: 'Unique identifier' }, + { name: 'name', type: 'string', description: 'User full name' }, + { name: 'email', type: 'string', description: 'User email' }, + { name: 'role', type: 'string', default: 'user', description: 'User role' }, + ], + constraints: [ + { + id: 'unique_email', + description: 'Email must be unique', + type: 'unique', + fields: ['email'] + } + ], + indexes: [ + { name: 'by_email', fields: ['email'], unique: true, type: 'hash' }, + ], + relationships: [ + { + name: 'tasks', + type: 'one-to-many', + target: 'Task', + foreignKey: 'assignee', + cascadeDelete: false + } + ] + }, + ], + + logic: [ + { + id: 'task-state-machine', + name: 'Task State Machine', + description: 'Manages task lifecycle from creation to completion with business rules', + + events: [ + { tag: 'TASK_CREATE', payload: { taskId: 'string', title: 'string', description: 'string', priority: 'number' }, description: 'Create a new task' }, + { tag: 'TASK_ASSIGN', payload: { taskId: 'string', assignee: 'string' }, description: 'Assign task to a user' }, + { tag: 'TASK_START', payload: { taskId: 'string' }, description: 'Start working on task' }, + { tag: 'TASK_PAUSE', payload: { taskId: 'string' }, description: 'Pause work on task' }, + { tag: 'TASK_COMPLETE', payload: { taskId: 'string' }, description: 'Mark task as completed' }, + { tag: 'TASK_REOPEN', payload: { taskId: 'string' }, description: 'Reopen a completed task' }, + { tag: 'TASK_CANCEL', payload: { taskId: 'string' }, description: 'Cancel a task' }, + { tag: 'TASK_SET_PRIORITY', payload: { taskId: 'string', priority: 'number' }, description: 'Update task priority' }, + ], + + facts: [ + { tag: 'TaskCreated', payload: { taskId: 'string', title: 'string', createdAt: 'date' }, description: 'Task was created' }, + { tag: 'TaskAssigned', payload: { taskId: 'string', assignee: 'string', assignedAt: 'date' }, description: 'Task was assigned' }, + { tag: 'TaskCompleted', payload: { taskId: 'string', completedAt: 'date' }, description: 'Task was completed' }, + { tag: 'TaskPriorityChanged', payload: { taskId: 'string', oldPriority: 'number', newPriority: 'number' }, description: 'Task priority was changed' }, + ], + + rules: [ + { + id: 'auto_assign_high_priority', + description: 'Automatically notify when high priority task is created', + on: ['TASK_CREATE'], + when: 'event.payload.priority >= 4', + then: 'emit("NOTIFY_ADMIN", { taskId: event.payload.taskId, reason: "high_priority" })', + priority: 1 + }, + { + id: 'validate_assignee', + description: 'Validate that assignee exists before assignment', + on: ['TASK_ASSIGN'], + when: 'state.users[event.payload.assignee] !== undefined', + then: 'transition("assigned")', + priority: 2 + }, + { + id: 'record_completion_time', + description: 'Record completion timestamp when task is completed', + on: ['TASK_COMPLETE'], + then: 'emit("TaskCompleted", { taskId: event.payload.taskId, completedAt: new Date() })', + priority: 1 + } + ], + + constraints: [ + { + id: 'no_complete_without_assignee', + description: 'Cannot complete a task without an assignee', + check: 'state.tasks[taskId].assignee !== null', + message: 'Task must be assigned before it can be completed' + }, + { + id: 'priority_in_range', + description: 'Priority must be between 0 and 5', + check: 'event.payload.priority >= 0 && event.payload.priority <= 5', + message: 'Task priority must be between 0 and 5' + } + ], + + transitions: [ + { from: 'new', event: 'TASK_ASSIGN', to: 'assigned', description: 'Assign a new task' }, + { from: 'assigned', event: 'TASK_START', to: 'in_progress', description: 'Start working on assigned task' }, + { from: 'in_progress', event: 'TASK_PAUSE', to: 'paused', description: 'Pause work' }, + { from: 'paused', event: 'TASK_START', to: 'in_progress', description: 'Resume work' }, + { from: 'in_progress', event: 'TASK_COMPLETE', to: 'completed', description: 'Complete the task' }, + { from: 'completed', event: 'TASK_REOPEN', to: 'assigned', description: 'Reopen completed task' }, + { from: 'new', event: 'TASK_CANCEL', to: 'cancelled', description: 'Cancel before assignment' }, + { from: 'assigned', event: 'TASK_CANCEL', to: 'cancelled', description: 'Cancel assigned task' }, + ], + }, + ], + + components: [ + { + name: 'TaskForm', + type: 'form', + model: 'Task', + description: 'Form for creating and editing tasks with validation', + props: [ + { name: 'taskId', type: 'string', required: false, description: 'Task ID for editing (empty for new task)' }, + { name: 'initialValues', type: 'object', required: false, description: 'Initial form values' }, + ], + events: [ + { name: 'submit', payload: 'Task', description: 'Fired when form is submitted' }, + { name: 'cancel', description: 'Fired when form is cancelled' }, + ], + layout: { + type: 'stack', + direction: 'vertical', + gap: 16, + padding: 24 + }, + styling: { + classes: ['task-form', 'elevated'], + theme: { + primary: 'blue', + spacing: 'comfortable' + } + } + }, + { + name: 'TaskList', + type: 'list', + model: 'Task', + description: 'List view of all tasks with filtering and sorting', + props: [ + { name: 'filterStatus', type: 'string', required: false, description: 'Filter by task status' }, + { name: 'sortBy', type: 'string', default: 'createdAt', description: 'Sort field' }, + { name: 'sortOrder', type: 'string', default: 'desc', description: 'Sort order (asc/desc)' }, + ], + events: [ + { name: 'taskSelected', payload: 'string', description: 'Fired when a task is selected' }, + { name: 'taskAction', payload: '{ action: string, taskId: string }', description: 'Fired when an action is performed on a task' }, + ], + layout: { + type: 'grid', + gap: 8 + } + }, + { + name: 'TaskDetail', + type: 'display', + model: 'Task', + description: 'Detailed view of a single task with actions', + props: [ + { name: 'taskId', type: 'string', required: true, description: 'ID of the task to display' }, + { name: 'editable', type: 'boolean', default: false, description: 'Whether the task can be edited' }, + ], + events: [ + { name: 'edit', description: 'Fired when edit button is clicked' }, + { name: 'delete', description: 'Fired when delete button is clicked' }, + { name: 'statusChange', payload: 'string', description: 'Fired when status is changed' }, + ], + layout: { + type: 'stack', + direction: 'vertical', + gap: 12 + } + }, + { + name: 'TaskBoard', + type: 'custom', + description: 'Kanban-style task board', + props: [ + { name: 'columns', type: 'string[]', required: true, description: 'Status columns to display' }, + { name: 'allowDragDrop', type: 'boolean', default: true, description: 'Enable drag and drop' }, + ], + events: [ + { name: 'taskMoved', payload: '{ taskId: string, from: string, to: string }', description: 'Fired when a task is moved between columns' }, + ], + layout: { + type: 'flex', + direction: 'horizontal', + gap: 16 + } + }, + ], + + orchestration: { + type: 'mcp', + nodes: [ + { + id: 'task_processor', + type: 'terminal', + config: { + command: 'node', + args: ['./scripts/process-tasks.js'] + }, + x: 100, + y: 100, + props: { + inputMode: 'text', + history: [], + lastOutput: null + }, + bindings: { + output: '/tasks/processing/output', + input: '/tasks/processing/input' + } + }, + { + id: 'notification_service', + type: 'webhook', + config: { + url: 'https://api.example.com/notifications', + method: 'POST' + }, + x: 300, + y: 100, + bindings: { + input: '/tasks/notifications/queue' + } + } + ], + sync: { + interval: 5000, + conflictResolution: 'last-write-wins', + targets: ['local', 'cloud'] + }, + health: { + interval: 30000, + endpoints: ['/health', '/ready'], + timeout: 5000 + } + }, + + metadata: { + author: 'Praxis Team', + version: '2.0.0', + tags: ['task-management', 'productivity', 'workflow'], + documentation: 'https://docs.example.com/task-management' + } +}; diff --git a/mod.ts b/mod.ts index 678bd80..c9e831f 100644 --- a/mod.ts +++ b/mod.ts @@ -17,3 +17,13 @@ export async function runOnce(cfg: PraxisDocConfig): Promise { const adapters = await loadAdapters(); return generateDocs(cfg, adapters); } + +// Re-export types from parser for external use +export type { + PraxisSchema, + PraxisModel, + PraxisLogic, + PraxisComponent, + PraxisOrchestration, +} from "./src/parser.ts"; + diff --git a/src/generate.ts b/src/generate.ts index e171ae9..834c894 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -18,8 +18,8 @@ export async function generateDocs(cfg: PraxisDocConfig, adapters: Adapters) { await adapters.fs.writeFile(adapters.join(cfg.target, "index.md"), indexMd); // Per-schema docs - const schemaTpl = cfg.templates?.schemaIndex ?? "# {{name}}\n\n{{desc}}\n\n## Models\n{{#each models}}\n### {{name}}\n{{desc}}\n\nFields:\n{{#each fields}}- **{{name}}** ({{type}}){{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/each}}\n\n## Logic Definitions\n{{#each logic}}- [{{name}}](./logic/{{slug}}.md) β€” {{desc}}\n{{/each}}\n"; - const logicTpl = cfg.templates?.logicPage ?? "# {{schema.name}} / {{name}}\n\n{{desc}}\n\n## Events\n{{#each events}}- **{{tag}}**{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n\n{{#if states}}## States\n{{#each states}}\n### {{name}}\n{{desc}}\n\nTransitions:\n{{#each on}}- {{event}} β†’ {{target}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/each}}\n{{/if}}\n{{#if facts}}## Facts\n{{#each facts}}- **{{tag}}**{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n{{#if transitions}}## State Transitions\n{{#each transitions}}- {{from}} --[{{event}}]--> {{to}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n"; + const schemaTpl = cfg.templates?.schemaIndex ?? "# {{name}}\n\n{{desc}}\n\n{{#if version}}**Version**: {{version}}\n\n{{/if}}## Models\n{{#each models}}\n### {{name}}\n{{desc}}\n\nFields:\n{{#each fields}}- **{{name}}** ({{type}}){{#if optional}} (optional){{/if}}{{#if description}} β€” {{description}}{{/if}}{{#if default}} β€” Default: `{{default}}`{{/if}}\n{{#if validation}} \n Validation: {{#each validation}}{{type}}{{#if value}}={{value}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}}\n{{/each}}\n{{#if constraints}}\n\nConstraints:\n{{#each constraints}}- **{{id}}**: {{description}} ({{type}} on {{#each fields}}{{this}}{{#unless @last}}, {{/unless}}{{/each}})\n{{/each}}\n{{/if}}\n{{#if indexes}}\n\nIndexes:\n{{#each indexes}}- **{{name}}**: {{#each fields}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} {{#if unique}}(unique){{/if}}{{#if type}} [{{type}}]{{/if}}\n{{/each}}\n{{/if}}\n{{#if relationships}}\n\nRelationships:\n{{#each relationships}}- **{{name}}** ({{type}}): β†’ {{target}}{{#if cascadeDelete}} (cascade delete){{/if}}\n{{/each}}\n{{/if}}\n{{/each}}\n\n{{#if components}}## Components\n{{#each components}}\n### {{name}} ({{type}})\n{{description}}\n{{#if model}}\n\n**Model**: {{model}}\n{{/if}}\n{{#if props}}\n\nProps:\n{{#each props}}- **{{name}}** ({{type}}){{#if required}} *required*{{/if}}{{#if description}} β€” {{description}}{{/if}}{{#if default}} β€” Default: `{{default}}`{{/if}}\n{{/each}}\n{{/if}}\n{{#if events}}\n\nEvents:\n{{#each events}}- **{{name}}**{{#if payload}} ({{payload}}){{/if}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n{{/each}}\n{{/if}}\n\n{{#if orchestration}}## Orchestration\n\n**Type**: {{orchestration.type}}\n\n{{#if orchestration.nodes}}### Nodes\n{{#each orchestration.nodes}}- **{{id}}** ({{type}})\n{{/each}}\n{{/if}}\n{{#if orchestration.sync}}\n\n### Synchronization\n- Interval: {{orchestration.sync.interval}}ms\n- Strategy: {{orchestration.sync.conflictResolution}}\n{{/if}}\n{{/if}}\n\n## Logic Definitions\n{{#each logic}}- [{{name}}](./logic/{{slug}}.md) β€” {{desc}}\n{{/each}}\n"; + const logicTpl = cfg.templates?.logicPage ?? "# {{schema.name}} / {{name}}\n\n{{desc}}\n\n## Events\n{{#each events}}- **{{tag}}**{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n\n{{#if states}}## States\n{{#each states}}\n### {{name}}\n{{desc}}\n\nTransitions:\n{{#each on}}- {{event}} β†’ {{target}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/each}}\n{{/if}}\n{{#if facts}}## Facts\n{{#each facts}}- **{{tag}}**{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n{{#if rules}}## Rules\n{{#each rules}}\n### {{id}}\n{{description}}\n\n{{#if on}}\n**Triggers**: {{#each on}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}\n{{/if}}\n{{#if when}}\n**Condition**: `{{when}}`\n{{/if}}\n**Action**: `{{then}}`\n{{#if priority}}\n**Priority**: {{priority}}\n{{/if}}\n{{/each}}\n{{/if}}\n{{#if constraints}}## Constraints\n{{#each constraints}}\n### {{id}}\n{{description}}\n\n**Check**: `{{check}}`\n\n**Error**: {{message}}\n{{/each}}\n{{/if}}\n{{#if transitions}}## State Transitions\n{{#each transitions}}- {{from}} --[{{event}}]--> {{to}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n"; for (const schema of schemas) { const sdir = adapters.join(cfg.target, "schemas", schema.slug); diff --git a/src/parser.ts b/src/parser.ts index 635915e..cd5b893 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -2,11 +2,14 @@ import type { Adapters } from "../runtime.ts"; import type { PraxisDocConfig } from "../mod.ts"; import { slugify } from "./utils.ts"; -// Praxis Schema Types +// Praxis Schema Types (aligned with Praxis 1.2.x) export type PraxisModel = { name: string; desc: string; - fields?: Array<{ name: string; type: string; description?: string; default?: any }>; + fields?: Array<{ name: string; type: string | Record; description?: string; default?: any; optional?: boolean; validation?: any[] }>; + constraints?: Array<{ id: string; description: string; type: string; fields: string[] }>; + indexes?: Array<{ name: string; fields: string[]; unique?: boolean; type?: string }>; + relationships?: Array<{ name: string; type: string; target: string; foreignKey?: string; cascadeDelete?: boolean }>; }; export type PraxisLogic = { @@ -16,10 +19,30 @@ export type PraxisLogic = { slug: string; events: Array<{ tag: string; payload?: Record; description?: string }>; facts?: Array<{ tag: string; payload?: Record; description?: string }>; + rules?: Array<{ id: string; description: string; on?: string[]; when?: string; then: string; priority?: number }>; + constraints?: Array<{ id: string; description: string; check: string; message: string }>; transitions?: Array<{ from: string; event: string; to: string; description?: string }>; states?: Array<{ name: string; desc: string; slug: string; on: Array<{ event: string; target: string; description?: string }> }>; }; +export type PraxisComponent = { + name: string; + type: string; + model?: string; + description?: string; + props?: Array<{ name: string; type: string; required?: boolean; default?: any; description?: string }>; + events?: Array<{ name: string; payload?: string; description?: string }>; + layout?: { type: string; direction?: string; gap?: number; padding?: number; alignment?: string }; + styling?: { classes?: string[]; styles?: Record; theme?: Record }; +}; + +export type PraxisOrchestration = { + type: string; + nodes?: Array<{ id: string; type: string; config: Record; x?: number; y?: number; props?: Record; bindings?: Record }>; + sync?: { interval: number; conflictResolution: string; targets: string[] }; + health?: { interval: number; endpoints: string[]; timeout: number }; +}; + export type PraxisSchema = { version?: string; name: string; @@ -27,30 +50,35 @@ export type PraxisSchema = { slug: string; models?: PraxisModel[]; logic: PraxisLogic[]; - components?: Array<{ name: string; type: string; model?: string; description?: string }>; + components?: PraxisComponent[]; + orchestration?: PraxisOrchestration; + metadata?: Record; }; /** - * Parse a Praxis schema object extracted from source code + * Parse a Praxis schema object extracted from source code (Praxis 1.2.x format) */ function parsePraxisSchema(schemaObj: any, varName: string): PraxisSchema { const name = schemaObj.name || varName; const slug = slugify(name); const desc = schemaObj.description || `Praxis application schema for ${name}`; - // Parse models + // Parse models (enhanced for Praxis 1.2.x) const models: PraxisModel[] = []; if (schemaObj.models && Array.isArray(schemaObj.models)) { for (const model of schemaObj.models) { models.push({ name: model.name || 'Unknown', desc: model.description || `Model for ${model.name || 'Unknown'}`, - fields: model.fields || [] + fields: model.fields || [], + constraints: model.constraints, + indexes: model.indexes, + relationships: model.relationships }); } } - // Parse logic definitions (facts, events, rules, transitions) + // Parse logic definitions (facts, events, rules, constraints, transitions) const logic: PraxisLogic[] = []; if (schemaObj.logic && Array.isArray(schemaObj.logic)) { for (const logicDef of schemaObj.logic) { @@ -72,6 +100,24 @@ function parsePraxisSchema(schemaObj: any, varName: string): PraxisSchema { description: fact.description || fact.desc })); + // Extract rules (new in Praxis 1.2.x) + const rules = (logicDef.rules || []).map((rule: any) => ({ + id: rule.id, + description: rule.description, + on: rule.on, + when: rule.when, + then: rule.then, + priority: rule.priority + })); + + // Extract constraints (new in Praxis 1.2.x) + const constraints = (logicDef.constraints || []).map((constraint: any) => ({ + id: constraint.id, + description: constraint.description, + check: constraint.check, + message: constraint.message + })); + // Extract transitions (if defined) const transitions = (logicDef.transitions || []).map((trans: any) => ({ from: trans.from || '', @@ -113,12 +159,31 @@ function parsePraxisSchema(schemaObj: any, varName: string): PraxisSchema { slug: slugify(logicId), events, facts, + rules: rules.length > 0 ? rules : undefined, + constraints: constraints.length > 0 ? constraints : undefined, transitions, states: states.length > 0 ? states : undefined }); } } + // Parse components (enhanced for Praxis 1.2.x) + const components: PraxisComponent[] = []; + if (schemaObj.components && Array.isArray(schemaObj.components)) { + for (const comp of schemaObj.components) { + components.push({ + name: comp.name, + type: comp.type, + model: comp.model, + description: comp.description, + props: comp.props, + events: comp.events, + layout: comp.layout, + styling: comp.styling + }); + } + } + return { version: schemaObj.version, name, @@ -126,7 +191,9 @@ function parsePraxisSchema(schemaObj: any, varName: string): PraxisSchema { slug, models, logic, - components: schemaObj.components + components: components.length > 0 ? components : undefined, + orchestration: schemaObj.orchestration, + metadata: schemaObj.metadata }; }