- Widen OXC dep to >= 0.7.0 (supports OXC 0.10 with codegen, bind, splice, and bundle :external option)
- Allow oxc ~> 0.9 (adds OXC.Format support)
- Allow oxc ~> 0.8 (adds OXC.Lint support)
- JS line coverage —
QuickBEAM.Coverintegrates withmix test --coverto report line-level coverage for all JS/TS code executed through QuickBEAM runtimes. Patches QuickJS to track execution via a per-function hit bitmap with near-zero overhead when disabled. Outputs LCOV and Istanbul JSON. Also works as a sidecar for excoveralls users. Beam.XML.parse— parse XML from JS using OTP's built-in:xmerl. Returns JS-friendly objects with@attrattributes,#textmixed content, and arrays for repeated siblings. Handles namespaces and CDATA.
- Toolchain upgraded to
oxc0.7 andnpm0.5.3 — bundler rewritten to useOXC.rewrite_specifiers/3andNPM.PackageResolver, removing ~150 lines of duplicated resolution logic. - Default
max_stack_sizeincreased from 4 MB to 8 MB — QuickJS's interpreter uses ~150 KB of C stack per JS call frame, limiting recursion to ~27 frames with the old default. The new default supports ~55 frames, covering all typical real-world patterns.
- WebSocket support — adds a Mint-backed
WebSocketimplementation with connection lifecycle events, frame send/receive support, close handling, and WPT coverage. - WebAssembly support — adds a WAMR-backed
WebAssemblyimplementation with module compilation, instantiation, imports/exports handling, and JS API compatibility coverage.
- Toolchain upgraded to
oxc0.6 — updates bundling integration for the new entry requirement and switches bare-specifier rewriting to AST-based source patching.
load_module/3now propagates top-level module evaluation errors — runtime exceptions thrown while evaluating module code are returned as{:error, %QuickBEAM.JSError{}}instead of incorrectly succeeding with:ok.- WebSocket runtime cleanup — runtime shutdown now drains pending jobs correctly, waits for WebSocket processes to terminate, and avoids GitHub Actions-only teardown failures after successful test runs.
- WebSocket spec compliance — fixes
close()duringCONNECTING, rejects credentialed WebSocket URLs, and allows case-distinct subprotocols. - N-API wrap cleanup on
remove_wrap— detached wraps are destroyed safely instead of relying on later finalizer cleanup, avoiding shutdown-time crashes in addon wrap tests. - N-API excluded test coverage — tags the C test addon suite so
--exclude napi_addonbehaves as intended.
- Fix precompiled NIF checksum mismatch from v0.8.0 release
- N-API addon support —
QuickBEAM.load_addon/3loads native.nodeaddons into a runtime and optionally exposes their exports as a global. - Node-API surface for native addons — adds the N-API implementation needed to run real addons, including constructors, typed arrays, external buffers, async work, and threadsafe functions.
- Real addon integration coverage — adds tests for a C test addon plus
@node-rs/crc32,@node-rs/argon2,@node-rs/bcrypt, andsqlite-napi. Beam.nanoseconds()— monotonic high-resolution timer via:erlang.monotonic_time(:nanosecond)Beam.uniqueInteger()— monotonically increasing unique integer via:erlang.unique_integerBeam.makeRef()— create a unique BEAM reference, useful for request/reply correlationBeam.inspect(value)— pretty-print any value viaKernel.inspect, especially useful for opaqueBeamPid/BeamRefterms
- TypeScript support in Context —
QuickBEAM.Contextnow auto-transforms.ts/.tsxscripts via OXC and auto-bundles scripts withimportstatements, matching Runtime behavior. Previously, Context loaded scripts as raw JS. - Repo-wide quality gate — added
mix ciwith test env defaults and brought the full quality pipeline to green: Elixir linting, Dialyzer, Zig lint, TypeScript type-aware linting, duplicate-code checks, and tests now pass together. - TypeScript polyfill quality — resolved DOM/global type collisions in
priv/tsby moving implementation classes to QB-prefixed names while preserving web-facing globals. Also removed TS lint/type errors and TS clone findings. - N-API implementation cleanup — aligned buffer APIs with QuickBEAM's
Uint8Arraybyte representation, tightened wrap and async cleanup behavior, and splitnapi.ziginto focused Zig modules. - Zig lint hygiene — added missing
SAFETY:notes for intentionalundefinedinitialization, replaced suppressed error handling with explicit handling, removed unused declarations, and fixed style warnings sozlintruns clean.
- JS→BEAM conversion hang on cyclic/deep object graphs —
evalandcallcould hang indefinitely when the return value contained circular references or deeply nested structures (e.g. Vue reactive proxies fromcreateApp().mount()). The converter now tracks visited objects to detect cycles and enforces a total node budget to prevent combinatorial explosion.
- Configurable conversion limits — new
:max_convert_depth(default: 32) and:max_convert_nodes(default: 10,000) options forQuickBEAM.start/1andContextPool.start_link/1control how deeply the JS→BEAM value serializer recurses. Values beyond the limits are replaced withnil.
- Bytecode disassembly —
QuickBEAM.disasm/1andQuickBEAM.disasm/2decode QuickJS bytecode into structured%QuickBEAM.Bytecode{}terms (the QuickJS equivalent of:beam_disasm). Returns function metadata, decoded opcode stream with byte offsets, constant pool with recursive nested functions, local/closure variable definitions, and source text.disasm/1works standalone without a runtime;disasm/2compiles source first.
- Bump
npmdependency to~> 0.4.2
- DOM prototype chain — full hierarchy:
Node→Element→HTMLElement/SVGElement/MathMLElement, plusDocument,DocumentFragment,Text,Comment. Constructor globals onglobalThisenableinstanceofchecks Symbol.toStringTag—Object.prototype.toString.call(el)returns[object HTMLDivElement]with 40+ HTML tag mappingsMutationObserverno-op stub —observe(),disconnect(),takeRecords()for SSR compatibilitydocument.nodeType(returns 9) anddocument.nodeName(returns"#document") getters- Node object identity — same DOM node always returns the same JS wrapper (
el.parentNode === el.parentNode,document.body === document.body). UsesDocumentData.node_mapwithgc_markto prevent premature collection - 163 WPT-ported tests —
Node,Element,ChildNode,DocumentAPIs adapted from Web Platform Tests
tagName/nodeNamereturn uppercase for HTML elements (spec compliant). SVG/MathML elements preserve original casetextContent = ""removes all children instead of creating an empty text node- Default
max_stack_sizebumped to 4 MB (from 1 MB) — Vue mount path needs ~2 MB+ innerHTMLsetter uses sharedremove_all_childrenpath with node map eviction
- Node identity cache leak —
innerHTML=andtextContent=now recursively evict replaced subtrees from the node map, preventing stale pointers and unbounded map growth JS_DupValueleak on OOM — map put failure now frees the duplicated ref- Removed unused
qb_node_set_user/qb_node_get_userC bridge functions,NodePtrtype alias, and emptyelement_finalizer
QuickBEAM.ContextPool— pool of N runtime threads (default:System.schedulers_online()) with round-robin context distributionQuickBEAM.Context— lightweight GenServer owning a singleJSContexton a shared pool thread. Full API: eval, call, Beam.call/callSync, DOM, messaging, handlers, supervision. Linked to the calling process for automatic cleanup (ideal for LiveViewmount)- Granular API groups — contexts can load individual API groups (
:fetch,:websocket,:worker,:channel,:eventsource,:url,:crypto,:compression,:buffer,:dom,:console,:storage,:locks) instead of the full:browserbundle. Dependencies auto-resolve - Per-context memory tracking (QuickJS patch) —
js_malloc/js_free/js_realloctrackctx->malloc_size.Context.memory_usage/1returns:context_malloc_size - Per-context memory limits (QuickJS patch) —
Context.start_link(memory_limit: 512_000)enforces per-context allocation limit viactx->malloc_limit - Per-context reduction limits (QuickJS patch) —
Context.start_link(max_reductions: 100_000)interrupts long-running evals after an opcode budget. Count resets per-operation; context stays usable - Precompiled bytecodes — polyfill JS compiled to QuickJS bytecodes once, cached in
persistent_term. Context creation ~3.2x faster viaJS_EvalFunction - NIF operations for globals —
get_global,list_globals,snapshot_globals,delete_globalsuseJS_GetPropertyStr/JS_GetOwnPropertyNames/JS_DeletePropertyinstead of eval round-trips QuickBEAM.JSmodule — shared polyfill compilation with granular API group system, compile-time barrel bundles, OXC toolchain delegations (imports,postwalk,patch_string)- 39 new tests (17 functional + 22 stress): 1K-context scale, 200-task thundering herd, memory leak checks, isolation, error recovery, handler contention, burst messaging, resource limits
- Worker protocol uses integer IDs instead of PIDs, with
Beam.call/callSynchandlers instead ofBeam.sendwith tagged arrays eval_with_varsusesdelete_globalsNIF instead oftry/finally/deleteJS hackglobals/2andget_global/2use NIF operations instead of evalBeam.callSyncdrain interval reduced from 10ms to 1ms with drain callback for context pool promise resolution
LocksTest— removed redundantstart_supervised!(QuickBEAM.LockManager)(already started by application supervisor)- Worker spawn/terminate race conditions with integer ID-based tracking
| Runtime (1:1 thread) | Context (pooled) | |
|---|---|---|
| Per instance | ~530 KB heap + 2.5 MB stack | 58–429 KB (no thread) |
| OS threads at 10K | 10,000 | 4 (configurable) |
| Total RAM at 10K | ~30 GB | 570 MB–4.2 GB |
Per-context memory by API surface: bare 58 KB, beam 71 KB, beam+url 108 KB, beam+fetch 231 KB, full browser 429 KB.
- MessageChannel / MessagePort — paired ports with
structuredClone+queueMicrotaskasync delivery,onmessageauto-start per spec - FormData — full CRUD + iteration, Blob→File wrapping, multipart/form-data encoding in
fetch - Performance Timeline —
performance.mark(),performance.measure(),getEntries(),getEntriesByType(),getEntriesByName(),clearMarks(),clearMeasures(),toJSON(),timeOrigin child_process.execSync— backed bySystem.cmd, withcwd,env,timeout,maxBufferoptionschild_process.exec— async callback variantBeam.peek(promise)— read a promise's value synchronously via nativeJS_PromiseState. Returns the promise itself if pending.Beam.peek.status(promise)returns'fulfilled','rejected', or'pending'Beam.password.hash/verify— PBKDF2-SHA256 password hashing with PHC-format output, constant-time comparison- Beam utility extensions —
Beam.sleep(),Beam.sleepSync(),Beam.hash(),Beam.escapeHTML(),Beam.which(),Beam.randomUUIDv7(),Beam.deepEquals(),Beam.version,Beam.semver.satisfies(),Beam.semver.order() - Beam OTP extensions —
Beam.nodes(),Beam.rpc(),Beam.spawn(),Beam.register(),Beam.whereis(),Beam.link(),Beam.unlink(),Beam.systemInfo(),Beam.processInfo() - Precompiled NIF support via
zigler_precompiled— end users don't need Zig installed - JavaScript API reference and Architecture overview on HexDocs
set_global/evalwith:varssilently failing on Linux —JS_SetPropertyStrrequires null-terminated C strings butgpa.dupeproduced non-null-terminated Zig slices. Fixed withgpa.dupeZ+[:0]const u8- Worker init hang — exposing PerformanceEntry/Mark/Measure on
globalThisexceeded QuickJS internal threshold (~126 globals). Classes are now returned from API methods only
- Expanded WPT test coverage for Blob, TextDecoder, and URL
- Test suite reorganized into
core/,web_apis/,dom/,node/,toolchain/,wpt/subdirectories - CI: all four jobs passing (test, UBSan, ASAN, clang-tidy)
eval/3:varsoption — pass variables into JS code as temporary globals, cleaned up automatically viatry/finally(even on errors or timeouts)set_global/3— set JS globals from Elixir using native BEAM→JS conversion, counterpart toget_global/2
- OXC upgraded to 0.5.0 — adds
jsx_factory,jsx_fragment, andimport_sourceoptions for JSX transform and bundle - Replaced examples — removed
content_pipelineandplugin_sandbox, added:rule_engine— user-defined business rules in sandboxed runtimes (apis: false, memory limits, timeouts, hot reload, handlers as controlled API)live_dashboard— Workers (BEAM processes) + BroadcastChannel (:pg) for parallel metrics computation with crash recoveryssrnow uses JSX syntax via OXC classic mode
- Node.js compatibility APIs —
process,path,fs,osbacked by OTP :apisoption forQuickBEAM.start/1— controls which API surface to load::browser— Web APIs (default, same as before):node— Node.js compat[:browser, :node]— bothfalse— bare QuickJS, no polyfills
- Extended DOM API backed by lexbor C library:
document.createDocumentFragment(),document.createComment()document.createElementNS()for SVG/MathML/namespaced elementsdocument.getElementsByClassName(),document.getElementsByTagName()element.insertBefore(),element.replaceChild(),element.cloneNode()element.contains(),element.remove(),element.before(),element.after()element.prepend(),element.append(),element.replaceWith()element.matches(),element.closest()element.getElementsByClassName(),element.getElementsByTagName()element.lastChild,element.previousSibling,element.nodeType,element.nodeName,element.nodeValue,element.parentElementelement.classNameis now read-writeDocumentFragmentcorrectly moves children inappendChild/insertBefore
element.classList— fullDOMTokenListimplementation (add,remove,toggle,contains,replace,forEach,item,length,value,Symbol.iterator)element.style—CSSStyleDeclarationbacked by lexbor CSS parser (getPropertyValue,setProperty,removeProperty,getPropertyPriority,cssText,length,item, camelCase property access via Proxy)getComputedStyle()— returns inline style declaration (no cascade/layout)addEventListener/removeEventListener/dispatchEventon elements anddocument, withonce,stopImmediatePropagation,handleEventobject listenersCustomEventwithdetailproperty
- Import extraction uses
OXC.imports/2NIF instead of full AST parsing — faster bundling and script loading
- Locks tests failing due to redundant
LockManagerstart (already started by application supervisor) - CI cache keys now hash Zig sources, fixing stale native builds
- ASAN CI job correctly finds beam binary path
clang-tidystatic analysis forlexbor_bridge.cin CI (clang-analyzer-*,bugprone-*,portability-*)
- Unified
Beamnamespace —beam.call→Beam.call,beam.callSync→Beam.callSync,beam.send→Beam.send,beam.self→Beam.self,Process.onMessage→Beam.onMessage,Process.monitor→Beam.monitor,Process.demonitor→Beam.demonitor
- Auto-bundle imports and TypeScript in
:scriptoption —.ts/.tsxfiles are transformed via OXC, files withimportstatements are bundled into a single IIFE with relative paths and bare specifiers resolved fromnode_modules/ QuickBEAM.JS.bundle_file/2for programmatic bundling of entry files with all dependencies- npm dependency management via
mix npm.install
- Atom cache for QuickJS↔BEAM boundary — pre-created JS strings for common atoms (
nil,true,false,ok,error, etc.) avoid repeated allocations on every conversion - Direct promise result inspection via
JS_PromiseState/JS_PromiseResult— removes temporary globals, eval overhead, and a per-iteration string leak - Lazy proxy objects for BEAM→JS map conversion — maps with >4 entries are wrapped in a
BeamMapProxybacked by the original BEAM term, converting properties only on access - Zero-copy string optimizations — map key conversion uses
JS_NewAtomLen/JS_AtomToCStringLendirectly, removing intermediate allocations and the 256-byte key length limit - Worker
postMessageuses directbeam.sendinstead of routing throughbeam.callhandlers - Non-blocking NIFs —
eval/call/compilereturn immediately with a ref, results delivered asynchronously viaenif_send. Dirty IO schedulers are no longer blocked.
- Segfault on concurrent runtime init — QuickJS class IDs for
BeamMapProxyand DOM were allocated under separate mutexes, causing ID collisions when multiple runtimes initialized concurrently. All custom class IDs are now allocated under a single shared mutex. - Deadlock on runtime shutdown —
beam.callSyncnow uses ashutting_downflag with timed polling so the worker thread exits cleanly whenGenServer.stopis called while a sync call is pending - Locks and BroadcastChannel tests failing with
--no-start - Test suite hanging under parallel execution due to missing
:telemetrydependency
Initial release.
- QuickJS-NG embedded via Zig NIFs — no system JS runtime needed
- Runtimes are GenServers with full OTP supervision support
- Persistent state across
eval/2andcall/3calls - ES module loading with
load_module/3 - Bytecode compilation and loading for fast cloning
- CPU timeout with
JS_SetInterruptHandler - Configurable memory limit and stack size
Beam.call/Beam.callSync— JS calls Elixir handler functionsBeam.send/Beam.self— JS sends messages to BEAM processesBeam.onMessage/Beam.monitor/Beam.demonitor- Direct BEAM term conversion (no JSON serialization)
- Runtime pools via NimblePool
Standard browser APIs backed by BEAM/Zig primitives:
fetch,Request,Response,Headers—:httpcdocument,querySelector,createElement— lexbor native DOMURL,URLSearchParams—:uri_stringcrypto.subtle(digest, sign, verify, encrypt, decrypt, generateKey, deriveBits) —:cryptocrypto.getRandomValues,randomUUID— Zigstd.crypto.randomTextEncoder,TextDecoder— native Zig UTF-8TextEncoderStream,TextDecoderStreamReadableStream,WritableStream,TransformStreamwithpipeThrough/pipeTosetTimeout,setInterval,clearTimeout,clearInterval— Zig timer heapconsole.log/warn/error/debug/trace/assert/time/timeEnd/count/dir/group— Erlang LoggerCompressionStream,DecompressionStream—:zlibBuffer(encode, decode, byteLength) —Base,:unicodeEventTarget,Event,CustomEvent,MessageEvent,CloseEvent,ErrorEventAbortController,AbortSignalBlob,FileBroadcastChannel—:pg(distributed across cluster)WebSocket—:gunWorker— BEAM process-backed JS workers withpostMessagenavigator.locks(Web Locks API) — GenServer with monitor-based cleanuplocalStorage— ETS (shared across runtimes)EventSource(Server-Sent Events) —:httpcstreamingDOMExceptionatob,btoa— Zig base64structuredClone— QuickJS serializationqueueMicrotask—JS_EnqueueJobperformance.now— nanosecond precision
- Native DOM via lexbor C library
- Elixir-side DOM queries:
dom_find/2,dom_find_all/2,dom_text/2,dom_attr/3,dom_html/1 - Returns Floki-compatible
{tag, attrs, children}tuples
QuickBEAM.JS— parse, transform, minify, bundle via OXC Rust NIFsQuickBEAM.eval_ts/3— evaluate TypeScript directly- TypeScript sources compiled at build time via OXC (no Node.js/Bun required)