Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

2560
docs/CLAUDE.dist.md Executable file

File diff suppressed because it is too large Load Diff

58
docs/README.md Executable file
View File

@@ -0,0 +1,58 @@
# RSpade Framework Documentation
## For Developers Using RSpade
Welcome to the RSpade framework documentation. RSpade is a Visual Basic-like development environment for PHP/Laravel that prioritizes simplicity, straightforwardness, and rapid development.
### Getting Started
1. **LLM Integration**: Point your AI assistant to `CLAUDE.dist.md` for comprehensive development directives
2. **Architecture**: Read `rsx-architecture.md` for framework overview and core concepts
3. **Coding Standards**: See `coding-conventions.md` for naming conventions and style guide
4. **Framework Updates**: Follow `framework_upstream.md` to sync with upstream framework updates
### Documentation Files
#### Core Documentation
- **`CLAUDE.dist.md`** - Comprehensive AI/LLM development guide (primary reference)
- **`rsx-architecture.md`** - Architecture overview and core philosophy
- **`rsx-dispatch-system.md`** - Routing and request handling system
- **`coding-conventions.md`** - Code style, naming standards, and conventions
#### Framework Behavior
- **`framework_divergences.md`** - How RSpade differs from standard Laravel
- **`framework_upstream.md`** - Keeping your project updated with framework changes
#### Advanced Topics
- **`llm_migration_system.md`** - Snapshot-based migration system for AI-friendly development
#### External Libraries
- **`jqhtml_quickstart.md`** - JQHTML component system build process
- **`jqhtml_migration_report.md`** - JQHTML NPM package migration notes
- **`jqhtml_vscode_extension_setup.md`** - VS Code extension setup for JQHTML
### Quick Reference
**Common Commands:**
```bash
php artisan rsx:check # Check code quality
php artisan rsx:clean # Clear caches (troubleshooting)
php artisan rsx:debug /route # Test routes with headless browser
php artisan rsx:man <topic> # View detailed documentation
```
**Project Structure:**
```
/rsx/ # Your application code (RSX)
├── app/ # Application modules
├── models/ # Database models
├── theme/ # Global theme assets
├── main.php # App-wide middleware
└── permission.php # Authorization methods
/app/RSpade/ # Framework runtime (do not modify)
```
### Support
For issues or questions about the RSpade framework, refer to the comprehensive `CLAUDE.dist.md` documentation or consult the specific topic files listed above.

556
docs/coding-conventions.md Executable file
View File

@@ -0,0 +1,556 @@
# Coding Conventions
This document outlines the coding standards and conventions used in RSpade and RSX. Following these conventions ensures consistency across the codebase and compatibility with the framework.
## PHP Conventions
### Naming Conventions
**CRITICAL**: This project uses **underscore_case** for all variables and functions, NOT camelCase.
```php
// CORRECT - Use underscore_case
$user_name = 'John';
$is_active = true;
function get_user_profile() { }
function calculate_total_price() { }
// WRONG - Do not use camelCase
$userName = 'John'; // ❌
$isActive = true; // ❌
function getUserProfile() { } // ❌
function calculateTotalPrice() { } // ❌
```
### Class and File Naming
- **Classes**: Use PascalCase
- **Files**: Match class name exactly with `.php` extension
- **Constants**: Use UPPERCASE_WITH_UNDERSCORES
```php
// Class naming
class UserController { } // ✓ PascalCase
class BlogPostManager { } // ✓ PascalCase
class Rsx_Controller { } // ✓ Legacy RSX base classes use underscore
// Constants
const MAX_ATTEMPTS = 5; // ✓
const DEFAULT_CACHE_TTL = 3600; // ✓
```
### Database Conventions
- **Tables**: Use plural snake_case (e.g., `users`, `blog_posts`)
- **Columns**: Use snake_case (e.g., `created_at`, `user_id`)
- **Foreign Keys**: Use `{singular_table}_id` (e.g., `user_id`, `post_id`)
```php
// Model with database fields
class BlogPost extends Model
{
protected $table = 'blog_posts'; // plural snake_case
protected $fillable = [
'title',
'content',
'user_id', // foreign key
'published_at', // snake_case
'view_count' // snake_case
];
}
```
### Code Style
RSpade follows [PSR-12](https://www.php-fig.org/psr/psr-12/) with these specific requirements:
```php
namespace App\Models;
use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
/**
* Blog post model
*
* @property int $id
* @property string $title
* @property string $content
* @property int $user_id
* @property Carbon $created_at
*/
class BlogPost extends Rsx_Model_Abstract
{
protected $fillable = [
'title',
'content',
'user_id',
'status'
];
/**
* Get the author of the post
*
* @return BelongsTo
*/
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* Mark post as published
*
* @return bool
*/
public function mark_as_published() // underscore_case method
{
$this->status = 'published';
$this->published_at = now();
return $this->save();
}
/**
* Get formatted title
*
* @return string
*/
public function get_formatted_title() // underscore_case method
{
$max_length = 50;
$title_text = $this->title;
if (strlen($title_text) > $max_length) {
return substr($title_text, 0, $max_length) . '...';
}
return $title_text;
}
}
```
## RSX Attribute Conventions
### Attribute Usage
Use PHP 8 attributes for routing, caching, and other decorators:
```php
use App\RSpade\Core\Attributes\{Route, Cache, RateLimit, Middleware};
class UserController extends Rsx_Controller
{
#[Route('/users', methods: ['GET'])]
#[Cache(ttl: 300, tags: ['users'])]
#[RateLimit(max_attempts: 60)]
public function list_users($params)
{
$page_number = $params['page'] ?? 1;
$items_per_page = 20;
return $this->paginate_results($page_number, $items_per_page);
}
#[Route('/users/:id', methods: ['GET'])]
#[Middleware(['auth'])]
public function get_user($params)
{
$user_id = $params['id'];
$include_posts = $params['include_posts'] ?? false;
return $this->load_user_data($user_id, $include_posts);
}
}
```
### Attribute Placement
- **Class-level**: Applies to all methods in the class
- **Method-level**: Applies to specific method only
- **Multiple attributes**: Stack vertically for readability
```php
#[Cors(allowed_origins: ['https://app.example.com'])] // Class-level
#[ApiVersion('v2')] // Class-level
class ApiController extends Rsx_Api
{
#[Route('/api/data', methods: ['GET'])] // Method-level
#[Cache(ttl: 600)] // Method-level
#[RateLimit(max_attempts: 100)] // Method-level
public function get_data($params)
{
// Implementation
}
}
```
## JavaScript Conventions
### ES6+ Standards
Use modern JavaScript with ES6+ features:
```javascript
// Use const/let, arrow functions, destructuring
const process_user_data = (user_data) => {
const { first_name, last_name, email } = user_data;
const full_name = `${first_name} ${last_name}`;
return {
full_name,
email,
display_name: full_name.toLowerCase()
};
};
// Class with static methods for namespacing
class UserManager {
static async load_user(user_id) {
const response = await fetch(`/api/users/${user_id}`);
const user_data = await response.json();
return user_data;
}
static format_user_name(first_name, last_name) {
return `${first_name} ${last_name}`.trim();
}
}
```
### jQuery Usage
Use jQuery for DOM manipulation where appropriate:
```javascript
$(document).ready(() => {
// Use underscore_case for functions and variables
const init_user_form = () => {
const $form = $('#user_form');
const $submit_button = $form.find('.submit_button');
$submit_button.on('click', async (e) => {
e.preventDefault();
const form_data = get_form_data($form);
await submit_form_data(form_data);
});
};
const get_form_data = ($form) => {
const form_values = {};
$form.find('input, select, textarea').each(function() {
const field_name = $(this).attr('name');
const field_value = $(this).val();
form_values[field_name] = field_value;
});
return form_values;
};
init_user_form();
});
```
## SCSS/CSS Conventions
### File Organization
```scss
// resources/sass/app.scss
@import 'variables'; // Custom variables
@import 'bootstrap'; // Bootstrap framework
@import 'layout'; // Layout styles
@import 'components'; // Component styles
@import 'pages'; // Page-specific styles
```
### Naming Conventions
Use BEM-style naming with underscores:
```scss
// Component naming
.user_card {
padding: 1rem;
border: 1px solid $border_color;
&__header {
font-size: 1.2rem;
margin_bottom: 0.5rem;
}
&__content {
color: $text_color;
}
&--featured {
border_color: $primary_color;
background: $featured_background;
}
}
// Page-specific styles
.page_is_user_profile {
.profile_section {
margin_bottom: 2rem;
}
.profile_stats {
display: flex;
gap: 1rem;
}
}
```
## Database Migration Conventions
### Naming
Use descriptive names with timestamps:
```php
// Good migration names
2024_01_15_000001_create_users_table.php
2024_01_15_000002_add_avatar_to_users_table.php
2024_01_15_000003_create_blog_posts_table.php
```
### Structure
```php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBlogPostsTable extends Migration
{
public function up()
{
Schema::create('blog_posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->unsignedBigInteger('user_id');
$table->enum('status', ['draft', 'published', 'archived']);
$table->datetime('published_at')->nullable();
// Standard tracking columns
$table->unsignedBigInteger('created_by')->nullable();
$table->unsignedBigInteger('updated_by')->nullable();
$table->unsignedBigInteger('deleted_by')->nullable();
$table->timestamps();
$table->softDeletes();
// Indexes
$table->index('user_id');
$table->index('status');
$table->index('published_at');
// Foreign keys
$table->foreign('user_id')->references('id')->on('users');
});
}
public function down()
{
Schema::dropIfExists('blog_posts');
}
}
```
## Testing Conventions
### Test Naming
```php
namespace Tests\Unit;
use Tests\TestCase;
class UserServiceTest extends TestCase
{
// Use descriptive test names with underscores
public function test_user_can_be_created()
{
$user_data = [
'name' => 'John Doe',
'email' => 'john@example.com'
];
$user = $this->create_test_user($user_data);
$this->assertNotNull($user->id);
$this->assertEquals('John Doe', $user->name);
}
public function test_user_email_must_be_unique()
{
$existing_user = User::factory()->create([
'email' => 'test@example.com'
]);
$this->expectException(ValidationException::class);
$this->create_user_with_email('test@example.com');
}
}
```
## Documentation Conventions
### PHPDoc Comments
```php
/**
* Process user registration
*
* Handles the complete user registration flow including validation,
* user creation, email verification, and initial setup.
*
* @param array $user_data User registration data
* @param bool $send_email Whether to send welcome email
* @return User The created user instance
* @throws ValidationException If validation fails
* @throws RegistrationException If registration cannot be completed
*/
public function process_registration(array $user_data, bool $send_email = true): User
{
// Implementation
}
```
### Inline Comments
Use inline comments sparingly for complex logic:
```php
public function calculate_discount($order_total, $user)
{
$discount_amount = 0;
// Apply loyalty discount for users with 10+ orders
if ($user->order_count >= 10) {
$discount_amount += $order_total * 0.1;
}
// Apply seasonal discount during December
if (date('n') == 12) {
$discount_amount += $order_total * 0.05;
}
// Cap discount at 25% of total
$max_discount = $order_total * 0.25;
return min($discount_amount, $max_discount);
}
```
## Git Commit Conventions
### Commit Message Format
```
<type>: <subject>
<body>
<footer>
```
### Types
- **feat**: New feature
- **fix**: Bug fix
- **docs**: Documentation changes
- **style**: Code style changes (formatting, missing semicolons, etc.)
- **refactor**: Code refactoring
- **test**: Test additions or changes
- **chore**: Maintenance tasks
### Examples
```
feat: Add user profile image upload
Implemented file upload functionality for user avatars with
automatic resizing and format conversion to WebP.
Closes #123
```
```
fix: Correct discount calculation for bulk orders
The previous calculation was not applying the bulk discount
correctly when orders exceeded 100 items.
```
## Environment Configuration
### Environment Variables
Use descriptive names in SCREAMING_SNAKE_CASE:
```env
# Application
APP_NAME="RSpade"
APP_ENV=local
APP_DEBUG=true
APP_URL=https://rspade.example.com
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=rspade
DB_USERNAME=root
DB_PASSWORD=secret
# Cache
CACHE_DRIVER=redis
CACHE_PREFIX=rspade_cache
# RSX Framework
RSX_CACHE_ENABLED=true
RSX_CACHE_TTL=3600
RSX_AUTO_BUILD=true
```
## Error Handling
### Exception Messages
Use clear, actionable error messages:
```php
public function process_payment($payment_data)
{
if (!isset($payment_data['amount'])) {
throw new PaymentException(
'Payment amount is required. Please provide a valid amount in the payment_data array.'
);
}
if ($payment_data['amount'] <= 0) {
throw new PaymentException(
sprintf(
'Payment amount must be greater than zero. Received: %s',
$payment_data['amount']
)
);
}
// Process payment...
}
```
## Summary
Key points to remember:
1. **Always use underscore_case** for variables and functions
2. **Use PascalCase** for class names
3. **Use SCREAMING_SNAKE_CASE** for constants
4. **Follow PSR-12** for general PHP style
5. **Use PHP 8 attributes** for RSX features
6. **Document complex logic** with clear comments
7. **Write descriptive test names** with underscores
8. **Keep code readable** over clever
9. **Be consistent** throughout the codebase

128
docs/framework_divergences.md Executable file
View File

@@ -0,0 +1,128 @@
# RSpade Framework Divergences from Laravel
This file documents all intentional divergences from standard Laravel behavior. These are framework-level changes that alter how developers interact with common Laravel features.
**Note**: This log tracks behavioral changes to existing Laravel functionality, not new architectural additions like RSX. A divergence is when we take something Laravel already does and make it work differently.
## Database & Models
### Mass Assignment Prevention
- **Change**: All mass assignment methods throw `MassAssignmentException`
- **Affected**: `Model::create()`, `fill()`, `forceFill()`, `update()`, `firstOrCreate()`, `firstOrNew()`, `updateOrCreate()`
- **Reason**: Security and code clarity - forces explicit field assignment
- **Implementation**: `Model_Abstract` base class overrides these methods
- **Date**: 2025-08-12
### Eager Loading Prevention
- **Change**: ALL eager loading methods throw `RuntimeException`
- **Affected**: `with()`, `load()`, `loadMissing()`, `loadCount()`, `loadMorph()`, `loadAggregate()`, `loadMax()`, `loadMin()`, `loadSum()`, `loadAvg()`, `loadExists()`, `withCount()`, `withMax()`, `withMin()`, `withSum()`, `withAvg()`, `withExists()`, `withAggregate()`
- **Reason**: Enforce explicit queries for relationships, prevent N+1 optimization patterns that complicate code
- **Implementation**: `Model_Abstract` overrides instance methods, `RestrictedEloquentBuilder` blocks query methods, `RestrictedEloquentCollection` blocks collection methods
- **Date**: 2025-08-19
## Routing
### Route Parameter Syntax
- **Change**: Route parameters use `:param` syntax instead of Laravel's `{param}`
- **Affected**: All route definitions in #[Route] attributes
- **Reason**: Clearer syntax, prevents confusion with Blade syntax
- **Implementation**: RouteResolver validates and rejects old {param} syntax with fatal errors
- **Date**: 2025-09-07
### Resource Routes Prevention
- **Change**: Resource route methods throw `RuntimeException`
- **Affected**: `Route::resource()`, `Route::apiResource()`, `Route::resources()`, `Route::apiResources()`, `Route::singleton()`, `Route::singletons()`
- **Reason**: Enforce explicit route definitions, prevent REST pattern assumptions
- **Implementation**: `RestrictedRouter` class overrides these methods
- **Date**: 2025-08-20
### HTTP Method Restrictions
- **Change**: Non-GET/POST HTTP methods throw `RuntimeException`
- **Affected**: `Route::put()`, `Route::patch()`, `Route::delete()`, `Route::options()`, `Route::any()`
- **Reason**: Simplify HTTP semantics, use POST for all data modifications
- **Implementation**: `RestrictedRouter` class blocks these methods, `addRoute()` validates allowed methods
- **Date**: 2025-08-20
### Forward-Only Migrations
- **Change**: Migration stubs no longer include `down()` methods
- **Affected**: All generated migrations via `make:migration`
- **Reason**: Prevent data loss, enforce forward-only database evolution
- **Implementation**: Customized stubs in `/stubs/migration*.stub`
- **Notice**: Warning displayed after migration creation
- **Date**: 2025-08-12
### Restricted Database Commands
- **Change**: Several database commands are disabled with professional error messages
- **Affected**: `db:wipe`, `migrate:fresh`, `migrate:reset`, `migrate:refresh`, `migrate:rollback`
- **Reason**: Prevent accidental data loss in production
- **Implementation**: `RestrictedDatabaseCommand` base class
- **Date**: 2025-08-06
## Naming Conventions
### Underscore Case Enforcement
- **Change**: All custom methods must use underscore_case (not camelCase)
- **Affected**: Controllers, Models, Services - any custom PHP methods
- **Reason**: Consistency and readability
- **Implementation**: `rsx:check` command checks and reports violations
- **Note**: Framework methods keep original names (e.g., `toArray`, `broadcastOn`)
- **Date**: 2025-08-12
## Git Operations
### Force Full Staging
- **Change**: All git operations must use `git add -A` (full staging)
- **Affected**: Git workflow
- **Reason**: Prevent partial commits and ensure complete change tracking
- **Implementation**: Documented directive, not enforced in code
- **Date**: 2025-05-15
## Session & Authentication
### Extended Session Lifetime
- **Change**: Sessions last 365 days instead of Laravel's default 120 minutes
- **Affected**: User sessions
- **Reason**: Better user experience for long-term applications
- **Implementation**: `config/session.php` configuration
- **Date**: 2025-05-15
## Code Organization
### No $fillable or $guarded Properties
- **Change**: Models should not define `$fillable` or `$guarded` arrays
- **Affected**: All Eloquent models
- **Reason**: Mass assignment is prohibited, making these properties unnecessary
- **Implementation**: `rsx:check` warns when these properties are found
- **Date**: 2025-08-12
## Development Workflow
### No Service Restart for Code Changes
- **Change**: Never restart PHP-FPM or Nginx after code changes
- **Affected**: Development and deployment workflow
- **Reason**: OPcache is disabled, changes take effect immediately
- **Implementation**: Environment configuration
- **Date**: 2025-05-15
### Snapshot-Protected Migrations (Development)
- **Change**: Migrations require database snapshot in development environments
- **Affected**: `php artisan migrate` command in development
- **Reason**: Prevents partial migrations from corrupting database, LLM-friendly error recovery
- **Implementation**: Enhanced `Maint_Migrate` command with snapshot commands
- **Commands**: `migrate:begin`, `migrate:commit`, `migrate:rollback`, `migrate:status`
- **Bypass**: Use `--production` flag to skip snapshot requirement
- **Note**: Only enforced in development with Docker, production runs normally
- **Date**: 2025-08-13
## Error Handling
### Educational Exception Messages
- **Change**: Custom exceptions include detailed explanations and correct code examples
- **Affected**: All custom exception classes
- **Reason**: Help developers learn framework patterns quickly
- **Implementation**: `MassAssignmentException` and similar custom exceptions
- **Date**: 2025-08-12
---
*When adding entries, include: Change description, Affected areas, Reason, Implementation method, and Date*

198
docs/framework_upstream.md Executable file
View File

@@ -0,0 +1,198 @@
# Framework Upstream Synchronization
This document explains how to keep your RSpade project synchronized with framework updates.
## Overview
When you create a new project from the RSpade framework template, you get your own Git repository for your project-specific code. However, you'll also want to pull in framework updates and improvements over time. This is managed through Git's upstream remote functionality.
## Initial Setup
When starting a new project from this template:
1. **Clone/fork this template** to create your project repository
2. **Change the origin** to your project's repository:
```bash
git remote set-url origin git@your-server:your-project.git
```
3. **Setup framework upstream**:
```bash
bin/framework-upstream setup
```
This configures `ssh://git@192.168.0.3:3322/brianhansonxyz/rspade-publish.git` as the `framework-upstream` remote.
## Workflow
### Check for Updates
Check if framework updates are available:
```bash
bin/framework-upstream status
```
This shows:
- Whether you're up to date with the framework
- How many commits ahead/behind you are
- If your project has diverged from the framework
### Pull Framework Updates
To merge framework updates into your project:
```bash
bin/framework-upstream pull
```
This will:
1. Fetch the latest framework changes
2. Attempt to merge them into your current branch
3. Report any conflicts that need manual resolution
### View Framework Changes
See recent framework commits:
```bash
bin/framework-upstream logs
```
View differences between your project and the framework:
```bash
bin/framework-upstream diff
```
## Handling Conflicts
When pulling framework updates, you may encounter merge conflicts if you've modified framework files. This is normal and expected.
### Common Conflict Areas
- `CLAUDE.md` - If you've customized AI directives
- `config/` files - If you've modified framework configuration
- `.gitignore` - If you've added project-specific ignores
### Resolution Strategy
1. **Review the conflicts** carefully
2. **Keep your project-specific changes** where appropriate
3. **Accept framework improvements** where they don't conflict with your customizations
4. **Test thoroughly** after resolving conflicts
### Conflict Commands
During a merge conflict:
```bash
# See which files have conflicts
git status
# After resolving conflicts in a file
git add <resolved-file>
# Complete the merge
git commit
# Or abort if needed
git merge --abort
```
## Best Practices
### 1. Minimize Framework Modifications
- Work primarily in `/rsx/` for your application code
- Avoid modifying `/app/RSpade/` framework files
- Use configuration files for customization when possible
### 2. Regular Updates
- Check for updates weekly: `bin/framework-upstream status`
- Pull updates regularly to avoid large divergences
- Test thoroughly after each update
### 3. Document Customizations
If you must modify framework files:
- Document the changes in your project README
- Consider if the change should be contributed back to the framework
- Keep modifications minimal and well-commented
### 4. Commit Before Updating
Always commit your changes before pulling framework updates:
```bash
git add -A
git commit -m "Save work before framework update"
bin/framework-upstream pull
```
## Framework Update Types
### Patch Updates
- Bug fixes
- Security patches
- Documentation improvements
- Usually safe to pull immediately
### Feature Updates
- New framework capabilities
- New helper functions
- Enhanced commands
- Review changes before pulling
### Breaking Changes
- Rare but possible
- Will be documented in framework commits
- May require code changes in your project
## Troubleshooting
### "Not in a git repository"
Ensure you're in your project root directory
### "Upstream not configured"
Run: `bin/framework-upstream setup`
### "You have uncommitted changes"
Commit or stash your changes before pulling:
```bash
git add -A && git commit -m "WIP"
# or
git stash
```
### Merge conflicts persist
Consider:
1. Understanding what changed in the framework
2. Reviewing your local modifications
3. Potentially refactoring to reduce conflicts
## Manual Git Commands
The `bin/framework-upstream` script automates these Git commands:
```bash
# Add upstream remote manually
git remote add framework-upstream ssh://git@192.168.0.3:3322/brianhansonxyz/rspade-publish.git
# Fetch updates
git fetch framework-upstream
# Merge updates
git merge framework-upstream/master
# Check status
git log HEAD..framework-upstream/master --oneline
```
## Contributing Back
If you make improvements that would benefit all RSpade projects:
1. Consider contributing them back to the framework
2. Create a clean branch with just the framework improvements
3. Submit a pull request to the framework repository
## Support
For framework update issues:
- Check the framework repository for announcements
- Review recent commits for breaking changes
- Test in a development environment first

102
docs/jqhtml_migration_report.md Executable file
View File

@@ -0,0 +1,102 @@
# JQHTML Migration Report: Git Submodule to NPM Packages
## Migration Completed Successfully
### What Changed
#### 1. **Package Management**
- **Before**: JQHTML was managed as a git submodule at `internal-libs/jqhtml`
- **After**: JQHTML is now managed via NPM packages from private Verdaccio registry
#### 2. **NPM Packages Installed**
- `@jqhtml/core` - Core runtime library
- `@jqhtml/parser` - Template parser/compiler
- `@jqhtml/router` - SPA router functionality
- `@jqhtml/webpack-loader` - Webpack integration (for future use)
#### 3. **Path Updates**
All references have been updated from submodule paths to npm package paths:
| Component | Old Path | New Path |
|-----------|----------|----------|
| Runtime Bundle | `internal-libs/jqhtml/packages/core/dist/jqhtml-bundle.js` | `node_modules/@jqhtml/core/dist/jqhtml-core.esm.js` |
| Parser CLI | `internal-libs/jqhtml/compile-cli.js` | `bin/jqhtml-compile` (wrapper) |
| Router | `internal-libs/jqhtml/packages/router/dist/router-bundle.js` | `node_modules/@jqhtml/router/dist/index.js` |
#### 4. **Files Modified**
- `/app/RSpade/Services/JqhtmlCompiler.php` - Updated parser path
- `/app/RSpade/Integrations/Jqhtml/JqhtmlBundleModule.php` - Updated runtime paths
- `/app/RSpade/Integrations/Jqhtml/JqhtmlIntegration.php` - Updated config paths
- `/app/RSpade/Modules/External/JqhtmlModule.php` - Updated runtime path
- `/app/RSpade/Modules/External/JqhtmlSpaModule.php` - Updated router path
#### 5. **Files Created**
- `/.npmrc` - NPM registry configuration for @jqhtml scope
- `/bin/jqhtml-compile` - CLI wrapper for @jqhtml/parser
#### 6. **Files Removed**
- `/internal-libs/jqhtml/` - Entire git submodule removed
- `.gitmodules` entry for jqhtml removed
### Critical Changes Made
#### Removed ALL Fallback Code
Per project requirements, all fallback logic was removed. The system now fails loudly if required files are missing:
- No fallback file paths
- No alternative locations
- Throws `RuntimeException` if npm packages not found
- Clear error messages directing to run `npm install`
### Authentication Configuration
NPM is configured to authenticate with the private Verdaccio registry:
```
Registry: https://privatenpm.hanson.xyz/
Username: rspade
Auth Token: [configured in .npmrc]
```
### Testing Completed
**Manifest Build**: Successfully builds with new paths
**Bundle Compilation**: FrontendBundle compiles with JQHTML integration
**No Legacy References**: All `internal-libs/jqhtml` references removed (except historical docs)
### Next Steps for Webpack Integration
The `@jqhtml/webpack-loader` package is installed but not yet integrated. When ready to implement:
1. Configure webpack to use the loader for .jqhtml files
2. Remove the PHP-based compilation wrapper
3. Build-time compilation instead of runtime
### Migration Commands Used
```bash
# Install packages
npm install
# Remove git submodule
git submodule deinit -f internal-libs/jqhtml
rm -rf internal-libs/jqhtml
git rm internal-libs/jqhtml
# Test integration
php artisan rsx:manifest:build
php artisan rsx:bundle:compile FrontendBundle
```
### Impact
- **No breaking changes** to existing functionality
- **Improved dependency management** via npm versioning
- **Easier updates** via `npm update @jqhtml/*`
- **Version locking** via package-lock.json
- **No more submodule sync issues**
### Verification
The integration has been tested and verified working with:
- RSX manifest system recognizing JQHTML files
- Bundle compiler including JQHTML runtime
- All paths correctly updated
- System fails loud if packages missing (no silent fallbacks)

734
docs/jqhtml_quickstart.md Executable file
View File

@@ -0,0 +1,734 @@
# JQHTML Webpack Build Process - Quick Start Guide
## Overview
This document provides a comprehensive overview of how JQHTML's build tools work in an npm Express project, with contextual information on how it might be applicable to other environments. The JQHTML framework provides a webpack-based build system that compiles `.jqhtml` template files into efficient JavaScript modules at build time, eliminating the need for runtime template parsing.
## Architecture Summary
JQHTML consists of four core packages that work together:
1. **@jqhtml/parser** - Compiles templates to JavaScript at build time
2. **@jqhtml/webpack-loader** - Integrates the parser with webpack
3. **@jqhtml/core** - Runtime library for component lifecycle and rendering
4. **@jqhtml/router** - Optional SPA routing functionality
The build process transforms declarative `.jqhtml` templates into instruction-based JavaScript modules that integrate seamlessly with webpack's module system.
## Setting Up Webpack for JQHTML
### Basic Configuration
The minimal webpack configuration required to process `.jqhtml` files:
```javascript
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jqhtml$/,
use: '@jqhtml/webpack-loader'
}
]
},
resolve: {
extensions: ['.js', '.jqhtml', '.json']
}
};
```
### Using Configuration Helpers
JQHTML provides helper functions to simplify webpack configuration:
```javascript
// webpack.config.js
const { addJQHTMLSupport } = require('@jqhtml/webpack-loader');
module.exports = addJQHTMLSupport({
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
// Your other webpack configurations
devtool: 'source-map',
mode: process.env.NODE_ENV || 'development'
});
```
### Advanced Configuration with Options
```javascript
const { createJQHTMLRule } = require('@jqhtml/webpack-loader');
module.exports = {
module: {
rules: [
createJQHTMLRule({
sourceMap: true,
minify: process.env.NODE_ENV === 'production'
}),
// Other rules (CSS, images, etc.)
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
```
## How the Build Process Works
### 1. Template Compilation Pipeline
The webpack loader processes `.jqhtml` files through a multi-stage pipeline:
```
.jqhtml file → Lexer → Tokens → Parser → AST → CodeGenerator → ES Module
```
**Stage 1: Lexical Analysis**
- The Lexer scans the template character by character
- Generates a stream of tokens representing template elements
- Tracks line/column positions for error reporting
**Stage 2: Parsing**
- The Parser converts tokens into an Abstract Syntax Tree (AST)
- Validates template syntax and structure
- Reports errors with precise file locations
**Stage 3: Code Generation**
- The CodeGenerator traverses the AST
- Produces JavaScript instruction arrays
- Wraps output in ES module exports
### 2. Template Syntax Processing
The loader handles various template constructs:
```jqhtml
<!-- Component Definition -->
<Define:UserCard as="div">
<!-- Data Binding -->
<h2 :text="this.data.title"></h2>
<!-- Conditional Rendering -->
<% if (this.data.showDetails): %>
<div :html="this.data.description"></div>
<% endif; %>
<!-- Event Handling -->
<button @click="handleClick">Click Me</button>
<!-- Slots for Content Projection -->
<#content />
<!-- Scoped Element IDs -->
<div $id="output"></div>
</Define:UserCard>
```
### 3. Generated Module Format
The loader generates ES modules with this structure:
```javascript
// Generated from UserCard.jqhtml
const template = {
name: 'UserCard',
as: 'div',
defaultAttributes: {},
render: function render() {
const _output = [];
// Opening tag instruction
_output.push({tag: ["div", {"class": "user-card"}, false]});
// Text binding instruction
_output.push({tag: ["h2", {}, false]});
_output.push({expr: () => this.data.title});
_output.push({tag: ["h2", {}, true]});
// Conditional block
if (this.data.showDetails) {
_output.push({tag: ["div", {}, false]});
_output.push({html: () => this.data.description});
_output.push({tag: ["div", {}, true]});
}
// Event handler
_output.push({tag: ["button", {}, false]});
_output.push({on: ["click", this.handleClick]});
_output.push("Click Me");
_output.push({tag: ["button", {}, true]});
// Slot for content projection
_output.push({slot: ["content", {}, null]});
// Scoped ID element
_output.push({tag: ["div", {"id": this.$id("output")}, false]});
_output.push({tag: ["div", {}, true]});
// Closing tag
_output.push({tag: ["div", {}, true]});
return [_output, this];
}
};
export default template;
```
## Express Integration Pattern
### Project Structure
```
my-jqhtml-app/
├── src/
│ ├── components/
│ │ ├── App.jqhtml
│ │ ├── UserCard.jqhtml
│ │ └── UserList.jqhtml
│ ├── index.js
│ └── styles.css
├── server/
│ └── index.js
├── dist/
│ └── (generated bundles)
├── package.json
└── webpack.config.js
```
### Express Server Setup
```javascript
// server/index.js
const express = require('express');
const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('../webpack.config.js');
const app = express();
const compiler = webpack(config);
// Development mode with hot reload
if (process.env.NODE_ENV !== 'production') {
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
app.use(webpackHotMiddleware(compiler));
}
// Serve static files
app.use(express.static(path.join(__dirname, '../dist')));
// API routes
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'John Doe', active: true },
{ id: 2, name: 'Jane Smith', active: false }
]);
});
// Fallback to index.html for SPA
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../dist/index.html'));
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
```
### Package.json Scripts
```json
{
"name": "my-jqhtml-app",
"version": "1.0.0",
"scripts": {
"dev": "NODE_ENV=development nodemon server/index.js",
"build": "webpack --mode production",
"start": "NODE_ENV=production node server/index.js",
"watch": "webpack --watch --mode development"
},
"dependencies": {
"@jqhtml/core": "^2.0.0",
"@jqhtml/router": "^2.0.0",
"express": "^4.18.0",
"jquery": "^3.7.0"
},
"devDependencies": {
"@jqhtml/parser": "^2.0.0",
"@jqhtml/webpack-loader": "^2.0.0",
"webpack": "^5.95.0",
"webpack-cli": "^5.0.0",
"webpack-dev-middleware": "^5.3.0",
"webpack-hot-middleware": "^2.25.0",
"nodemon": "^2.0.0",
"html-webpack-plugin": "^5.5.0"
}
}
```
## Entry Point Configuration
### Main Application Entry
```javascript
// src/index.js
import { Component, mount } from '@jqhtml/core';
import AppTemplate from './components/App.jqhtml';
import './styles.css';
// Define the root component
class App extends Component {
static template = AppTemplate;
constructor(element, data, parent) {
super(element, data, parent);
this.state = {
users: [],
loading: true
};
}
async on_load() {
try {
const response = await fetch('/api/users');
const users = await response.json();
this.update({ users, loading: false });
} catch (error) {
console.error('Failed to load users:', error);
this.update({ loading: false, error: error.message });
}
}
}
// Mount the application
$(document).ready(() => {
mount(App, $('#app'), {
title: 'JQHTML Demo Application'
});
});
```
## Development vs Production Builds
### Development Configuration
```javascript
// webpack.dev.js
const { addJQHTMLSupport } = require('@jqhtml/webpack-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = addJQHTMLSupport({
mode: 'development',
devtool: 'eval-source-map',
entry: [
'webpack-hot-middleware/client?reload=true',
'./src/index.js'
],
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
publicPath: '/'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
inject: true
}),
new webpack.HotModuleReplacementPlugin()
]
});
```
### Production Configuration
```javascript
// webpack.prod.js
const { addJQHTMLSupport } = require('@jqhtml/webpack-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = addJQHTMLSupport({
mode: 'production',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.[contenthash].js',
clean: true
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
})
]
});
```
## TypeScript Integration
### TypeScript Configuration
```typescript
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["ES2020", "DOM"],
"jsx": "preserve",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "types/**/*"]
}
```
### Type Declarations
```typescript
// types/jqhtml.d.ts
declare module '*.jqhtml' {
import { ComponentTemplate } from '@jqhtml/core';
const template: ComponentTemplate;
export default template;
}
// Extend jQuery types
interface JQuery {
component(): Component | null;
findComponent(name: string): Component | null;
}
```
### TypeScript Component
```typescript
// src/components/UserCard.ts
import { Component, ComponentOptions } from '@jqhtml/core';
import UserCardTemplate from './UserCard.jqhtml';
interface UserData {
id: number;
name: string;
email: string;
active: boolean;
}
export class UserCard extends Component<UserData> {
static template = UserCardTemplate;
constructor(element: JQuery, data: UserData, parent?: Component) {
super(element, data, parent);
}
handleClick(): void {
this.emit('user-selected', this.data);
}
async on_load(): Promise<void> {
// TypeScript ensures type safety
console.log(`Loading user: ${this.data.name}`);
}
}
```
## Adapting to Other Environments
### Vite Integration
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import { createJQHTMLPlugin } from '@jqhtml/vite-plugin'; // Hypothetical
export default defineConfig({
plugins: [createJQHTMLPlugin()],
build: {
rollupOptions: {
input: 'src/index.js'
}
}
});
```
### Parcel Integration
Parcel should work out of the box with a `.parcelrc` configuration:
```json
{
"extends": "@parcel/config-default",
"transformers": {
"*.jqhtml": ["@jqhtml/parcel-transformer"]
}
}
```
### Create React App Ejected
After ejecting, modify `webpack.config.js`:
```javascript
// Add to module.rules array
{
test: /\.jqhtml$/,
use: '@jqhtml/webpack-loader',
exclude: /node_modules/
}
```
## Common Webpack Pitfalls and Solutions
### Issue 1: Module Resolution
**Problem**: Webpack can't find `.jqhtml` files
**Solution**: Add to resolve.extensions:
```javascript
resolve: {
extensions: ['.js', '.jqhtml', '.json']
}
```
### Issue 2: Source Maps Not Working
**Problem**: Can't debug original template code
**Solution**: Enable source maps in loader options:
```javascript
{
test: /\.jqhtml$/,
use: {
loader: '@jqhtml/webpack-loader',
options: {
sourceMap: true
}
}
}
```
### Issue 3: jQuery Not Available
**Problem**: `$` is not defined errors
**Solution**: Provide jQuery globally:
```javascript
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
```
### Issue 4: Template Changes Not Detected
**Problem**: Webpack doesn't rebuild on template changes
**Solution**: Ensure watch mode includes `.jqhtml`:
```javascript
watchOptions: {
ignored: /node_modules/,
poll: 1000 // For network filesystems
}
```
## Performance Optimization
### Code Splitting
```javascript
// Dynamic imports for lazy loading
async loadUserModule() {
const { UserCard } = await import('./components/UserCard.js');
const template = await import('./components/UserCard.jqhtml');
// Use the dynamically loaded component
mount(UserCard, $('#user-container'), userData);
}
```
### Bundle Analysis
```javascript
// Add to webpack config
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
]
```
### Tree Shaking
Ensure proper ES module usage:
```javascript
// Good - tree-shakeable
import { Component, mount } from '@jqhtml/core';
// Bad - imports entire library
import * as jqhtml from '@jqhtml/core';
```
## Testing Setup
### Jest Configuration
```javascript
// jest.config.js
module.exports = {
transform: {
'^.+\\.jqhtml$': '@jqhtml/jest-transformer',
'^.+\\.jsx?$': 'babel-jest'
},
moduleFileExtensions: ['js', 'jqhtml'],
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/test/setup.js']
};
```
### Test Setup File
```javascript
// test/setup.js
import $ from 'jquery';
global.$ = global.jQuery = $;
// Mock template loader for tests
jest.mock('*.jqhtml', () => ({
name: 'MockComponent',
render: jest.fn(() => [[], this])
}), { virtual: true });
```
## Debugging Techniques
### Webpack Build Debugging
```javascript
// Enable verbose output
module.exports = {
stats: 'verbose',
infrastructureLogging: {
level: 'verbose'
}
};
```
### Template Compilation Debugging
```javascript
// Add to loader options
{
loader: '@jqhtml/webpack-loader',
options: {
debug: true,
logLevel: 'verbose'
}
}
```
### Runtime Debugging
```javascript
// Enable debug mode in core
import { setDebugMode } from '@jqhtml/core';
setDebugMode(true);
```
## Migration Guide
### From Inline Templates
**Before** (v1 style):
```javascript
const template = `
<div class="user">
<h2>:text="name"</h2>
</div>
`;
```
**After** (v2 with webpack):
```jqhtml
<!-- UserCard.jqhtml -->
<Define:UserCard as="div">
<h2 :text="this.data.name"></h2>
</Define:UserCard>
```
### From Runtime Compilation
**Before**:
```javascript
Component.create('UserCard', {
template: templateString,
// ...
});
```
**After**:
```javascript
import UserCardTemplate from './UserCard.jqhtml';
class UserCard extends Component {
static template = UserCardTemplate;
// ...
}
```
## Conclusion
The JQHTML webpack loader provides a robust build-time compilation system that transforms declarative templates into efficient JavaScript modules. By processing templates at build time rather than runtime, applications benefit from:
- **Better Performance**: No runtime template parsing overhead
- **Improved Developer Experience**: Build-time error detection and source maps
- **Standard Tooling**: Integration with webpack ecosystem
- **Type Safety**: Full TypeScript support
- **Code Splitting**: Dynamic imports and lazy loading
- **Production Optimization**: Minification and tree shaking
The system is designed to work seamlessly with Express servers and can be adapted to other build tools and environments. The key to successful integration is understanding the transformation pipeline and properly configuring webpack to handle `.jqhtml` files as modules.

View File

@@ -0,0 +1,96 @@
# JQHTML VSCode Extension Setup
## Overview
The JQHTML VSCode extension is now integrated into the RSpade development environment alongside the RSpade extension. Both extensions are automatically installed and updated through the IDE setup scripts.
## Installation
### Automatic Installation
1. The JQHTML extension is installed as an NPM dependency: `@jqhtml/vscode-extension`
2. When you run VS Code tasks or open the project, the setup script checks for both extensions
3. If either extension is missing or outdated, they are automatically installed (if auto-install is enabled)
### Manual Installation
If auto-install is disabled or fails:
1. Press `Ctrl+Shift+X` in VS Code to open Extensions
2. Click the `...` menu → "Install from VSIX..."
3. For RSpade: Select `app/RSpade/Extension/rspade-framework.vsix`
4. For JQHTML: Select `node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-*.vsix`
## Configuration
### Settings
The following settings in `.vscode/settings.json` control extension behavior:
- `rspade.autoCheckExtension` - Whether to check for extension updates (default: true)
- `rspade.autoInstallExtension` - Whether to auto-install/update extensions (default: true)
### Extension Locations
- **RSpade Extension**: `/app/RSpade/Extension/rspade-framework.vsix`
- **JQHTML Extension**: `/node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-*.vsix`
## How It Works
### Check Process
1. The setup script (`/.vscode/ide_setup/check_setup.sh`) runs on VS Code startup
2. It checks for both RSpade and JQHTML extensions
3. Compares installed versions with expected versions from:
- RSpade: `app/RSpade/Extension/package.json`
- JQHTML: `node_modules/@jqhtml/vscode-extension/package.json`
### Update Process
1. If extensions need updating and auto-install is enabled:
- Installs RSpade extension from local VSIX
- Installs JQHTML extension from npm package VSIX
2. Creates marker file `.extensions-updated` to trigger VS Code reload
3. VS Code automatically reloads to activate the updated extensions
## Features Provided by JQHTML Extension
- **Syntax Highlighting**: Full syntax highlighting for `.jqhtml` template files
- **Code Snippets**: Quick snippets for common JQHTML patterns
- **Language Support**: Proper language configuration for JQHTML templates
- **IntelliSense**: Auto-completion for JQHTML tags and attributes
## Updating the JQHTML Extension
To update to a newer version:
```bash
npm update @jqhtml/vscode-extension
```
The next time VS Code starts, it will detect the new version and auto-install it.
## Troubleshooting
### Extension Not Installing
1. Check that the VSIX file exists:
```bash
ls -la node_modules/@jqhtml/vscode-extension/*.vsix
```
2. Ensure VS Code CLI is available:
```bash
code --version
```
3. Check the setup script output for specific errors
### Version Mismatch
If the extension shows as outdated:
1. Run `npm update @jqhtml/vscode-extension`
2. Restart VS Code to trigger the update check
3. Or manually install from the VSIX file
### Disable Auto-Install
To disable automatic extension installation, add to `.vscode/settings.json`:
```json
{
"rspade.autoInstallExtension": false
}
```
## Integration with RSpade Extension
Both extensions work together:
- **RSpade Extension**: Provides RSX framework support, PHP formatting, namespace management
- **JQHTML Extension**: Provides JQHTML template support, syntax highlighting, snippets
The setup script handles both extensions in a single update cycle, minimizing VS Code reloads.

210
docs/llm_migration_system.md Executable file
View File

@@ -0,0 +1,210 @@
# LLM-Friendly Migration System - Snapshot Implementation
## Problem Statement
LLMs struggle with partial migration failures because:
- MySQL DDL operations auto-commit and cannot be rolled back
- Partial migrations leave the database in an inconsistent state
- LLMs get confused about what has/hasn't been applied
- Recovery requires manual intervention or database reset
## Implemented Solution: Docker Snapshot System
### Core Design
The system uses filesystem-level snapshots of the MySQL data directory in Docker environments to provide complete rollback capability for migrations.
### How It Works
1. **Development Mode Only**
- System only enforces snapshots in development environments
- Production migrations run normally without snapshots
- Detects environment via `APP_ENV` and Docker presence
2. **Migration Session Flow**
```bash
# 1. Start migration session (creates snapshot)
php artisan migrate:begin
# 2. Run migrations (with safety net)
php artisan migrate
# 3a. If successful, commit changes
php artisan migrate:commit
# 3b. If failed, rollback to snapshot
php artisan migrate:rollback
```
3. **Automatic Rollback on Failure**
- When migrations fail in development, system offers automatic rollback
- LLM gets clear instructions on how to proceed
- Database restored to exact pre-migration state
### Implementation Details
#### Commands
**migrate:begin**
- Stops MySQL via supervisord
- Creates backup of `/var/lib/mysql` to `/var/lib/mysql_backup`
- Restarts MySQL
- Creates `.migrating` flag file
- Blocks web UI access (development only)
**migrate:rollback**
- Stops MySQL
- Replaces MySQL data directory with backup
- Restarts MySQL
- Removes flag file and backup
- Re-enables web UI
**migrate:commit**
- Removes backup directory
- Removes flag file
- Re-enables web UI
**migrate:status**
- Shows current migration session status
- Lists available commands
- Checks database connectivity
- Shows pending migrations count
#### Modified migrate Command
The existing `Maint_Migrate` command was enhanced to:
- Check for `.migrating` flag in development mode
- Require snapshot unless `--production` flag is used
- Offer automatic rollback on failure
- Run normally in production environments
#### Middleware Protection
`CheckMigrationMode` middleware:
- Only active in development environments
- Blocks HTTP requests during migration sessions
- Shows detailed error message with recovery instructions
### Usage Examples
#### Successful Migration (Development)
```bash
$ php artisan migrate:begin
✅ Database snapshot created successfully!
$ php artisan migrate
✅ Migration mode active - snapshot available for rollback
Running migrations...
✅ Migrations completed successfully!
$ php artisan migrate:commit
✅ Migration changes committed successfully!
```
#### Failed Migration with Auto-Rollback (Development)
```bash
$ php artisan migrate:begin
✅ Database snapshot created successfully!
$ php artisan migrate
✅ Migration mode active - snapshot available for rollback
Running migrations...
❌ Migration failed!
Error: Column already exists
🔄 Database snapshot is available for rollback.
Would you like to rollback to the snapshot now? (yes/no) [no]: yes
Rolling back database...
✅ Database rolled back successfully!
You can now:
1. Fix your migration files
2. Run "php artisan migrate:begin" to create a new snapshot
3. Run "php artisan migrate" to try again
```
#### Production Migration
```bash
$ php artisan migrate
🚀 Running in production mode (no snapshot protection)
Running migrations...
✅ Migrations completed successfully!
```
#### Bypass Snapshot in Development
```bash
$ php artisan migrate --production
⚠️ Running without snapshot protection!
Are you absolutely sure? This cannot be undone! (yes/no) [no]: yes
Running migrations...
```
### Environment Detection
The system automatically detects the environment:
1. **Development Mode** (snapshots required):
- `APP_ENV` is not 'production'
- Docker environment detected (`/.dockerenv` exists)
- No `--production` flag passed
2. **Production Mode** (no snapshots):
- `APP_ENV` is 'production' OR
- `--production` flag is passed OR
- Not running in Docker
### Benefits for LLMs
1. **Clear Error Recovery**
- Failed migrations automatically offer rollback
- No partial state confusion
- Explicit instructions on next steps
2. **Safe Experimentation**
- LLMs can try migrations without fear
- Complete rollback always available
- Learn from failures without consequences
3. **Simple Mental Model**
- Migration session is atomic: all or nothing
- Clear session lifecycle: begin → migrate → commit/rollback
- Status command provides current state
4. **Production Safety**
- System automatically adapts to environment
- No dangerous operations in production
- Clear separation of dev/prod behaviors
### Implementation Files
- `/app/Console/Commands/MigrateBeginCommand.php` - Snapshot creation
- `/app/Console/Commands/MigrateRollbackCommand.php` - Snapshot restoration
- `/app/Console/Commands/MigrateCommitCommand.php` - Session completion
- `/app/Console/Commands/MigrateStatusCommand.php` - Status display
- `/app/Console/Commands/Maint_Migrate.php` - Enhanced migrate command
- `/app/Http/Middleware/CheckMigrationMode.php` - Web UI protection
### Limitations
1. **Docker Only**: Snapshots require Docker environment with supervisord
2. **Development Only**: Not available in production environments
3. **Disk Space**: Requires space for MySQL data directory copy
4. **Single Session**: Only one migration session at a time
### Future Enhancements
Potential improvements:
- Incremental snapshots for large databases
- Migration preview/dry-run mode
- Automatic migration file fixes for common errors
- Integration with version control for migration rollback
## Why This Approach
This implementation was chosen over alternatives because:
1. **Complete Safety**: Filesystem snapshots guarantee perfect rollback
2. **Simple Implementation**: Uses existing tools (supervisord, cp, rm)
3. **LLM-Friendly**: Clear session model with explicit states
4. **Production Compatible**: Gracefully degrades in production
5. **Laravel Integration**: Works with existing migration system
6. **Immediate Value**: Solves the core problem without complexity
The snapshot approach provides the "all or nothing" behavior that makes migrations predictable and safe for LLM interaction, while maintaining full compatibility with Laravel's migration system and production deployments.

457
docs/rsx-architecture.md Executable file
View File

@@ -0,0 +1,457 @@
# RSX Architecture Documentation
## Overview
RSX (RSpade eXtension) is a path-agnostic development framework built on top of Laravel that provides a modern, attribute-driven development experience. Developers can organize their code freely without worrying about file paths, imports, or directory structures.
## Core Philosophy
1. **Path Independence**: Files reference each other by class name, not file path
2. **Attribute-Driven**: PHP 8 attributes for routing, caching, middleware, and more
3. **Automatic Discovery**: All files are automatically indexed and made available
4. **Zero Configuration**: Works out of the box with sensible defaults
5. **Performance First**: Fast in development, optimized in production
6. **Opinionated Design**: Strong conventions for consistent development
## Three-Tier Architecture
RSX follows a clear three-tier directory structure philosophy:
### 1. Framework Layer: `/app/RSpade/`
This directory contains framework and library code that should NOT be modified by users.
**Purpose**: Core RSX functionality that powers the path-agnostic environment
**Key Components**:
#### Core System (`/app/RSpade/Core/`)
- **Manifest.php** - File indexing and discovery system
- Scans directories for PHP classes
- Builds comprehensive class map
- Handles file-based and memory caching
- Supports incremental rebuilds
- **Autoloader.php** - SPL autoloader for RSX classes
- Integrates with Manifest for class loading
- Supports path-agnostic class resolution
- Falls back to PSR-4 for non-RSX classes
#### Dispatch System (`/app/RSpade/Core/Dispatch/`)
- **Dispatcher.php** - Central request dispatcher
- Routes HTTP requests to appropriate handlers
- Processes attributes before and after execution
- Handles static assets, API requests, and controllers
- Integrates with Laravel's request/response cycle
- **RouteResolver.php** - URL pattern matching
- Converts route patterns to regex
- Extracts parameters from URLs
- Handles optional parameters and wildcards
- Sorts routes by specificity
- **AttributeProcessor.php** - Attribute handling
- Chain of Responsibility pattern
- Processes attributes before/after method execution
- Supports custom processors
- Handles caching, rate limiting, CORS, etc.
- **ApiHandler.php** - API request processing
- JSON-only responses (opinionated design)
- Internal API execution without HTTP overhead
- Session stack management for nested calls
- Pagination, filtering, and sorting support
- **AssetHandler.php** - Static file serving
- Serves files from `/rsx/*/public/` directories
- Proper MIME type detection
- Cache headers for performance
- Security checks for path traversal
#### Attributes (`/app/RSpade/Core/Attributes/`)
- **Route.php** - HTTP route definition
```php
#[Route('/api/users', methods: ['GET', 'POST'])]
```
- **Cache.php** - Response caching
```php
#[Cache(ttl: 3600, key: 'user-list', tags: ['users'])]
```
- **RateLimit.php** - Request throttling
```php
#[RateLimit(max_attempts: 60, decay_minutes: 1)]
```
- **Middleware.php** - Middleware application
```php
#[Middleware(['auth', 'verified'])]
```
- **Cors.php** - Cross-Origin Resource Sharing
```php
#[Cors(allowed_origins: ['https://app.example.com'])]
```
- **ApiVersion.php** - API versioning
```php
#[ApiVersion('v2', deprecated: false)]
```
#### Processors (`/app/RSpade/Core/Processors/`)
- **CacheProcessor.php** - Handles Cache attributes
- **RateLimitProcessor.php** - Handles RateLimit attributes
- **MiddlewareProcessor.php** - Handles Middleware attributes
- **CorsProcessor.php** - Handles CORS preflight and headers
#### Base Classes (`/app/RSpade/Core/Base/`)
- **Rsx_Controller** - Base controller with dispatch integration
- **Rsx_Model_Abstract** - Base model with manifest awareness
- **Rsx_Api** - Base API controller with JSON responses
- **Rsx_Service** - Base service class
- **Rsx_Component** - Base UI component class
### 2. Application Layer: `/app/*` (other directories)
Standard Laravel application directories that users can modify.
**Purpose**: Traditional Laravel application code
**Examples**:
- `/app/Http/Controllers/` - Laravel controllers
- `/app/Models/` - Eloquent models
- `/app/Console/Commands/` - Artisan commands
- `/app/Services/` - Business logic services
### 3. User Development Space: `/rsx/`
Path-agnostic RSX development directory.
**Purpose**: User-created RSX components that are automatically discovered
**Convention-based Structure** (optional):
```
/rsx/
├── controllers/ # HTTP controllers
├── api/ # API endpoints
├── models/ # Data models
├── services/ # Business logic
├── components/ # UI components
├── views/ # Blade templates
├── js/ # JavaScript modules
├── styles/ # SCSS/CSS files
└── */public/ # Public assets (served directly)
```
## Request Flow
### 1. HTTP Request Arrives
```
Browser/Client → Laravel → RSX Dispatcher
```
### 2. Dispatcher Processing
```
Dispatcher → AssetHandler (if static file)
→ RouteResolver → Find matching route
→ AttributeProcessor → Process before attributes
→ HandlerFactory → Create handler instance
→ Execute handler method
→ AttributeProcessor → Process after attributes
→ Response
```
### 3. Attribute Processing Pipeline
```
Request → RateLimitProcessor → MiddlewareProcessor → CacheProcessor → Handler
← CorsProcessor ← CacheProcessor ← Response
```
## Key Features
### 1. Attribute-Driven Development
```php
namespace Rsx\Api;
use App\RSpade\Core\Base\Rsx_Api;
use App\RSpade\Core\Attributes\{Route, Cache, RateLimit, Cors};
#[Cors(allowed_origins: ['https://app.example.com'])]
class UserApi extends Rsx_Api
{
#[Route('/api/users', methods: ['GET'])]
#[Cache(ttl: 300, tags: ['users'])]
#[RateLimit(max_attempts: 60)]
public static function list_users($params)
{
return ['users' => User::all()];
}
#[Route('/api/users/:id', methods: ['GET'])]
public static function get_user($params)
{
return User::find($params['id']);
}
}
```
### 2. Path-Agnostic Class Loading
```php
// No need for use statements or full paths
class OrderController extends Rsx_Controller
{
public function process_order($params)
{
// Classes are auto-discovered
$order = new OrderModel();
$payment = new PaymentService();
return $payment->process($order);
}
}
```
### 3. Internal API Execution (Future)
```php
// Call API endpoints internally without HTTP overhead
$result = API::execute('UserApi.get_profile', ['user_id' => 123]);
// Nested calls maintain separate session contexts
// UserApi → OrderApi → PaymentApi (each with own session)
```
### 4. Smart Asset Serving
Any file in `/rsx/*/public/` is automatically served:
- `/rsx/shop/public/logo.png``/rsx/shop/public/logo.png`
- Proper MIME types and cache headers
- No configuration needed
## Manifest System
The Manifest is the heart of RSX's path-agnostic architecture:
### Building
```bash
php artisan rsx:manifest:build # Full rebuild
php artisan rsx:manifest:rebuild # Force rebuild
php artisan rsx:manifest:clear # Clear cache
```
### Manifest Structure
```json
{
"classes": {
"UserController": {
"file": "/rsx/controllers/UserController.php",
"type": "controller",
"namespace": "Rsx\\Controllers"
}
},
"routes": {
"controllers": {
"/users": {
"GET": {
"class": "UserController",
"method": "index"
}
}
}
},
"metadata": {
"build_time": "2024-01-01 12:00:00",
"file_count": 150,
"route_count": 45
}
}
```
## Performance Optimizations
### 1. Two-Level Caching
- **Memory Cache**: In-process array cache for current request
- **File Cache**: Persistent cache across requests
- **Automatic invalidation**: Based on file modification times
### 2. Route Compilation
```bash
php artisan rsx:routes:cache # Compile routes for production
php artisan rsx:routes:clear # Clear route cache
```
### 3. Lazy Loading
- Classes are only loaded when actually used
- Manifest is loaded once per request
- Attributes are extracted on-demand
## Testing Framework
RSX includes comprehensive testing infrastructure:
### Test Organization
```
/tests/RsxFramework/
├── Unit/ # Isolated class tests
├── Integration/ # Component interaction tests
├── EndToEnd/ # Complete flow tests
└── Commands/ # Artisan command tests
```
### Running Tests
```bash
php artisan test # All tests (color-free output)
php artisan test tests/RsxFramework # Framework tests only
php artisan test --coverage # With coverage report
```
## Artisan Commands
### Manifest Management
```bash
php artisan rsx:manifest:build # Build manifest
php artisan rsx:manifest:show # Display manifest
php artisan rsx:manifest:stats # Show statistics
php artisan rsx:manifest:watch # Watch for changes
```
### Route Management
```bash
php artisan rsx:routes # List all routes
php artisan rsx:routes:list # Detailed route list
php artisan rsx:routes:cache # Cache routes
php artisan rsx:routes:clear # Clear route cache
```
### Development Tools
```bash
php artisan rsx:make:controller # Create RSX controller
php artisan rsx:make:api # Create API endpoint
php artisan rsx:make:model # Create RSX model
```
## Configuration
### Environment Variables
```env
RSX_CACHE_ENABLED=true # Enable manifest caching
RSX_CACHE_TTL=3600 # Cache TTL in seconds
RSX_AUTO_BUILD=true # Auto-build manifest if missing
RSX_WATCH_ENABLED=false # Enable file watching
```
### Configuration File
```php
// config/rsx.php
return [
'manifest' => [
'cache_enabled' => env('RSX_CACHE_ENABLED', true),
'cache_ttl' => env('RSX_CACHE_TTL', 3600),
],
'dispatch' => [
'api_json_only' => true, # APIs always return JSON
'asset_cache_ttl' => 86400,
],
];
```
## Best Practices
### 1. Use Attributes Wisely
- Apply at class level for all methods
- Apply at method level for specific routes
- Combine multiple attributes for complex behavior
### 2. Organize by Feature
```
/rsx/shop/
├── controllers/
├── models/
├── services/
└── public/
/rsx/blog/
├── controllers/
├── models/
└── public/
```
### 3. Keep Controllers Thin
- Business logic in services
- Data access in models
- Controllers for request/response only
### 4. Leverage Caching
- Use Cache attribute for expensive operations
- Set appropriate TTL values
- Use cache tags for invalidation
### 5. Rate Limiting
- Always add rate limiting to public APIs
- Use different limits for authenticated vs anonymous
- Consider using custom key resolvers
## Migration from Laravel
### Step 1: Move Controllers
```bash
# Move Laravel controller to RSX
mv app/Http/Controllers/UserController.php rsx/controllers/
```
### Step 2: Add Attributes
```php
// Before (Laravel)
Route::get('/users', [UserController::class, 'index']);
// After (RSX)
#[Route('/users', methods: ['GET'])]
public function index($params) { }
```
### Step 3: Update References
```php
// Before
use App\Http\Controllers\UserController;
// After - no import needed!
$controller = new UserController();
```
## Troubleshooting
### Manifest Not Building
```bash
php artisan rsx:manifest:rebuild --verbose
```
### Routes Not Found
```bash
php artisan rsx:routes:list | grep "your-route"
```
### Class Not Loading
```bash
php artisan rsx:manifest:show | grep "YourClass"
```
### Cache Issues
```bash
php artisan rsx:manifest:clear
php artisan rsx:routes:clear
php artisan cache:clear
```
## Future Roadmap
1. **GraphQL Support** - Automatic GraphQL schema from attributes
2. **WebSocket Integration** - Real-time features with attributes
3. **Module System** - Package RSX modules for reuse
4. **Hot Reload** - Automatic reload during development
5. **IDE Integration** - Full IntelliSense for RSX classes
6. **Distributed Caching** - Redis/Memcached for manifest
7. **API Documentation** - Auto-generate from attributes
8. **Database Migrations** - Attribute-based migrations

669
docs/rsx-dispatch-system.md Executable file
View File

@@ -0,0 +1,669 @@
# RSX Dispatch System Documentation
## Overview
The RSX Dispatch System is the core request handling mechanism that processes HTTP requests through an attribute-driven, extensible pipeline. It replaces traditional Laravel routing with a more flexible, discoverable system based on PHP 8 attributes.
## Architecture
### Core Components
```
Request → Dispatcher → AssetHandler
→ RouteResolver
→ AttributeProcessor
→ HandlerFactory
→ Response
```
## Dispatcher
The central hub that coordinates request processing.
### Key Responsibilities
1. **Request Routing** - Finds matching routes from the manifest
2. **Asset Serving** - Delegates static file requests to AssetHandler
3. **Attribute Processing** - Coordinates before/after attribute processors
4. **Response Building** - Converts handler results to HTTP responses
### Usage
```php
// In RsxServiceProvider
$dispatcher = new Dispatcher(
$manifest,
$route_resolver,
$attribute_processor,
$asset_handler,
$api_handler
);
// Handle request
$response = $dispatcher->dispatch($url, $method, $params);
```
### Handler Priority
The dispatcher processes handlers in this order:
1. **Assets** (Priority: 10) - Static files from `/rsx/*/public/`
2. **API** (Priority: 20) - API endpoints
3. **Controllers** (Priority: 30) - Web controllers
4. **Files** (Priority: 40) - File download endpoints
## Route Resolution
### Route Patterns
Routes support various pattern types:
```php
#[Route('/users')] // Static route
#[Route('/users/:id')] // Required parameter
#[Route('/users/:id?')] // Optional parameter
#[Route('/posts/:category/:slug')] // Multiple parameters
#[Route('/files/*')] // Wildcard (captures rest of URL)
```
### Route Matching
Routes are matched based on specificity:
1. Exact static matches
2. Routes with fewer parameters
3. Routes with required parameters before optional
4. Wildcard routes last
### Parameter Extraction
Routes use `:param` syntax for parameters:
```php
// Route: /users/:id/posts/:post_id?
// URL: /users/123/posts/456?sort=date
$params = [
'id' => '123', // URL route parameter
'post_id' => '456', // URL route parameter
'sort' => 'date', // GET parameter
'_route' => '/users/:id/posts/:post_id?',
'_method' => 'GET'
];
```
### Parameter Priority
Parameters are merged with the following priority order (earlier takes precedence):
1. **URL Route Parameters** - Values extracted from the route pattern (e.g., `:id`)
2. **GET Parameters** - Query string parameters
3. **POST Parameters** - Request body parameters
If the same parameter name exists in multiple sources, the higher priority value is used:
```php
// Route: /users/:id
// URL: /users/123?id=456
// POST body: id=789
// Result: $params['id'] = '123' (URL route parameter wins)
```
## Attribute System
### Available Attributes
#### Route Attribute
Defines HTTP endpoints:
```php
use App\RSpade\Core\Attributes\Route;
class UserController extends Rsx_Controller
{
#[Route('/users', methods: ['GET', 'POST'])]
public function index($params) { }
#[Route('/users/:id', methods: ['GET'], name: 'user.show')]
public function show($params) { }
}
```
#### Cache Attribute
Caches responses:
```php
use App\RSpade\Core\Attributes\Cache;
#[Cache(ttl: 3600, key: 'user-list', tags: ['users'])]
public function list_users($params)
{
// Expensive operation cached for 1 hour
return User::with('posts')->get();
}
```
#### RateLimit Attribute
Throttles requests:
```php
use App\RSpade\Core\Attributes\RateLimit;
#[RateLimit(
max_attempts: 60,
decay_minutes: 1,
key: 'ip' // or 'user', 'api_key'
)]
public function api_endpoint($params) { }
```
#### Middleware Attribute
Applies Laravel middleware:
```php
use App\RSpade\Core\Attributes\Middleware;
#[Middleware(['auth', 'verified'])]
public function protected_action($params) { }
#[Middleware(['auth'], except: ['index', 'show'])]
class PostController { }
```
#### Cors Attribute
Configures Cross-Origin Resource Sharing:
```php
use App\RSpade\Core\Attributes\Cors;
#[Cors(
allowed_origins: ['https://app.example.com', 'https://*.example.com'],
allowed_methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowed_headers: ['Content-Type', 'Authorization'],
exposed_headers: ['X-Total-Count'],
max_age: 3600,
allow_credentials: true
)]
class ApiController { }
```
#### ApiVersion Attribute
Manages API versioning:
```php
use App\RSpade\Core\Attributes\ApiVersion;
#[ApiVersion(
version: 'v2',
deprecated: true,
deprecation_message: 'Use /api/v3/users instead',
sunset_date: '2025-01-01'
)]
public function legacy_endpoint($params) { }
```
### Attribute Processing Pipeline
Attributes are processed in a specific order based on processor priority:
1. **RateLimit** (Priority: 90) - Blocks excessive requests early
2. **Middleware** (Priority: 80) - Authentication/authorization
3. **Cache** (Priority: 70) - Returns cached responses
4. **Cors** (Priority: 60) - Adds CORS headers
### Creating Custom Attributes
1. Create the attribute class:
```php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
class Audit
{
public function __construct(
public string $action,
public bool $log_params = false
) {}
}
```
2. Create the processor:
```php
namespace App\Processors;
use App\RSpade\Core\Dispatch\Processors\ProcessorInterface;
class AuditProcessor implements ProcessorInterface
{
public function can_handle($attribute): bool
{
return $attribute instanceof Audit;
}
public function process_before($attribute, Request $request, array &$context)
{
Log::info('Audit: ' . $attribute->action, [
'user' => $request->user()?->id,
'params' => $attribute->log_params ? $request->all() : null
]);
}
public function process_after($attribute, Response $response, array $context): ?Response
{
// Log response if needed
return $response;
}
public function get_priority(): int
{
return 50;
}
}
```
3. Register the processor:
```php
// In a service provider
$attribute_processor->register_processor(new AuditProcessor());
```
## API Handler
### JSON-Only Responses
The API handler enforces JSON-only responses (opinionated design):
```php
// Always returns JSON, regardless of Accept header
#[Route('/api/users', methods: ['GET'])]
public function get_users($params)
{
return ['users' => User::all()]; // Automatically converted to JSON
}
```
### API Parameters
The API handler automatically extracts common parameters:
```php
public function list_items($params)
{
// Pagination
$page = $params['_page'] ?? 1; // ?page=2
$per_page = $params['_per_page'] ?? 25; // ?per_page=50
// Sorting
$sort = $params['_sort'] ?? 'id'; // ?sort=name
$order = $params['_order'] ?? 'asc'; // ?order=desc
// Searching
$search = $params['_search'] ?? null; // ?q=term or ?search=term
// Field filtering
$fields = $params['_fields'] ?? null; // ?fields=id,name,email
// API key
$api_key = $params['_api_key'] ?? null; // ?api_key=secret or X-API-Key header
}
```
### Internal API Execution (Future)
```php
// Call API methods internally without HTTP overhead
$result = $api_handler->execute_internal(
'UserApi.get_profile',
['user_id' => 123]
);
// Returns PHP array/object, not JSON response
```
## Asset Handler
### Static File Serving
Automatically serves files from `/rsx/*/public/` directories:
```
/rsx/shop/public/logo.png → Accessible at /rsx/shop/public/logo.png
/rsx/blog/public/styles.css → Accessible at /rsx/blog/public/styles.css
```
### Security Features
- Path traversal protection
- MIME type detection
- Cache headers (1 day default)
- Only serves from designated public directories
### Usage
Files are automatically served without configuration:
```html
<!-- In your views -->
<img src="/rsx/shop/public/logo.png" alt="Shop Logo">
<link rel="stylesheet" href="/rsx/theme/public/styles.css">
```
## Error Handling
### Built-in Error Responses
The dispatcher handles common errors:
```php
// 404 Not Found
if (!$route_match) {
return $this->handle_not_found($url, $method);
}
// 405 Method Not Allowed
if (!in_array($method, $route['methods'])) {
return $this->handle_method_not_allowed($method, $route['methods']);
}
// 500 Internal Server Error
try {
// ... dispatch logic
} catch (Throwable $e) {
return $this->handle_exception($e);
}
```
### Custom Error Handling
Override error methods in a custom dispatcher:
```php
class CustomDispatcher extends Dispatcher
{
protected function handle_not_found($url, $method)
{
return response()->json([
'error' => 'Endpoint not found',
'url' => $url,
'method' => $method
], 404);
}
}
```
## Performance Optimization
### Caching Strategy
1. **Memory Cache** - In-process array cache for current request
2. **File Cache** - Persistent cache using Laravel's cache system
3. **Route Cache** - Pre-compiled routes for production
### Cache Invalidation
```php
// Clear specific cache tags
Cache::tags(['users'])->flush();
// Clear all RSX caches
php artisan rsx:manifest:clear
php artisan rsx:routes:clear
```
### Production Optimization
```bash
# Build and cache for production
php artisan rsx:manifest:build
php artisan rsx:routes:cache
php artisan config:cache
```
## Testing
### Unit Testing Dispatching
```php
use Tests\TestCase;
use App\RSpade\Core\Dispatch\Dispatcher;
class DispatcherTest extends TestCase
{
public function test_routes_to_correct_handler()
{
$dispatcher = $this->create_test_dispatcher();
$response = $dispatcher->dispatch('/users', 'GET');
$this->assertEquals(200, $response->getStatusCode());
$this->assertJson($response->getContent());
}
public function test_applies_rate_limiting()
{
$dispatcher = $this->create_test_dispatcher();
// Make requests up to limit
for ($i = 0; $i < 60; $i++) {
$dispatcher->dispatch('/api/limited', 'GET');
}
// Next request should fail
$this->expectException(TooManyRequestsHttpException::class);
$dispatcher->dispatch('/api/limited', 'GET');
}
}
```
### Integration Testing
```php
class ApiIntegrationTest extends TestCase
{
public function test_complete_api_flow()
{
$response = $this->get('/api/users', [
'Accept' => 'application/xml' // Should still return JSON
]);
$response->assertStatus(200)
->assertHeader('Content-Type', 'application/json')
->assertJsonStructure(['users']);
}
}
```
## Debugging
### Debug Mode
Enable detailed logging:
```php
// In .env
RSX_DEBUG=true
RSX_LOG_DISPATCH=true
```
### Viewing Routes
```bash
# List all discovered routes
php artisan rsx:routes:list
# Search for specific route
php artisan rsx:routes:list | grep "users"
# Show route details
php artisan rsx:routes:show /users/:id
```
### Tracing Requests
```php
// In Dispatcher
Log::debug('Dispatch', [
'url' => $url,
'method' => $method,
'route' => $route_match,
'attributes' => $attributes,
'params' => $params
]);
```
## Migration Guide
### From Laravel Routes
Before (Laravel):
```php
// routes/web.php
Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store']);
Route::get('/users/:id', [UserController::class, 'show']);
```
After (RSX):
```php
// /rsx/controllers/UserController.php
class UserController extends Rsx_Controller
{
#[Route('/users', methods: ['GET'])]
public function index($params) { }
#[Route('/users', methods: ['POST'])]
public function store($params) { }
#[Route('/users/:id', methods: ['GET'])]
public function show($params) { }
}
```
### From Laravel Middleware
Before:
```php
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
```
After:
```php
#[Middleware(['auth', 'verified'])]
class DashboardController extends Rsx_Controller
{
#[Route('/dashboard', methods: ['GET'])]
public function index($params) { }
}
```
## Best Practices
### 1. Attribute Organization
Place related attributes together:
```php
#[Route('/api/users', methods: ['GET'])]
#[Cache(ttl: 300, tags: ['users'])]
#[RateLimit(max_attempts: 100)]
#[Middleware(['auth:api'])]
public function list_users($params) { }
```
### 2. Parameter Validation
Validate parameters early:
```php
public function get_user($params)
{
$user_id = $params['id'] ?? null;
if (!$user_id || !is_numeric($user_id)) {
return response()->json(['error' => 'Invalid user ID'], 400);
}
// Process request...
}
```
### 3. Error Handling
Use consistent error responses:
```php
public function process_order($params)
{
try {
$result = $this->order_service->process($params);
return ['success' => true, 'order' => $result];
} catch (ValidationException $e) {
return response()->json(['error' => $e->getMessage()], 422);
} catch (Exception $e) {
Log::error('Order processing failed', ['error' => $e]);
return response()->json(['error' => 'Processing failed'], 500);
}
}
```
### 4. Cache Strategy
Use appropriate cache TTLs and tags:
```php
#[Cache(ttl: 3600, tags: ['products', 'catalog'])] // 1 hour, tagged
public function get_product_catalog($params) { }
#[Cache(ttl: 60, key: 'hot-deals')] // 1 minute for frequently changing
public function get_hot_deals($params) { }
#[Cache(ttl: 86400, tags: ['static'])] // 1 day for static content
public function get_terms_of_service($params) { }
```
### 5. Rate Limiting
Apply appropriate limits:
```php
// Public endpoints - strict limits
#[RateLimit(max_attempts: 10, decay_minutes: 1)]
public function public_search($params) { }
// Authenticated - higher limits
#[RateLimit(max_attempts: 100, decay_minutes: 1, key: 'user')]
public function user_search($params) { }
// Internal APIs - very high limits
#[RateLimit(max_attempts: 1000, decay_minutes: 1, key: 'api_key')]
public function internal_api($params) { }
```
## Summary
The RSX Dispatch System provides:
- **Attribute-driven routing** without route files
- **Automatic discovery** of controllers and routes
- **Extensible processing** through attribute processors
- **Built-in features** like caching, rate limiting, CORS
- **Static asset serving** without configuration
- **JSON-only APIs** for consistency
- **Performance optimization** through caching
- **Easy testing** with comprehensive test helpers
It replaces traditional Laravel routing with a more flexible, discoverable system that keeps route definitions with the code they control.