Skip to content

Latest commit

 

History

History
191 lines (154 loc) · 7.79 KB

File metadata and controls

191 lines (154 loc) · 7.79 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Node-RC is a high-performance runtime controller for executing code across multiple programming languages (Python, JavaScript, TypeScript, C, Java, Ruby, Go) from TypeScript. It uses Rust via NAPI bindings for maximum performance. JavaScript and TypeScript use QuickJS embedded interpreter for fast cold starts (<200μs) and predictable performance metrics ideal for AI agent tracing.

Critical Prerequisites

LLVM 18 is REQUIRED - This is a hard dependency that must be installed and properly configured:

# macOS
brew install llvm@18
export LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)

# Linux
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 18
export LLVM_SYS_180_PREFIX=/usr/lib/llvm-18

# Add to ~/.bashrc or ~/.zshrc to persist

Key Commands

# ALWAYS set LLVM path first
export LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)  # macOS
# export LLVM_SYS_180_PREFIX=/usr/lib/llvm-18         # Linux

# Build everything (native + TypeScript)
npm run build

# Build components separately
npm run build:native  # Rust NAPI bindings (requires LLVM 18 + Rust toolchain)
npm run build:ts      # TypeScript compilation

# Testing
npm test              # Run all tests
npm run test:watch    # Watch mode
npm run test:coverage # With coverage report
jest path/to/test.ts  # Run specific test file

# Performance benchmarks
npm run bench

# Development workflow (typical order)
export LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)  # Set LLVM first
cd native && cargo check    # Quick Rust syntax check
npm run build:native        # Build Rust bindings
npm run build:ts           # Compile TypeScript
npm test                   # Verify everything works

Testing specific languages

jest --testNamePattern="Python"    # Test Python interpreter only
jest --testNamePattern="JavaScript" # Test JavaScript interpreter only
jest --testNamePattern="Ruby"      # Test Ruby interpreter only
# etc...

Architecture

Two-Layer Design

  1. TypeScript API Layer (/src)

    • RuntimeController class provides the main entry point
    • Each language has its own interpreter class extending BaseInterpreter
    • Simple API design: rc.PythonInterpreter().execute(code)
    • All interpreters share common interfaces from types.ts
  2. Rust NAPI Layer (/native)

    • Actual runtime implementations using native libraries
    • lib.rs exposes NAPI bindings to TypeScript
    • Each language has its own runtime module:
      • python_runtime.rs → PyO3 for native Python execution
      • javascript_runtime.rs → Porffor AOT compiler to WASM
      • typescript_runtime.rs → Porffor native TypeScript to WASM
      • c_runtime.rs → LLVM/clang to WASM compilation
      • java_runtime.rs → javac compilation with WASM execution
      • ruby_runtime.rs → System Ruby or mruby
      • go_runtime.rs → Go/TinyGo to WASM compilation
      • wasm_runtime.rs → Wasmtime for WASM execution

Critical Design Patterns

  1. Lazy Initialization: Native NAPI bindings are loaded lazily on first use to avoid startup costs
  2. Interpreter Lifecycle: Each interpreter maintains an instanceId for tracking in Rust
  3. Execution Flow: TypeScript → NAPI call → Rust runtime → Result with metrics
  4. Native Module Structure:
    • native/index.js - Platform-specific loader for .node files
    • native/package.json - NAPI configuration and build metadata
    • native/build.rs - Python linking configuration for PyO3
  5. Controller Pattern: RuntimeController manages native binding initialization and interpreter creation
  6. Tracing: When enabled, captures variables, call stacks, and memory snapshots
  7. Sandboxing: Three levels (strict/moderate/relaxed) enforced at Rust level
  8. WASM Compilation: C, Java, and Go compile to WASM for sandboxed execution
  9. Error Handling: All runtimes gracefully handle missing dependencies (e.g., clang, javac)

Language Runtime Status

Language Runtime Implementation Status
Python PyO3 ✅ Fully functional
JavaScript QuickJS embedded ✅ Production ready (full ECMA-262)
TypeScript QuickJS + type stripping ✅ Production ready (regex-based TS support)
C LLVM/clang → WASM ✅ Fully functional
Java javac + WASM wrapper ✅ Fully functional
Ruby System Ruby / mruby ✅ Fully functional
Go Go/TinyGo → WASM ✅ Fully functional

Common Development Tasks

Running a single test

jest src/__tests__/runtime-controller.test.ts -t "should execute simple Python code"

Debugging native bindings

RUST_BACKTRACE=1 npm test  # Get full backtrace on Rust panics
RUST_LOG=debug npm test    # Enable Rust debug logging

# Rebuild after Rust changes
cd native && cargo clean && cd .. && npm run build:native

Debugging initialization issues

# If getting "cannot find module" errors for native binding
npm run build:native      # Ensure native module is built
ls native/*.node          # Verify .node files exist

# If getting Python symbol errors
export LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)  # Set LLVM path
cd native && cargo clean && cargo build --release     # Clean rebuild

Checking for memory leaks

npm run test:coverage -- --detectLeaks

Development Notes

Rust Development

  • LLVM 18 is REQUIRED - Must be installed and LLVM_SYS_180_PREFIX set for inkwell crate
  • Python runtime requires PyO3 and Python dev headers
  • Java runtime requires javac (JDK) installed
  • Ruby runtime requires Ruby or mruby installed
  • Go runtime requires Go or TinyGo installed
  • Use cargo check for quick syntax validation before full build
  • Release builds use aggressive optimizations (LTO, strip, opt-level=3)
  • build.rs handles Python linking configuration for PyO3 integration
  • Native module files (native/index.js, native/package.json) are critical for NAPI builds

TypeScript Development

  • Lazy Initialization Pattern: Native bindings loaded via controller.getNativeController()
  • RuntimeController handles native module initialization and lifecycle
  • Base interpreter class provides common functionality (tracing, error handling, etc.)
  • All interpreter classes extend BaseInterpreter in src/interpreters/base.ts
  • Each interpreter has unique instanceId for Rust-side tracking
  • All public types exported from src/types.ts
  • Tests use Jest with ts-jest preset

Adding New Languages

  1. Create Rust runtime in native/src/{language}_runtime.rs
  2. Add runtime module to native/src/lib.rs (import and execution match)
  3. Add compile/execute methods to native/src/lib.rs RuntimeController impl
  4. Create TypeScript interpreter in src/interpreters/{language}.ts extending BaseInterpreter
  5. Add language to Language enum in src/types.ts
  6. Export from src/interpreters/index.ts
  7. Add factory method to RuntimeController class in src/index.ts
  8. Write tests in src/__tests__/runtime-controller.test.ts
  9. Update native type definitions in native/index.d.ts

Performance Targets

  • Cold start: <1ms (achieving 100-200μs for JS/TS with QuickJS, 100-1000μs for others)
  • Memory overhead: <10MB per interpreter (1-2MB for JS/TS with QuickJS)
  • Concurrent execution: 100+ interpreters
  • Compilation time: <5ms for QuickJS validation, <50ms for WASM compilation

Known Limitations

  • Windows support untested
  • QuickJS has full ECMA-262 support but runs as embedded interpreter (not compiled)
  • WASI threading limitations (single-core only)
  • TeaVM integration pending for improved Java → WASM compilation
  • Native bindings must be built locally (not pre-compiled)
  • TypeScript support uses regex-based type stripping (not full TSC)