{"id":34611187,"url":"https://github.com/roadrunner-plugins/js-machine","last_synced_at":"2026-05-26T12:05:04.558Z","repository":{"id":323734042,"uuid":"1094472672","full_name":"roadrunner-plugins/js-machine","owner":"roadrunner-plugins","description":"[concept] A simple RoadRunner plugin that executes JavaScript code using the otto JavaScript interpreter.","archived":false,"fork":false,"pushed_at":"2025-11-13T06:23:44.000Z","size":43,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-26T02:58:05.082Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://build.roadrunner.dev/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roadrunner-plugins.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-11T18:50:50.000Z","updated_at":"2025-11-13T06:23:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/roadrunner-plugins/js-machine","commit_stats":null,"previous_names":["roadrunner-plugins/js-machine"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/roadrunner-plugins/js-machine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roadrunner-plugins%2Fjs-machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roadrunner-plugins%2Fjs-machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roadrunner-plugins%2Fjs-machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roadrunner-plugins%2Fjs-machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roadrunner-plugins","download_url":"https://codeload.github.com/roadrunner-plugins/js-machine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roadrunner-plugins%2Fjs-machine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33519228,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T03:12:49.672Z","status":"ssl_error","status_checked_at":"2026-05-26T03:12:47.976Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-24T14:13:24.278Z","updated_at":"2026-05-26T12:05:04.524Z","avatar_url":"https://github.com/roadrunner-plugins.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JavaScript Machine Plugin\n\nA simple RoadRunner plugin that executes JavaScript code using the [otto](https://github.com/robertkrimen/otto)\nJavaScript interpreter.\n\n## Features\n\n- **VM Pool**: Manages a pool of JavaScript VMs for concurrent execution\n- **Timeout Control**: Configurable execution timeouts to prevent runaway scripts\n- **Simple RPC Interface**: Single RPC method for executing JavaScript code\n- **Prometheus Metrics**: Comprehensive observability with execution stats, latency, and pool utilization\n- **Graceful Shutdown**: Properly handles shutdown with active execution cleanup\n\n## Installation\n\n```bash\ngo get github.com/roadrunner-plugins/js-machine\n```\n\n## Configuration\n\nAdd to your `.rr.yaml`:\n\n```yaml\njs:\n  pool_size: 4              # Number of JavaScript VMs in pool (default: 4)\n  max_memory_mb: 512        # Memory limit per VM (default: 512)\n  default_timeout_ms: 30000 # Default execution timeout in ms (default: 30000)\n```\n\n**Note**: Configuration is optional. If not specified, the plugin will use default values.\n\n## RPC Interface\n\n### Execute Method\n\nExecutes JavaScript code and returns the result.\n\n**Request Structure:**\n\n```go\ntype ExecuteRequest struct {\nCode       string `json:\"code\"`       // JavaScript code to execute\nTimeoutMs  int    `json:\"timeout_ms\"` // Execution timeout (optional)\nRequestID  string `json:\"request_id,omitempty\"` // Request correlation ID\n}\n```\n\n**Response Structure:**\n\n```go\ntype ExecuteResponse struct {\nResult     interface{} `json:\"result\"`          // Execution result\nDurationMs int64       `json:\"duration_ms\"`     // Execution time\nError      string      `json:\"error,omitempty\"` // Error message if failed\nRequestID  string      `json:\"request_id,omitempty\"` // Request correlation ID\n}\n```\n\n## PHP Usage\n\n### Basic Example\n\n```php\n\u003c?php\n\nuse Spiral\\Goridge\\RPC\\RPC;\nuse Spiral\\Goridge\\RPC\\Codec\\JsonCodec;\n\n$rpc = new RPC(\n    RPC::create('tcp://127.0.0.1:6001')\n        -\u003ewithCodec(new JsonCodec())\n);\n\n// Execute JavaScript\n$response = $rpc-\u003ecall('js.Execute', [\n    'code' =\u003e 'var result = 2 + 2; result;',\n    'timeout_ms' =\u003e 5000,\n    'request_id' =\u003e 'req-123'\n]);\n\necho \"Result: \" . $response['result'] . \"\\n\";        // 4\necho \"Duration: \" . $response['duration_ms'] . \"ms\\n\";\n```\n\n### Complex Calculation\n\n```php\n$response = $rpc-\u003ecall('js.Execute', [\n    'code' =\u003e '\n        function fibonacci(n) {\n            if (n \u003c= 1) return n;\n            return fibonacci(n - 1) + fibonacci(n - 2);\n        }\n        fibonacci(10);\n    ',\n    'timeout_ms' =\u003e 1000\n]);\n\necho \"Fibonacci(10) = \" . $response['result'] . \"\\n\"; // 55\n```\n\n### JSON Data Processing\n\n```php\n$response = $rpc-\u003ecall('js.Execute', [\n    'code' =\u003e '\n        var data = [\n            { name: \"Alice\", age: 30 },\n            { name: \"Bob\", age: 25 },\n            { name: \"Charlie\", age: 35 }\n        ];\n        \n        var adults = data.filter(function(person) {\n            return person.age \u003e= 30;\n        });\n        \n        JSON.stringify(adults);\n    '\n]);\n\n$adults = json_decode($response['result'], true);\nprint_r($adults);\n```\n\n### Error Handling\n\n```php\n$response = $rpc-\u003ecall('js.Execute', [\n    'code' =\u003e 'throw new Error(\"Something went wrong\");'\n]);\n\nif (!empty($response['error'])) {\n    echo \"JavaScript Error: \" . $response['error'] . \"\\n\";\n}\n```\n\n## Laravel Integration\n\n### Service Provider\n\nCreate `app/Providers/JavaScriptServiceProvider.php`:\n\n```php\n\u003c?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Spiral\\Goridge\\RPC\\RPC;\nuse Spiral\\Goridge\\RPC\\Codec\\JsonCodec;\n\nclass JavaScriptServiceProvider extends ServiceProvider\n{\n    public function register()\n    {\n        $this-\u003eapp-\u003esingleton('js', function ($app) {\n            $rpc = new RPC(\n                RPC::create(config('roadrunner.rpc_address', 'tcp://127.0.0.1:6001'))\n                    -\u003ewithCodec(new JsonCodec())\n            );\n            \n            return new \\App\\Services\\JavaScriptService($rpc);\n        });\n    }\n}\n```\n\n### Service Class\n\nCreate `app/Services/JavaScriptService.php`:\n\n```php\n\u003c?php\n\nnamespace App\\Services;\n\nuse Spiral\\Goridge\\RPC\\RPCInterface;\n\nclass JavaScriptService\n{\n    private RPCInterface $rpc;\n    \n    public function __construct(RPCInterface $rpc)\n    {\n        $this-\u003erpc = $rpc;\n    }\n    \n    public function execute(string $code, int $timeoutMs = 5000, string $requestId = null): array\n    {\n        return $this-\u003erpc-\u003ecall('js.Execute', [\n            'code' =\u003e $code,\n            'timeout_ms' =\u003e $timeoutMs,\n            'request_id' =\u003e $requestId ?? uniqid('js-', true)\n        ]);\n    }\n    \n    public function eval(string $code): mixed\n    {\n        $response = $this-\u003eexecute($code);\n        \n        if (!empty($response['error'])) {\n            throw new \\RuntimeException($response['error']);\n        }\n        \n        return $response['result'];\n    }\n}\n```\n\n### Usage in Controllers\n\n```php\n\u003c?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Services\\JavaScriptService;\n\nclass CalculationController extends Controller\n{\n    public function calculate(JavaScriptService $js)\n    {\n        $result = $js-\u003eeval('\n            function calculate(x, y) {\n                return (x * y) + (x / y);\n            }\n            calculate(10, 5);\n        ');\n        \n        return response()-\u003ejson(['result' =\u003e $result]);\n    }\n}\n```\n\n## Architecture\n\n### VM Pool Management\n\nThe plugin maintains a pool of otto JavaScript VMs:\n\n```\n┌─────────────────────────────────────┐\n│      JavaScript Plugin              │\n│                                     │\n│  ┌───────────────────────────────┐ │\n│  │      VM Pool (Channel)        │ │\n│  │                               │ │\n│  │  ┌──────┐  ┌──────┐          │ │\n│  │  │ VM 1 │  │ VM 2 │  ...     │ │\n│  │  └──────┘  └──────┘          │ │\n│  └───────────────────────────────┘ │\n│                                     │\n│  PHP Worker ──RPC──\u003e Execute()      │\n│                  ↓                  │\n│            Acquire VM from Pool     │\n│                  ↓                  │\n│            Run JavaScript           │\n│                  ↓                  │\n│            Return VM to Pool        │\n│                                     │\n│  Prometheus Metrics:                │\n│  - js_executions_total              │\n│  - js_execution_duration_seconds    │\n│  - js_pool_available                │\n│  - js_active_executions             │\n└─────────────────────────────────────┘\n```\n\n### Execution Flow\n\n1. **Request Received**: PHP sends JavaScript code via RPC\n2. **VM Acquisition**: Plugin acquires a VM from the pool (blocks if all busy)\n3. **Timeout Setup**: Creates context with timeout and watchdog goroutine\n4. **Execution**: Runs JavaScript in separate goroutine\n5. **Result Return**: Converts otto.Value to Go interface{} and returns\n6. **VM Release**: Returns VM to pool for reuse\n\n### Timeout Mechanism\n\n```go\n// Watchdog goroutine monitors execution\ngo func () {\nselect {\ncase \u003c-execCtx.Done():\n// Timeout occurred, interrupt VM\nvm.Interrupt \u003c- func () {\npanic(\"execution timeout\")\n}\ncase \u003c-watchdogDone:\n// Execution completed normally\n}\n}()\n```\n\n## Limitations\n\n### Otto Engine Limitations\n\n- **ECMAScript 5.1**: Does not support ES6+ features (let, const, arrow functions, classes)\n- **No Async/Await**: Promises and async patterns not supported\n- **Limited Stdlib**: No Node.js modules or browser APIs\n- **Regexp Limitations**: Uses Go's regexp engine (no lookaheads/lookbehinds)\n\n### Performance Considerations\n\n- **Single-threaded**: Each VM executes one script at a time\n- **Pool Size**: Adjust `pool_size` based on CPU cores and workload\n- **Memory**: Each VM consumes ~20MB base memory\n- **Timeout**: Always set reasonable timeouts to prevent resource exhaustion\n\n## Security Considerations\n\n### Code Execution Risks\n\n⚠️ **Warning**: This plugin executes arbitrary JavaScript code. Only execute trusted code.\n\n**Recommendations**:\n\n- Run RoadRunner in isolated environment (container, VM)\n- Set strict resource limits (memory, timeout)\n- Validate/sanitize input before execution\n- Monitor execution metrics for anomalies\n\n### Future Enhancements (Out of Scope)\n\nThe following features are intentionally excluded from this minimal implementation:\n\n- **Metrics**: Prometheus integration for execution stats\n- **Go Bindings**: HTTP client, logging, cache access from JavaScript\n- **Script Registry**: Pre-loaded named functions\n- **Async Execution**: Fire-and-forget mode with job tracking\n- **Sandboxing**: Restricted filesystem/network access\n- **ES6+ Support**: Requires different JavaScript engine (V8, QuickJS)\n\n## Troubleshooting\n\n### VM Pool Exhaustion\n\n**Symptom**: Requests timeout waiting for available VM\n\n**Metrics**: Check `js_pool_available` gauge (should be \u003e 0)\n\n**Solution**: Increase `pool_size` in configuration\n\n```yaml\njs:\n  pool_size: 8  # Increase from default 4\n```\n\n### Memory Issues\n\n**Symptom**: RoadRunner OOM or high memory usage\n\n**Metrics**: Monitor `js_pool_size * max_memory_mb` total\n\n**Solution**: Reduce pool size or implement VM rotation\n\n```yaml\njs:\n  pool_size: 2\n  max_memory_mb: 256\n```\n\n### High Error Rate\n\n**Symptom**: Many failed executions\n\n**Metrics**: Check `js_executions_total{status=\"error\"}`\n\n**Investigation**:\n\n- Review error logs\n- Verify JavaScript syntax\n- Check timeout configuration\n\n### Performance Degradation\n\n**Symptom**: Slow execution times\n\n**Metrics**: Monitor `js_execution_duration_seconds` percentiles\n\n**Investigation**:\n\n- Check `js_code_size_bytes` for large scripts\n- Review `js_active_executions` for high concurrency\n- Verify no resource contention\n\n## Metrics \u0026 Monitoring\n\nThe plugin exposes comprehensive Prometheus metrics. See [METRICS.md](METRICS.md) for detailed documentation including:\n\n- All available metrics (counters, histograms, gauges)\n- PromQL query examples\n- Grafana dashboard templates\n- Alerting rules for production monitoring\n\n**Quick Start**: Enable metrics in `.rr.yaml`:\n\n```yaml\nmetrics:\n  address: 127.0.0.1:2112\n\njs:\n  pool_size: 4\n```\n\nAccess metrics at `http://localhost:2112/metrics`\n\nKey metrics:\n\n- `js_executions_total` - Total executions by status\n- `js_execution_duration_seconds` - Latency distribution\n- `js_pool_available` - Available VMs\n- `js_active_executions` - Current concurrency\n\n### Syntax Errors\n\n**Symptom**: `SyntaxError` in response\n\n**Cause**: Invalid JavaScript or unsupported ES6+ syntax\n\n**Solution**: Use ES5.1 syntax only\n\n```javascript\n// ❌ ES6 - Not supported\nconst result = (x) =\u003e x * 2;\n\n// ✅ ES5 - Supported\nvar result = function (x) {\n    return x * 2;\n};\n```\n\n## License\n\nMIT\n\n## Contributing\n\nThis is a minimal reference implementation. For production use, consider:\n\n- Adding comprehensive metrics\n- Implementing Go function bindings\n- Adding script caching\n- Supporting async execution modes\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froadrunner-plugins%2Fjs-machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froadrunner-plugins%2Fjs-machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froadrunner-plugins%2Fjs-machine/lists"}