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>
This commit is contained in:
root
2025-11-13 19:10:02 +00:00
parent fc494c1e08
commit 77b4d10af8
28155 changed files with 2191860 additions and 12967 deletions

121
docs/BREAKING_CHANGES.md Executable file
View File

@@ -0,0 +1,121 @@
# RSpade Framework - Breaking Changes
This document tracks breaking changes in the RSpade framework that require developers to update their application code.
## Format
Each breaking change entry includes:
- **Date**: When the change was introduced
- **Component**: Affected system/component
- **Change**: Description of what changed
- **Migration**: How to update existing code
- **Impact**: Severity (Critical/High/Medium/Low)
---
## 2025-11-03: Rsx_Form Automatic Submit Button Wiring
**Component**: `Rsx_Form` component
**Impact**: Medium - Requires manual code updates to existing forms
### Change
Rsx_Form now automatically wires all submit buttons (`button[type="submit"]`) to call the form's `submit()` method. Manual button wiring is no longer needed and should be removed.
### Previous Pattern
```javascript
class Frontend_Clients_Edit {
static init() {
// Wire save button to Rsx_Form component
$('#save-btn').on('click', function() {
const $form = $('.Rsx_Form').first();
const form_component = $form.component();
if (form_component) {
form_component.submit();
}
});
}
}
```
```blade
<button type="button" class="btn btn-primary" id="save-btn">
Save Changes
</button>
```
### New Pattern
```javascript
class Frontend_Clients_Edit {
static init() {
// No initialization needed - submit button automatically wired by Rsx_Form
}
}
```
```blade
<button type="submit" class="btn btn-primary">
Save Changes
</button>
```
### Migration Steps
1. **Update button markup**:
- Change `type="button"` to `type="submit"`
- Remove `id` attribute if only used for wiring
2. **Remove manual wiring code**:
- Delete the `$('#save-btn').on('click', ...)` handler
- If your init() function is now empty, you can leave it (it may be needed in the future) or add a comment explaining it's for future use
3. **Test form submission**:
- Verify the form still submits correctly
- Check validation error handling still works
### Benefits
- Less boilerplate code
- More semantic HTML (`type="submit"`)
- Consistent pattern across all forms
- Automatic event prevention (no need for `e.preventDefault()`)
### Notes
- This change only affects forms using the `Rsx_Form` component
- Custom submit logic (pre-submission validation, etc.) should now be handled differently - contact framework maintainer for patterns
- Forms with multiple submit buttons (e.g., "Save" vs "Save and Continue") will have all buttons wired - differentiate using button values or data attributes
---
## Template for Future Entries
```
## YYYY-MM-DD: Brief Description
**Component**: Affected component/system
**Impact**: Critical/High/Medium/Low
### Change
Description of what changed and why.
### Previous Pattern
[Code example showing old way]
### New Pattern
[Code example showing new way]
### Migration Steps
1. Step one
2. Step two
3. Step three
### Benefits
- Benefit one
- Benefit two
### Notes
Additional context or edge cases.
```

View File

@@ -81,6 +81,26 @@ $clean = Sanitizer::sanitize($input); // Let it throw
**SECURITY-CRITICAL**: If sanitization/validation/auth fails, NEVER continue. Always throw immediately.
**NO BLANKET TRY/CATCH**: Use try/catch only for expected failures (file uploads, external APIs, user input parsing). NEVER wrap database operations or entire functions "just in case".
```php
// ❌ WRONG - Defensive "on error resume"
try {
$user->save();
$result = process_data($user);
return $result;
} catch (Exception $e) {
throw new Exception("Failed: " . $e->getMessage(), 0, $e);
}
// ✅ CORRECT - Let exceptions bubble
$user->save();
$result = process_data($user);
return $result;
```
Exception handlers format errors for different contexts (Ajax JSON, CLI, HTML). Don't wrap exceptions with generic messages - let them bubble to the global handler.
### 🔴 No Defensive Coding
Core classes ALWAYS exist. Never check.
@@ -135,6 +155,24 @@ AI should follow the rule's guidance precisely. Rules are deliberately written a
| Database Tables | `lowercase_plural` | `users` |
| Constants | `UPPERCASE` | `MAX_SIZE` |
### File Prefix Grouping
**Critical insight**: Files with the same prefix are conceptually grouped and should stay grouped!
When multiple files share a common prefix in the same directory, they form a visual group that indicates they work together. This grouping must be preserved when renaming files.
**Example** - `rsx/app/frontend/calendar/`:
```
frontend_calendar_event.scss
frontend_calendar_event_controller.php
frontend_calendar_event.blade.php
frontend_calendar_event.js
```
All share prefix `frontend_calendar_event` → they're a related set! If renaming to remove redundancy, ALL files in the group must be renamed consistently to maintain the visual relationship.
**Why this matters**: Developers scan file listings and use shared prefixes to quickly identify which files work together. Breaking this pattern creates cognitive overhead and makes the codebase harder to navigate.
**Critical**: Never create same-name different-case files.
---
@@ -209,16 +247,31 @@ class Frontend_Controller extends Rsx_Controller_Abstract
### Type-Safe URLs
**MANDATORY**: All URLs must be generated using `Rsx::Route()` - hardcoded URLs are forbidden.
```php
// PHP
// PHP - Basic usage
Rsx::Route('User_Controller', 'show', ['id' => 123]);
Rsx::Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
// JavaScript (identical)
// PHP - With query parameters (third argument)
Rsx::Route('Login_Controller', 'logout', ['redirect' => '/dashboard']);
// Generates: /logout?redirect=%2Fdashboard
// JavaScript (identical syntax)
Rsx.Route('User_Controller', 'show', {id: 123});
Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
Rsx.Route('Login_Controller', 'logout', {redirect: '/dashboard'});
```
**Query Parameters**: Pass associative array/object as third argument:
- Keys become query parameter names
- Values are automatically URL-encoded
- Framework handles proper URL construction
**Enforcement**: `rsx:check` will flag hardcoded URLs like `/login` or `/logout?redirect=...`
and require you to use `Rsx::Route()`. Do it right the first time to avoid rework.
---
## BLADE & VIEWS
@@ -231,6 +284,29 @@ Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
@rsx_include('Component_Name') {{-- Include by name, not path --}}
```
**NO inline styles, scripts, or event handlers** in Blade views:
- No `<style>` tags
- No `<script>` tags
- No inline event handlers: `onclick=""`, `onchange=""`, etc. are **forbidden**
- Use companion `.scss` and `.js` files instead
- Exception: jqhtml templates may use `@click` directive syntax (jqhtml-specific feature)
**Why no inline handlers**:
- Violates separation of concerns (HTML structure vs behavior)
- Makes code harder to maintain and test
- `rsx:check` will flag inline event handlers and require refactoring
**Correct pattern**:
```php
// Blade view - NO event handlers
<button id="submit-btn" class="btn btn-primary">Submit</button>
// Companion .js file
$('#submit-btn').click(() => {
// Handle click
});
```
### SCSS Pairing
```scss
@@ -240,6 +316,25 @@ Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
}
```
### Passing Data to JavaScript
Use `@rsx_page_data` for page-specific data needed by JavaScript (IDs, config, etc.):
```blade
@rsx_page_data(['user_id' => $user->id, 'mode' => 'edit'])
@section('content')
<!-- Your HTML -->
@endsection
```
Access in JavaScript:
```javascript
const user_id = window.rsxapp.page_data.user_id;
```
Use when data doesn't belong in DOM attributes. Multiple calls merge together.
---
## JAVASCRIPT
@@ -403,7 +498,7 @@ this.$id('button').on('click', ...); // Access scoped element
3. ONLY modify `this.data` in `on_load()`
4. Use `Controller.method()` not `$.ajax()`
5. Blade components self-closing only
6. `on_create/render/destroy` must be sync
6. `on_create/render/stop` must be sync
### Bundle Integration Required
@@ -418,7 +513,30 @@ For advanced topics: `php artisan rsx:man jqhtml`
## FORM COMPONENTS
Form components use the **vals() dual-mode pattern** for getting/setting values:
**Form fields** (`<Rsx_Form>` with `$data`, `$controller`, `$method`):
```blade
<Rsx_Form $data="{!! json_encode($form_data) !!}" $controller="Controller" $method="save">
<Form_Field $name="email" $label="Email" $required=true>
<Text_Input $type="email" />
</Form_Field>
<Form_Field_Hidden $name="id" />
</Rsx_Form>
```
- **Form_Field** - Standard formatted field with label, errors, help text
- **Form_Field_Hidden** - Single-tag hidden input (extends Form_Field_Abstract)
- **Form_Field_Abstract** - Base class for custom formatting (advanced)
**Disabled fields**: Use `$disabled=true` attribute on input components to disable fields. Unlike standard HTML, disabled fields still return values via `vals()` (useful for read-only data that should be submitted).
```blade
<Text_Input $type="email" $disabled=true />
<Select_Input $options="{!! json_encode($options) !!}" $disabled=true />
<Checkbox_Input $label="Subscribe" $disabled=true />
```
**Form component classes** use the **vals() dual-mode pattern**:
```javascript
class My_Form extends Jqhtml_Component {
@@ -426,14 +544,10 @@ class My_Form extends Jqhtml_Component {
if (values) {
// Setter - populate form
this.$id('name').val(values.name || '');
this.$id('email').val(values.email || '');
return null;
} else {
// Getter - extract values
return {
name: this.$id('name').val(),
email: this.$id('email').val()
};
return {name: this.$id('name').val()};
}
}
}
@@ -474,6 +588,27 @@ const result = await Modal.form({
**Requirements**: Form component must implement `vals()` and include `<div $id="error_container"></div>`.
**Modal Classes** (for complex/reusable modals):
```javascript
// Define modal class
class Add_User_Modal extends Modal_Abstract {
static async show() {
const result = await Modal.form({...});
return result || false;
}
}
// Use from page JS
const user = await Add_User_Modal.show();
if (user) {
// Orchestrate post-modal actions
grid.reload();
await Next_Modal.show(user.id);
}
```
Pattern: Extend `Modal_Abstract`, implement static `show()`, return data or `false`. Page JS orchestrates flow, modal classes encapsulate UI.
Details: `php artisan rsx:man modals`
---
@@ -593,6 +728,113 @@ php artisan migrate:commit
---
## FILE ATTACHMENTS
**Upload Flow**: Files upload UNATTACHED → validate → assign via API
### Security Model
Session-based validation prevents cross-user file assignment:
- Files get `session_id` automatically on upload
- `can_user_assign_this_file()` validates: not assigned, same site, same session
- User-provided `fileable_*` params are ignored during upload
### Attachment API
```php
// Find attachment by key from form submission
$attachment = File_Attachment_Model::find_by_key($params['photo_key']);
// Validate user can assign this file
if ($attachment && $attachment->can_user_assign_this_file()) {
// Single file (replaces existing in category)
$attachment->attach_to($user, 'profile_photo');
// OR multiple files (adds to collection)
$attachment->add_to($project, 'documents');
}
// Remove assignment without deleting file
$attachment->detach();
```
### Model Helpers
```php
// Get single attachment (for profile photos, logos, etc)
$photo = $user->get_attachment('profile_photo');
if ($photo) {
echo $photo->get_thumbnail_url('cover', 128, 128);
}
// Get multiple attachments (for documents, galleries, etc)
$documents = $project->get_attachments('documents');
foreach ($documents as $doc) {
echo $doc->file_name;
}
```
### Display
```php
$photo->get_thumbnail_url('cover', 128, 128) // Thumbnail
$photo->get_url() // Full file
$photo->get_download_url() // Force download
```
### Endpoints
- `POST /_upload` - Upload file (returns attachment key)
- `GET /_download/:key` - Download file
- `GET /_thumbnail/:key/:type/:width/:height` - Generate thumbnail
### Complete Workflow
```javascript
// Frontend: Upload file
$.ajax({
url: '/_upload',
type: 'POST',
data: formData,
success: function(response) {
$('#photo_key').val(response.attachment.key);
}
});
```
```php
// Backend: Save form with attachment
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
$user = Session::get_user();
if (!empty($params['photo_key'])) {
$attachment = File_Attachment_Model::find_by_key($params['photo_key']);
if ($attachment && $attachment->can_user_assign_this_file()) {
$attachment->attach_to($user, 'profile_photo');
}
}
return ['success' => true];
}
```
```blade
{{-- Display attachment --}}
@php
$photo = $user->get_attachment('profile_photo');
@endphp
@if($photo)
<img src="{{ $photo->get_thumbnail_url('cover', 128, 128) }}">
@else
<div>No photo</div>
@endif
```
**Details**: `php artisan rsx:man file_upload`
---
## AJAX ENDPOINTS
```php
@@ -839,6 +1081,31 @@ public static function show_user(Request $request, array $params = [])
}
```
**IMPORTANT**: `$params` contains ONLY GET and route parameters. POST data is accessed via `$request`:
```php
public static function update(Request $request, array $params = [])
{
$user_id = $params['id']; // From URL route: /users/:id
$sort = $params['sort']; // From query: ?sort=name
$name = $request->input('name'); // From POST body
$email = $request->post('email'); // From POST body
}
```
### File Responses
Use Response facade for file downloads/viewing:
```php
// Force download
return Response::download($path, $filename, $headers);
// Display inline (browser)
return Response::file($path, $headers);
```
### Controller-Wide Auth
```php

View File

@@ -1,102 +0,0 @@
# 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)

View File

@@ -1,734 +0,0 @@
# 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

@@ -1,96 +0,0 @@
# 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.

View File

@@ -1,669 +0,0 @@
# 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.