v0.6.0 (latest)

Changes (since previous release)

  • fix: Denox.Deps.add_to_config/3 and remove_from_config/2 now return {:error, "Failed to write ..."} (human-readable string) instead of {:error, :eacces} (raw posix atom) when the config file is not writable

  • test: add Denox.Deps.add/3 write failure test — verifies that readonly config files produce a readable error string, not a raw posix error atom

  • test: add Denox.Deps.runtime/1 empty import_map branch coverage — exercises the map_size == 0 conditional skip in the runtime builder

  • test: add CallbackHandler non-JSON-serializable return value test — verifies that when a callback function returns a value that cannot be JSON-encoded (e.g., a PID), the rescue clause catches the JSON.EncodeError and sends it to JS as an error tuple rather than crashing the handler

  • test: add stream/1 RuntimeError test — verifies that when start_link fails (e.g., neither :file nor :package provided), the stream raises RuntimeError when enumerated, as documented in the @doc warning block

  • fix(test): replace flaky Application.stop(:inets) test in coverage_gaps_test.exs with a safe synchronous test — the dangerous global state mutation was intermittently causing TLS errors in concurrently running async test modules

  • test: add Denox.CLI.find_deno/0 system PATH success test — verifies system deno binary is found and returned as {:ok, path} when present on PATH, taking priority over the bundled CLI (exercises the first branch of the 3-step fallback chain)

  • test: add Denox.CLI.handle_response/2 edge case coverage — :exit reason tuples (e.g., connection refused caught by download/2) and non-200 responses with empty body are both handled and produce readable error messages

  • test: add unsubscribe/1 idempotency tests — unsubscribing a PID that was never subscribed, and calling unsubscribe twice after one subscribe, both return :ok without error (verified against Denox.Run.Base.__handle_call__/4 logic)

  • test: add stream_from/2 edge case — empty list returned when server exits immediately without producing any output (complements stream/1 no-output test)

  • fix(rust): log unexpected I/O errors in stdout pipe reader thread instead of silently discarding them; normal pipe-close errors (BrokenPipe, UnexpectedEof) remain silent as they are expected on runtime exit

  • refactor: extract Denox.Permissions module to centralize NIF permission JSON building — eliminates duplicate @valid_permission_keys and build_permissions_json between Denox and Denox.Run

  • refactor: deduplicate ts_extension?/1 between Denox and Denox.Pool

  • test: add unit tests for Denox.Permissions (8 tests covering all code paths)

  • test: add permission enforcement tests verifying Deno actually restricts env and file read access under permissions: :none and allows under :all / granular

  • fix: tag deno-dependent tests with :deno to skip in CI

  • fix(ci): set MIX_ENV=test for E2E workflow

  • refactor: replace JS polyfill I/O with native deno_io pipe bridging in runtime_run

  • refactor: extract RuntimeRunResource and NIF functions into runtime_run.rs

  • fix: add panic safety (catch_unwind) to runtime threads

  • chore: add @spec annotations to callback implementations in Denox.Run, Denox.CLI.Run, and Denox.CallbackHandler

  • chore: add @spec to injected GenServer callbacks (init/1, handle_call/3, dispatch_line/2, handle_exit/2) in Denox.Run.Base

  • chore: add @spec to private helpers in Denox.Pool and Denox.CLI.Run

  • feat: validate unknown granular permission keys in Denox.runtime/1 and Denox.Run.start_link/1 — raises ArgumentError instead of silently ignoring

  • feat: move permission validation before CLI lookup in Denox.CLI.Run.init_backend/1 so callers get ArgumentError before any binary search

  • test: add deny_* permission tests for both Denox.Run (NIF) and Denox.CLI.Run (subprocess) backends

  • test: add Pool error-path tests for eval_file, eval_file_decode, eval_module, exec, exec_ts, and call/call_async with undefined functions

  • test: add with_runtime/2 error-passthrough test (user function returning {:error, ...})

  • docs: document deny_* keys and ArgumentError in Denox.runtime/1 :permissions option

  • docs: expand Denox.Pool moduledoc with load_npm and exec usage patterns

  • docs: clarify exec/2 and exec_ts/2: errors must still be handled (not fire-and-forget)

  • docs: document with_runtime/2 error-passthrough semantics

  • docs: clarify Denox.CLI.Run primary use case is npm/jsr packages

  • fix: Denox.Pool.init/1 now returns {:stop, {:failed_to_create_runtime, msg}} instead of raising when runtime creation fails — enables proper supervision tree error propagation

  • fix: Denox.Run.Base subscribe/1 now deduplicates by PID — calling subscribe twice from the same process no longer creates duplicate monitor refs or sends duplicate stdout messages

  • fix: Denox.Permissions.to_nif_json/1 now raises ArgumentError on unknown permission keys even when the value is false (previously silently ignored)

  • test: add duplicate subscribe idempotency test

  • test: add Pool init failure propagation test

  • test: add CLI.Run invalid env type validation test

  • test: add permission edge case tests (mixed allow/deny, empty lists, false unknown keys)

Improvements

  • Denox.Run stdin/stdout now uses native OS pipe bridging via deno_io::Stdio/StdioPipe instead of JS-level polyfills. This eliminates the 10ms busy-poll loop for stdin reads, removes the console.log override, and implements the PRD-specified 2N+1 thread model (event loop + pipe reader + pipe writer).
  • Denox.Run.Base now has a proper @moduledoc documenting the shared behaviour contract (init_backend/1, send_backend/2, stop_backend/1, alive_backend?/1) for backend implementors.
  • Denox.Run documentation now explicitly states that npm: and jsr: specifiers are not supported by the NIF backend (TsModuleLoader only handles file://, https://, and http:// schemes). Users are directed to Denox.CLI.Run for npm/jsr packages.
  • Test coverage expanded: pipe I/O edge cases (empty lines, Unicode, Deno.stdout.write, long lines, rapid output), Web Streams API, Blob, MessageChannel, Promise combinators, setTimeout/setInterval, granular permissions enforcement.
  • PRD checklist fully verified (29/29 items passing).
  • capture/1 convenience function added to Denox.Run.Base (and inherited by both Denox.Run and Denox.CLI.Run): starts a runtime, collects all stdout lines until exit or timeout, and returns them as {:ok, [String.t()]}. Uses the recv-poll pattern to avoid subscribe race conditions on fast-completing scripts.
  • stream/1 convenience function added to Denox.Run.Base: returns a lazy Stream.resource/3 enumerable that yields stdout lines one-by-one, suitable for large or streaming output. Halts on process exit or timeout.
  • send_and_recv/3 convenience function added to Denox.Run.Base: one-shot request/response helper for JSON-RPC over stdio (e.g. MCP servers). Sends a line and returns the next line, wrapping send/2 + recv/2 in a single call.
  • stream_from/2 convenience function added to Denox.Run.Base: returns a lazy Stream.resource/3 enumerable from an already-running server PID. Unlike stream/1, it does not start or stop the server — the caller retains ownership. Pairs naturally with with_runtime/2 for request-response workflows.
  • with_runtime/2 bracket-style resource management added to Denox.Run.Base: starts a runtime, passes the PID to a user function, and guarantees cleanup via an after block — even if the function raises. Returns {:error, reason} if the runtime fails to start.

v0.5.0 — 2026-03-22

Features

  • Denox.Run NIF backend — replaced subprocess deno run with an in-process deno_runtime MainWorker via Rustler NIF. No external deno binary required for Denox.Run. Supports stdin/stdout streaming, telemetry, OTP supervision, and all permission modes.
  • Denox.CLI binary manager — downloads and caches the official Deno binary for the current platform (macOS/Linux, x86_64/aarch64). Configure with config :denox, :cli, version: "2.x.x" and run mix denox.cli.install.
  • Denox.CLI.Run — subprocess-based runner using the bundled CLI binary; same API as Denox.Run but spawns a deno process per instance. Useful when full CLI features are needed.
  • Shared Run.Base behaviour — shared GenServer dispatch logic extracted as a __using__ macro behaviour, supporting both NIF and CLI backends with a common send/recv/subscribe/unsubscribe/alive?/stop API.
  • Granular permissions — both Denox.Run and Denox.CLI.Run support :all, nil/:none, or a keyword list of allow_*/deny_* permission flags.
  • Telemetry[:denox, :run, :start], [:denox, :run, :stop], [:denox, :run, :recv] events emitted for all backends.

Fixes

  • deno_core replaced with deno_runtime MainWorker for full Deno API compatibility (fetch, timers, Deno.env, etc.)
  • Monitor leak on unsubscribe/1: monitors are now properly demonitored when subscribers unsubscribe
  • Stale recv_waiters on timeout: timed-out recv/2 callers are now monitored; dead waiters are removed before dispatching lines
  • mix denox.run now uses Denox.CLI.find_deno() for bundled CLI fallback, consistent with Denox.Deps and Denox.Npm
  • mix denox.run drains remaining port messages after exit_status to prevent dropping final output without trailing newline (race condition on Linux where {:exit_status, 0} arrives before {:noeol, chunk})
  • Denox.Deps.ensure_vendor_config/1 refactored to reduce nesting depth
  • File write errors in Denox.Deps.ensure_vendor_config now include the filename in the error message
  • send/2 documentation updated to document automatic newline appending and {:error, :closed} return
  • Denox.CLI.Run now documents the :deno_flags option for passing extra flags to deno run

v0.4.1

Features

  • Added 206 comprehensive web/Node.js global polyfills
  • Added globalThis.fetch polyfill with Headers/Request/Response

Fixes

  • Added :mix to Dialyzer PLT apps for Mix.Task modules
  • Added credo and dialyxir deps, resolved all credo issues
  • Added Rust toolchain and DENOX_BUILD to CI workflow
  • Fixed hello script and cleaned up .envrc

v0.4.0

Features

  • Denox.Run subprocess runner — GenServer wrapping deno run with bidirectional stdio, enabling Elixir apps to run full Deno programs (MCP servers, CLI tools) with OTP supervision
  • mix denox.run task — drop-in replacement for deno run with stdin forwarding, e.g. mix denox.run -A @modelcontextprotocol/server-github
  • Deno permission mapping:all for -A, or granular keyword list (allow_net, allow_env, etc.)
  • Auto npm: prefix — bare @scope/name specifiers are automatically prefixed with npm:
  • Pub/sub stdoutDenox.Run.subscribe/1 for receiving {:denox_run_stdout, pid, line} messages
  • Real setTimeout — native op_sleep async op for millisecond-accurate delays in setTimeout/setInterval
  • Convenience functions — added eval_async_decode, eval_ts_async_decode, eval_file_async, eval_file_decode, eval_file_async_decode for cleaner API

Fixes

  • Fixed setTimeout returning immediately instead of waiting for the specified delay

v0.3.0

Breaking Changes

Features

  • Unified event loop pumping — all eval modes now pump the event loop, making sync and async consistent
  • Task-based async API — true concurrency with proper task management
  • Denox.await/2 helper — delegates to Task.await for convenience

Playground Enhancements

  • Added Preact JSX SSR demo with real JSX syntax support
  • Added import map example showing bare specifier resolution
  • Display import map content above code editor
  • Fixed async examples to handle long CDN fetches without timing out

v0.2.2

Features

  • JSX/TSX transpilation — changed from MediaType::TypeScript to MediaType::Tsx for native JSX support
  • setTimeout polyfill — async-safe timer polyfill for microtask-based delays
  • Preact SSR example — server-side rendering via preact-render-to-string

Playground

  • 12 example snippets including Preact JSX SSR demo
  • CDN imports from esm.sh, esm.run, jsr
  • Zod schema validation example

v0.2.1

Features

  • Multiple CDN examples — lodash, Zod, JSR packages
  • Import map supportDenox.runtime(import_map: %{...}) for bare specifier resolution
  • Timer polyfill — basic setTimeout/setInterval (microtask-based, no real delays)

Playground

  • 10 example snippets
  • Language toggle (JS/TS)
  • Mode toggle (Sync/Async)
  • Execution history with accordion view
  • Theme switcher

v0.1.0

Initial release.

Features

  • JavaScript evaluation — sub-millisecond V8 eval via deno_core 0.311
  • TypeScript transpilation — native swc/deno_ast, transpile-only (no type-checking)
  • ES module loadingimport/export between .ts/.js files with eval_module/2
  • Async evaluationawait, dynamic import(), Promise resolution via eval_async/2
  • CDN imports — fetch from esm.sh, esm.run, etc. with in-memory + disk caching
  • Dependency managementdeno.json + mix denox.install for npm/jsr packages
  • Pre-bundlingmix denox.bundle and Denox.Npm.load/2 for self-contained JS files
  • Runtime poolDenox.Pool GenServer with round-robin across N V8 isolates
  • Import maps — bare specifier resolution via :import_map option
  • JS → Elixir callbacksDenox.callback() in JS, Denox.CallbackHandler GenServer
  • V8 snapshotsDenox.create_snapshot/2 for faster cold starts
  • Sandbox mode — strip extensions for reduced attack surface
  • Telemetry[:denox, :eval, :start | :stop | :exception] events

  • Precompiled NIFs — via RustlerPrecompiled for macOS and Linux (x86_64, aarch64)

API