Files
rspade_system/docs/skills/js-decorators/SKILL.md
root 1b46c5270c Add skills documentation and misc updates
Add form value persistence across cache revalidation re-renders

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-29 04:38:06 +00:00

5.5 KiB
Executable File

name, description
name description
js-decorators RSX JavaScript decorators including @route, @spa, @layout, @mutex, and custom decorator creation. Use when adding route decorators to actions, understanding decorator restrictions, creating custom decorators, or troubleshooting decorator errors.

RSX JavaScript Decorators

Overview

RSX uses decorators to enhance static methods. Unlike standard JavaScript, RSX requires explicit whitelisting via @decorator marker to prevent arbitrary code injection.

Key difference: Only functions marked with @decorator can be used as decorators elsewhere.


Common Framework Decorators

@route - SPA Routing

Defines URL paths for SPA actions:

@route('/contacts')
class Contacts_Index_Action extends Spa_Action { }

@route('/contacts/:id')
class Contacts_View_Action extends Spa_Action { }

// Dual routes (add/edit in one action)
@route('/contacts/add')
@route('/contacts/:id/edit')
class Contacts_Edit_Action extends Spa_Action { }

@spa - SPA Bootstrap

Links action to its SPA bootstrap controller:

@route('/contacts')
@spa('Frontend_Spa_Controller::index')
class Contacts_Index_Action extends Spa_Action { }

@layout - Layout Assignment

Assigns layout(s) to action:

@route('/contacts')
@layout('Frontend_Layout')
@spa('Frontend_Spa_Controller::index')
class Contacts_Index_Action extends Spa_Action { }

// Nested layouts (sublayouts)
@route('/settings/general')
@layout('Frontend_Layout')
@layout('Settings_Layout')
@spa('Frontend_Spa_Controller::index')
class Settings_General_Action extends Spa_Action { }

@mutex - Mutual Exclusion

Prevents concurrent execution of async methods:

class My_Component extends Component {
    @mutex()
    async save() {
        // Only one save() can run at a time
        await Controller.save(this.vals());
    }
}

Decorator Restrictions

Static Methods Only

Decorators can only be applied to static methods, not instance methods:

class Example {
    @myDecorator
    static validMethod() { }      // ✅ Valid

    @myDecorator
    invalidMethod() { }           // ❌ Invalid - instance method
}

No Class Name Identifiers in Parameters

Decorator parameters must not use class name identifiers (bundle ordering issues):

// WRONG - class identifier as parameter
@some_decorator(User_Model)
class My_Action extends Spa_Action { }

// CORRECT - use string literal
@some_decorator('User_Model')
class My_Action extends Spa_Action { }

Why: Bundle compilation doesn't guarantee class definition order for decorator parameters.

Multiple Decorators

Execute in reverse order (bottom to top):

@logExecutionTime      // Executes second (outer)
@validateParams        // Executes first (inner)
static transform(data) { }

Creating Custom Decorators

Basic Decorator

@decorator
function myCustomDecorator(target, key, descriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args) {
        console.log(`Calling ${key}`);
        return original.apply(this, args);
    };
    return descriptor;
}

Using Custom Decorator

class MyClass {
    @myCustomDecorator
    static myMethod() {
        return "result";
    }
}

Common Decorator Patterns

Logging

@decorator
function logCalls(target, key, descriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args) {
        console.log(`Calling ${target.name}.${key}`, args);
        const result = original.apply(this, args);
        console.log(`${target.name}.${key} returned`, result);
        return result;
    };
    return descriptor;
}

Async Error Handling

@decorator
function catchAsync(target, key, descriptor) {
    const original = descriptor.value;
    descriptor.value = async function(...args) {
        try {
            return await original.apply(this, args);
        } catch (error) {
            console.error(`Error in ${key}:`, error);
            throw error;
        }
    };
    return descriptor;
}

Rate Limiting

@decorator
function rateLimit(target, key, descriptor) {
    const calls = new Map();
    const original = descriptor.value;
    descriptor.value = function(...args) {
        const now = Date.now();
        const lastCall = calls.get(key) || 0;
        if (now - lastCall < 1000) {
            throw new Error(`${key} called too frequently`);
        }
        calls.set(key, now);
        return original.apply(this, args);
    };
    return descriptor;
}

Decorator Function Signature

function myDecorator(target, key, descriptor) {
    // target: The class being decorated
    // key: The method name being decorated
    // descriptor: Property descriptor for the method

    const original = descriptor.value;
    descriptor.value = function(...args) {
        // Pre-execution logic
        const result = original.apply(this, args);
        // Post-execution logic
        return result;
    };
    return descriptor;
}

Important: Always use original.apply(this, args) to preserve context.


Troubleshooting

Error Solution
"Decorator 'X' not whitelisted" Add @decorator marker to function
"Decorator 'X' not found" Ensure decorator loaded before usage
"Decorators can only be applied to static methods" Change to static or remove decorator

More Information

Details: php artisan rsx:man js_decorators