[HN Gopher] Show HN: QuickBEAM - run JavaScript as supervised Er...
       ___________________________________________________________________
        
       Show HN: QuickBEAM - run JavaScript as supervised Erlang/OTP
       processes
        
       QuickBEAM is a JavaScript runtime embedded inside the Erlang/OTP
       VM.  If you're building a full-stack app, JavaScript tends to leak
       in anyway -- frontend, SSR, or third-party code.  QuickBEAM runs
       that JavaScript inside OTP supervision trees.  Each runtime is a
       process with a `Beam` global that can: - call Elixir code -
       send/receive messages - spawn and monitor processes - inspect
       runtime/system state  It also provides browser-style APIs backed by
       OTP/native primitives (fetch, WebSocket, Worker, BroadcastChannel,
       localStorage, native DOM, etc.).  This makes it usable for: - SSR -
       sandboxed user code - per-connection state - backend JS with direct
       OTP interop  Notable bits:  - JS runtimes are supervised and
       restartable - sandboxing with memory/reduction limits and API
       control - native DOM that Erlang can read directly (no string
       rendering step) - no JSON boundary between JS and Erlang - built-in
       TypeScript, npm support, and native addons  QuickBEAM is part of
       Elixir Volt -- a full-stack frontend toolchain built on Erlang/OTP
       with no Node.js.  Still early, feedback welcome.
        
       Author : dannote
       Score  : 61 points
       Date   : 2026-03-28 21:03 UTC (1 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | theflyinghorse wrote:
       | This is very interest to me because we have accumulated a few
       | node packages containing logic that services simply import. So in
       | theory I could now use those node packages in elixir?
        
         | dannote wrote:
         | Yes, if the packages are pure JS logic (no native C++ addons,
         | no Node-specific I/O like child_process or net). The script
         | option auto-resolves imports from node_modules/ and bundles via
         | OXC. Node compat APIs (process, path, fs, os, Buffer) are
         | available with apis: [:browser, :node]. For packages with
         | native .node addons, there's load_addon/3 which supports N-API.
        
       | jbpd924 wrote:
       | Interesting!! I've been playing around with QuickJS lately and
       | uses Elixir at work.
       | 
       | I'm interested to hear about your sandboxing approach running
       | untrusted JS code. So you are setting an memory/reduction limit
       | to the process which 100% is a good idea. What other defense-in-
       | depth strategies are you using? possible support for seccomp in
       | the future?
        
         | dannote wrote:
         | Layers right now:
         | 
         | -- Memory limits: JS_SetMemoryLimit per-runtime (256 MB
         | default), JS_SetContextMemoryLimit per-context. Exceeding - JS
         | exception, not a crash.
         | 
         | -- Execution limits: interrupt handler checks a nanosecond
         | deadline every opcode. For contexts, max_reductions caps JS
         | operations independently of wall-clock time.
         | 
         | -- API surface: apis: false gives bare QuickJS -- no fetch, no
         | fs, no DOM, no I/O. You control exactly which Elixir functions
         | JS can call via the handlers map. JS cannot call arbitrary
         | Elixir code.
         | 
         | -- Conversion limits: max_convert_depth (32) and
         | max_convert_nodes (10k) prevent pathological objects from
         | blowing up during JS-BEAM conversion.
         | 
         | -- Process isolation: separate OS thread, separate QuickJS heap
         | per runtime.
         | 
         | No seccomp -- QuickJS runs in-process so seccomp would restrict
         | the entire BEAM. The sandbox boundary is QuickJS-NG's memory-
         | safe interpreter (no JIT, no raw pointer access from JS) plus
         | the API surface control above.
        
       | waffleophagus wrote:
       | Running JS on the Beam VM, all written in C. I don't know if this
       | is just cursed, or absolutely brilliant, either way I love it and
       | will be following closely. Will definitely have to play with it.
        
         | dnautics wrote:
         | did you notice that the middleware between C and BEAM is in
         | zig! (disclaimer self promotion)
        
           | kvirani wrote:
           | Whoa! you have quite the profile.
        
       | dnautics wrote:
       | love this! a while back i noodled around with this idea, but
       | didn't get that far:
       | 
       | https://github.com/ityonemo/yavascript
       | 
       | glad to see someone do a fuller implementation!
        
       | hosh wrote:
       | 1. Are each of the JS processes running in its own process and
       | mailbox? (I assume from the description is that each runtime
       | instance is its own process)
       | 
       | 2. can the BEAM scheduler pre-empt the JS processes?
       | 
       | 3. How is memory garbage collected? Do the JS processes garbage
       | collect for each individual process?
       | 
       | 4. Are values within JS immutable?
       | 
       | 5. If they are not immutable, are there risk for memory errors?
       | And if there is a memory error, would it crash the JS process
       | without crashing the rest of the system?
        
         | dannote wrote:
         | 1. Yes. Each runtime is a GenServer (= own process + mailbox).
         | There's also a lighter-weight Context mode where many JS
         | contexts share one OS thread via a ContextPool, but each
         | context still maps 1:1 to a BEAM process.
         | 
         | 2. No. JS runs on a dedicated OS thread, outside the BEAM
         | scheduler. But there's an interrupt handler
         | (JS_SetInterruptHandler) that checks a deadline on every JS
         | opcode boundary -- pass timeout: 1000 to eval and it interrupts
         | after 1s, runtime stays usable. For contexts there's also
         | max_reductions -- QuickJS-NG counts JS operations and
         | interrupts when the budget runs out, closest analog to BEAM
         | reductions.
         | 
         | 3. QuickJS-NG uses refcounting with cycle detection. Each
         | runtime/context has its own GC -- one collecting doesn't touch
         | another. When a Runtime GenServer terminates, JS_FreeContext +
         | JS_FreeRuntime release everything.
         | 
         | 4. No, standard JS mutability. But the JS-Erlang boundary
         | copies values -- no shared mutable state across that boundary.
         | 
         | 5. QuickJS-NG enforces JS_SetMemoryLimit per-runtime (default
         | 256 MB) and JS_SetContextMemoryLimit per-context. Exceeding the
         | limit raises a JS exception, not a segfault. It propagates as
         | {:error, ...} to the caller. Since each runtime is a supervised
         | GenServer, the supervisor restarts it. There are tests for OOM
         | in one context not crashing the pool, and one runtime crashing
         | not affecting siblings.
        
       ___________________________________________________________________
       (page generated 2026-03-29 23:00 UTC)