// the find
elixir-volt/quickbeam
JavaScript runtime for the BEAM — Web APIs backed by OTP, native DOM, and a built-in TypeScript toolchain.
QuickBEAM embeds a JavaScript/TypeScript runtime inside the BEAM, exposing QuickJS through Zig NIFs with a native DOM, OTP-aware bridging, and a second pure-Elixir bytecode interpreter for sandboxed execution. It's for Elixir/Phoenix teams who need JS execution (SSR, business rules, scripting) without shelling out to Node, and who want that execution to behave like any other OTP process — supervised, monitorable, resource-bounded.
The two-tier execution model is the real idea here: a native QuickJS NIF for full-featured, fast execution, plus a separate interpreter written entirely in Elixir for untrusted code, so a crash or runaway script in sandboxed mode can't take down the native engine or the node. Context pooling is a legitimate answer to the 'one OS thread per JS context' problem — the README's own numbers (30GB vs ~4.2GB at 10k concurrent contexts) are a believable, specific claim, not a marketing number. Backing the DOM with lexbor instead of a JS-side polyfill means Elixir can query/read the DOM directly as Floki-style tuples without round-tripping through the VM, which is a genuine architectural win for SSR. Bundling an OXC-based TypeScript toolchain removes Node/esbuild entirely from a BEAM app's JS build path.
The sandboxed interpreter is explicitly a 'bounded, tested JavaScript subset' with bytecode locked to an exact vendored QuickJS version and ABI fingerprint — upgrade QuickJS and every pinned program needs recompiling, which is a real operational tax for anyone depending on the safe path specifically because it's safe. The native NIF path (the fast, full-featured one) is still foreign C/Zig code running in-process; the README doesn't say what happens to the BEAM node if that native engine segfaults, which matters a lot for a library whose whole pitch is OTP-grade reliability. VM-mode calls always start from a fresh heap off an immutable program, so anything resembling stateful SSR has to go through the native runtime instead — meaning you can't get sandboxing and persistent state at the same time. The native dependency footprint is large (Zig 0.16, vendored QuickJS, lexbor, OXC Rust NIFs, Mint) and precompiled binaries only cover baseline CPU per architecture, so anything off that path is a source build across several toolchains at once.