Files
rspade_system/app/RSpade/man/dispatch.txt
root 77b4d10af8 Refactor filename naming system and apply convention-based renames
Standardize settings file naming and relocate documentation files
Fix code quality violations from rsx:check
Reorganize user_management directory into logical subdirectories
Move Quill Bundle to core and align with Tom Select pattern
Simplify Site Settings page to focus on core site information
Complete Phase 5: Multi-tenant authentication with login flow and site selection
Add route query parameter rule and synchronize filename validation logic
Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs
Implement filename convention rule and resolve VS Code auto-rename conflict
Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns
Implement RPC server architecture for JavaScript parsing
WIP: Add RPC server infrastructure for JS parsing (partial implementation)
Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation
Add JQHTML-CLASS-01 rule and fix redundant class names
Improve code quality rules and resolve violations
Remove legacy fatal error format in favor of unified 'fatal' error type
Filter internal keys from window.rsxapp output
Update button styling and comprehensive form/modal documentation
Add conditional fly-in animation for modals
Fix non-deterministic bundle compilation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 19:10:02 +00:00

688 lines
15 KiB
Plaintext
Executable File

# 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
The `$params` array passed to controllers contains ONLY:
1. **URL Route Parameters** - Values extracted from the route pattern (e.g., `:id`)
2. **GET Parameters** - Query string parameters
POST data is NOT included in `$params` and must be accessed via `$request`:
```php
// Route: /users/:id
// URL: /users/123?sort=name
// POST body: name=John&email=john@example.com
public static function update(Request $request, array $params = [])
{
// From URL route pattern
$user_id = $params['id']; // '123'
// From query string (GET)
$sort = $params['sort']; // 'name'
// From POST body (NOT in $params)
$name = $request->input('name'); // 'John'
$email = $request->post('email'); // 'john@example.com'
}
```
If the same parameter name exists in both route and query string, route parameter wins:
```php
// Route: /users/:id
// URL: /users/123?id=456
// 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.