Type-safe Haxe to Elixir compiler with Phoenix/LiveView support. Write business logic in Haxe, compile to idiomatic Elixir code for the BEAM ecosystem.
Warning
Stability: Reflaxe.Elixir v1.1.x is considered non‑alpha for the documented subset.
Some features remain experimental/opt‑in (e.g. source mapping, migrations .exs emission, fast_boot).
See: Known Limitations and Versioning & Stability.
- Stable (documented subset): exercised by CI + todo-app; intended to be reliable within
v1.x. - Experimental (opt-in): available, but expect rough edges and possible breaking changes (e.g. source maps, migrations
.exs,fast_boot). - Dev/internal tooling: debug flags and contributor-only workflows; not part of the end-user stability contract.
See: Versioning & Stability.
fast_bootis for faster local iteration; don’t rely on its output shape for CI/production builds.- Watcher port conflicts can happen (Phoenix watchers + Haxe server ports); the repo examples auto-probe, but custom setups may need tweaks.
- Typed boundaries matter: prefer
Termat external boundaries and decode into typed structures instead of usingDynamic.
See: Known Limitations.
Future Vision: See docs/08-roadmap/vision.md for long-term plans including AI tooling and universal platform support
Current Status: Stable subset (v1.1) — non‑alpha for the documented subset; experimental features are clearly labeled.
The Problem: You want BEAM's incredible concurrency and fault tolerance, but Elixir's dynamic typing means runtime errors in production.
The Solution: Reflaxe.Elixir brings compile-time type safety to the BEAM ecosystem:
- Type Safety Today - Catch errors at compile time, not in production
- Idiomatic Elixir Output - Generated code looks hand-written by Elixir experts
- Full Phoenix Integration - LiveView, Ecto, OTP, GenServers all supported
- Future Expansion - Haxe's multi-target nature enables future platform support
The BEAM is designed to recover from failures (supervision + “let it crash/let it heal”), but typed code still helps a lot:
- Prevent accidental crashes and make refactors safer.
- Make intentional failures explicit (e.g.
Result/Optionpatterns) so you can be deliberate about what should crash vs what should be handled locally.
Gleam is an excellent typed BEAM language with a strong focus on correctness and a great compiler UX. Reflaxe.Elixir takes a different path:
- Direct Elixir ecosystem leverage: Reflaxe.Elixir generates idiomatic Elixir and targets HEEx (
~H) and Phoenix/LiveView patterns directly. - Fewer “FFI boundary” tradeoffs: in Gleam, calling into non-Gleam libraries via externals is powerful, but the external code can’t be type-checked by the Gleam compiler and editor assistance is reduced. Reflaxe.Elixir aims to keep you on the “typed path” while still producing standard Elixir/Phoenix code.
Write your Phoenix app in Haxe and get:
- Compile-time validation of LiveView assign/update patterns
- Type-safe Ecto schemas with automatic changeset generation
- OTP supervision trees with typed GenServer callbacks
- Idiomatic Elixir output that Phoenix developers recognize
The foundation for multi-target development:
- Business logic in Haxe - validation, algorithms, data transformations
- Backend on BEAM - Phoenix/LiveView/Ecto with full type safety ✅
- Frontend on JavaScript - Async/await support + Genes ES module output (see todo-app) ✅
- TypeScript ecosystem access - dts2hx planned (optional future)
- Massive concurrency - Handle millions of connections with lightweight processes
- Fault tolerance - Let it crash philosophy with supervisor recovery
- Hot code reloading - Update production systems without downtime
- Type safety - Catch errors before they reach production
| Reflaxe.Elixir | Gleam | Pure Elixir | TypeScript | |
|---|---|---|---|---|
| Type Safety | ✅ Compile-time | ✅ Compile-time | ❌ Runtime | ✅ Compile-time |
| BEAM Integration | ✅ Phoenix/OTP integration | ✅ Native | ✅ Native | ❌ None |
| Phoenix LiveView | ✅ Native support | ✅ Native | ❌ None | |
| Multi-target Potential | ✅ Haxe foundation | ✅ BEAM + JS | ❌ BEAM only | |
| Ecosystem Maturity | ✅ Since 2005 | ✅ Mature | ✅ Mature | |
| Learning Curve | ✅ TypeScript-like | ✅ Familiar |
- Haxe (2005): Battle-tested cross-platform toolkit used in production by companies like Netflix, Disney, BBC, Toyota, and more
- Elixir/BEAM: Powers WhatsApp (2B users), Discord, Pinterest, and other massive-scale systems
- Reflaxe: Modern compiler framework making Haxe more powerful than ever
- Phoenix Integration - LiveView/controllers/templates/routers are supported for the documented subset (see examples + user guides)
- HEEx Templates (typed-first) - TSX-like inline markup + compile-time lowering to
~Hwith optional strict checks for Phoenix/LiveView attributes- Inline markup (
return <div>...</div>) is the recommended default:${...}splices are real Haxe expressions (parsed + type-checked) and are lowered into HEEx (~H) by the compiler pipeline. - TSX control tags are typed and compile to HEEx blocks:
<if ${cond}> ... <else> ... </else> </if>and<for ${item in items}> ... </for>. - TSX spread attrs are typed and lower to HEEx spread attrs:
<section {assigns.attrs}>(or<section {@assigns.attrs}>) compiles to<section {@attrs}>. - Template strings (
hxx('...')/HXX.hxx('...')) remain supported for migration, but they are string-rewritten + linted, not fully Haxe-typed. Prefer inline markup for typed Haxe expressions. - Layered template modes:
@:hxx_mode("tsx"): strict typed authoring (recommended for new code).@:hxx_mode("balanced"): default migration-friendly mode (typed inline markup + legacy string templates).@:hxx_mode("metal"): raw HEEx-leaning mode (discouraged).
- Raw HEEx escape hatch is explicit: use
@:allow_heex(or migration-only-D hxx_allow_raw_heex). - Short helper alias:
phoenix.hxx.Hmirrorsphoenix.hxx.HeexTemplate(H.root,H.root_ast,H.for_each,H.each). - Template Helper Metadata ✨ NEW - Uses @:templateHelper metadata for extensible Phoenix function compilation
- Type-Safe Phoenix Abstractions ✨ NEW - Assigns, LiveViewSocket, FlashMessage, RouteParams with operator overloading
- Inline markup (
- Ecto Integration - Schemas, changesets, and typed queries supported; migrations remain opt‑in/experimental (
-D ecto_migrations_exs) - Mix Integration - Seamless build pipeline with file watching and incremental compilation
- Source Mapping (experimental) -
.ex.mapemission +mix haxe.source_maplookup are implemented (line coverage + many expression-level boundaries; seedocs/04-api-reference/SOURCE_MAPPING.md) - OTP Support - GenServers, Supervisors, Registry with type-safe compilation
- Type Safety - Complete Haxe→Elixir type mapping and compile-time validation
- JavaScript Async/Await - Native async/await compilation for modern JS development
For the complete roadmap including AI tooling, universal deployment, and multi-platform support, see docs/08-roadmap/vision.md:
- JavaScript Integration - Advanced TypeScript ecosystem access
- Mobile Support - Capacitor and React Native deployment
- Desktop Applications - Electron/Tauri cross-platform apps
- AI-Enhanced Tooling - Intelligent development assistance
- Haxe 4.3.7+ (installed globally, or pinned per-project via
lixand used via./node_modules/.bin/haxe) - Node.js 16+ (for lix package management; Node 20 recommended)
- Elixir 1.14+ (for Phoenix/Ecto ecosystem)
# Create a lix scope in your project directory
npx lix scope create
# Install the latest GitHub release tag (recommended)
# If this fails (no `curl` / GitHub rate limit), pick a tag from the Releases page and set it manually.
REFLAXE_ELIXIR_TAG="$(curl -fsSL https://api.github.com/repos/fullofcaffeine/reflaxe.elixir/releases/latest | sed -n 's/.*\"tag_name\":[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n 1)"
npx lix install "github:fullofcaffeine/reflaxe.elixir#${REFLAXE_ELIXIR_TAG}"
# Download pinned Haxe libraries for the project (reflaxe + tink_* + deps)
npx lix download
# Bleeding edge (main branch; not necessarily a release)
# npx lix install github:fullofcaffeine/reflaxe.elixir --forceReflaxe.Elixir GitHub Releases are created for
vX.Y.Ztags. To use a specific release, install that tag with lix.
For projects that want to vendor the compiler source:
# Clone or download the repository
git clone https://github.com/fullofcaffeine/reflaxe.elixir.git
# Copy necessary files to your project
cp -r reflaxe.elixir/src/ your-project/vendor/reflaxe.elixir/src/
cp -r reflaxe.elixir/std/ your-project/vendor/reflaxe.elixir/std/
cp reflaxe.elixir/haxelib.json your-project/vendor/reflaxe.elixir/
# In your build.hxml, add:
# -cp vendor/reflaxe.elixir/src
# -cp vendor/reflaxe.elixir/std
# -lib reflaxe
# --macro reflaxe.elixir.CompilerInit.Start()Once installed, add to your build.hxml:
-lib reflaxe.elixir
-cp src_haxe
# Output directory for generated .ex files
-D elixir_output=lib/my_app_hx
# Required for Reflaxe targets
-D reflaxe_runtime
# Elixir is not a UTF-16 platform
-D no-utf16
# Application module prefix (used for Phoenix/Ecto integration; typically your app module name)
-D app_name=MyApp
# Enable dead code elimination to reduce output noise
-dce full
#
# Why this repo recommends `-dce full`:
# - Keeps generated `.ex` output small and readable (less stdlib/helpers noise).
# - Makes snapshot diffs meaningful and keeps CI/budgets predictable.
# - Catches “indirect runtime reference” regressions early (OTP/Phoenix callbacks/modules can be DCE’d otherwise).
# Define a stable entrypoint
# Entrypoint Haxe class (package.ClassName). Adjust to your app.
--main my_app_hx.MainDO NOT use -D analyzer-optimize when compiling to Elixir. This flag triggers aggressive optimizations designed for C++ and JavaScript that produce non-idiomatic Elixir code.
Recommended configuration:
# Good optimizations
-dce full # Dead code elimination (recommended)
-D loop_unroll_max_cost=0 # Disable loop unrolling (preserve functional shapes)
# AVOID these
# -D analyzer-optimize # Destroys functional patterns
# -D analyzer-check # May trigger unwanted optimizationsFor complete compiler configuration guidance, see docs/01-getting-started/compiler-flags-guide.md.
Reflaxe.Elixir has a single semantics-preserving compilation pipeline that aims to be Haxe-friendly by default (you can write straightforward Haxe, including loops/rebinding) while lowering to idiomatic Elixir shapes where it is semantics-safe.
Some codegen strategies are behind feature flags so you can opt in/out during rollouts or when debugging a regression:
docs/04-api-reference/FEATURE_FLAGS.mddocs/02-user-guide/IMPERATIVE_TO_FUNCTIONAL_LOWERING.mddocs/02-user-guide/AUTHORING_STYLES_PORTABLE_VS_ELIXIR_FIRST.md
You can keep most of your code “portable stdlib-first”, and still use typed extern surfaces for BEAM/Phoenix/Ecto integration when you want Elixir-native APIs and shapes (structs/atoms/tagged tuples):
docs/04-api-reference/STANDARD_LIBRARY_HANDLING.mddocs/02-user-guide/ESCAPE_HATCHES.md- Authoring style decision guide:
docs/02-user-guide/AUTHORING_STYLES_PORTABLE_VS_ELIXIR_FIRST.md - Canonical extern workflow:
docs/06-guides/ADDING_ELIXIR_LIBS_FROM_HAXE.md - Opt-in discipline for app code:
docs/06-guides/STRICT_MODE.md
Follow: docs/01-getting-started/START_HERE.md
- Run the repo todo-app (end-to-end) with a single command
- Learn the Haxe→Elixir→Phoenix mental model
- Generate a fresh Phoenix+Haxe project via the generator
This creates a new Phoenix+Haxe project using the latest GitHub release tag:
mkdir haxir-demo && cd haxir-demo
npm init -y
npm install --save-dev lix
npx lix scope create
# Install the generator (latest GitHub release tag)
# If this fails (no `curl` / GitHub rate limit), pick a tag from the Releases page and set it manually.
REFLAXE_ELIXIR_TAG="$(curl -fsSL https://api.github.com/repos/fullofcaffeine/reflaxe.elixir/releases/latest | sed -n 's/.*\"tag_name\":[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n 1)"
npx lix install "github:fullofcaffeine/reflaxe.elixir#${REFLAXE_ELIXIR_TAG}"
# Optional but recommended: pin a known-good Haxe toolchain (avoids relying on a global install).
npx lix download haxe 4.3.7
npx lix use haxe 4.3.7
# Generate a Phoenix app
# (Use `haxe --run Run` here because some lix/haxelib shim versions rely on an internal
# `run-dir` command which is not reliable across environments.)
REFLAXE_ELIXIR_SRC="$(./node_modules/.bin/haxelib path reflaxe.elixir | tr -d '\r' | grep -E 'reflaxe\.elixir/.*/src/?$' | head -n 1)"
./node_modules/.bin/haxe -cp "$REFLAXE_ELIXIR_SRC" --run Run create hello_haxir --type phoenix --no-interactive
cd hello_haxir
mix setup
mix phx.serverSee also: docs/06-guides/PHOENIX_NEW_APP.md.
Run a full build + boot + Playwright smoke without blocking your terminal:
scripts/qa-sentinel.sh --app examples/todo-app --env e2e --port 4001 --playwright --e2e-spec "e2e/*.spec.ts" --async --deadline 900 --verbose
scripts/qa-logpeek.sh --run-id <RUN_ID> --until-done 900- New Phoenix project:
docs/06-guides/PHOENIX_NEW_APP.md - Add Haxe gradually to an existing Phoenix project:
docs/06-guides/PHOENIX_GRADUAL_ADOPTION.md
Also see: docs/06-guides/QUICKSTART.md
# Compile once
haxe build.hxml
# Watch for changes (long-running)
mix haxe.watchFor Phoenix apps with client-side hooks, the recommended path is:
- Genes (recommended): ES modules + clean output via
-lib genes(seedocs/05-architecture/HXML_ARCHITECTURE.mdandexamples/todo-app/build-client.hxml). - Plain Haxe JS target: still works, but you’ll write more interop glue and typically won’t get ES module output.
# Full test suite (snapshots + Mix task tests)
npm test
# Compile-check every example under examples/
npm run test:examples
# Quick snapshot-only run
npm run test:quickSome parity tooling compares this repo against a local haxe.elixir.reference checkout.
Configure the path in a local .env (gitignored) using the provided template:
cp .env.example .env
# then edit .envexport HAXE_ELIXIR_REFERENCE=/path/to/haxe.elixir.referenceEach example is self-contained and documented. Start here:
examples/README.md
Most examples can be compiled with:
cd examples/<example-name>
haxe build.hxml # or compile-all.hxml when present# Add to your Phoenix project's mix.exs
defp deps do
[
# ... other deps
# Mix tasks only (build-time): pin to a GitHub release tag or use a commit SHA
# Replace <RELEASE_TAG> with the tag you installed via lix (e.g. v1.2.3)
{:reflaxe_elixir, github: "fullofcaffeine/reflaxe.elixir", tag: "<RELEASE_TAG>", only: [:dev, :test], runtime: false}
]
end
> Note: the `mix haxe.gen.*` generators are Haxe-first scaffolds (they emit **Haxe only**, not Elixir). For Phoenix client wiring (Genes + esbuild watch safety), use `mix haxe.phoenix.scaffold` and see `docs/06-guides/WATCHER_WORKFLOW.md` (marker-block + fail-fast behavior). Full task reference: `docs/04-api-reference/MIX_TASKS.md`.
# Compile Haxe as part of your build
mix compile.haxe
# Start Phoenix with Haxe compilation
mix phx.serveryour-project/
├── src_haxe/ # Your Haxe source files
│ ├── controllers/ # Phoenix controllers
│ ├── live/ # LiveView modules
│ ├── contexts/ # Business logic
│ └── schemas/ # Ecto schemas
├── lib/ # Generated Elixir files
│ └── (compiled output)
├── build.hxml # Haxe build configuration
└── mix.exs # Phoenix/Elixir dependencies
Start at docs/README.md for the curated documentation index.
- Installation Guide - Setup and prerequisites
- Quickstart - Your first Haxe→Elixir project
- Phoenix (New App) - Greenfield Phoenix setup
- Phoenix (Existing App) - Add Haxe to an existing Phoenix app
- Phoenix Chat Tutorial - Presence + PubSub + LiveView in Haxe
- Portable Chat Tutorial - Shared Haxe domain logic pattern
- Phoenix Integration - Controllers, LiveView, Ecto, Channels
- Escape Hatches - Calling Elixir from Haxe safely
- Known Limitations - Sharp edges and experimental surfaces
- Support Matrix - CI-tested toolchain versions
- Licensing & Distribution - GPL notes (not legal advice)
- Writing Idiomatic Haxe - Practical guidance for clean, idiomatic Elixir output
- Authoring Styles - Portable stdlib-first vs typed Elixir-first (one compiler pipeline)
- Elixir Idioms & Hygiene - Naming, unused vars, enum shapes, and codegen conventions
- Haxe→Elixir Mappings ✨ - Full mapping reference
- Source Mapping Guide 🎯 - Complete guide to our pioneering source mapping feature
- Annotations - Complete annotation reference
- Troubleshooting - Common issues and solutions
- Examples - Working code examples (index)
Each example includes its own README.md with compile/run steps and Haxe -> generated Elixir mapping snippets:
examples/01-simple-modules/README.mdexamples/02-mix-project/README.mdexamples/03-phoenix-app/README.mdexamples/04-ecto-migrations/README.mdexamples/05-heex-templates/README.mdexamples/06-user-management/README.mdexamples/07-protocols/README.mdexamples/08-behaviors/README.mdexamples/09-phoenix-router/README.mdexamples/10-option-patterns/README.mdexamples/11-domain-validation/README.mdexamples/12-phoenix-chat/README.mdexamples/13-elixir-first-liveview/README.mdexamples/test-integration/README.mdexamples/todo-app/README.md
You can compile-check all examples with npm run test:examples.
- Architecture Overview - Compiler internals
- Testing Guide - Snapshot + integration testing system
- Contributing - Contributing and extending
CONTRIBUTING.md– contribution workflow and commandsSECURITY.md– vulnerability reporting processCODE_OF_CONDUCT.md– community guidelinesRELEASING.md– release checklist and tagging
# Clone and setup
git clone https://github.com/fullofcaffeine/reflaxe.elixir
cd reflaxe.elixir
# Install dependencies (both ecosystems)
npm ci # Installs lix + Haxe dependencies
npx lix download # Downloads project-specific Haxe libraries
mix deps.get # Installs Elixir dependencies
# Run tests
npm test # Full suite (snapshots + Elixir validation + Mix)
npm run qa:sentinel # Todo-app build + boot probe (async)📖 New to lix or Haxe? See docs/01-getting-started/installation.md for complete setup guide with troubleshooting.
Reflaxe.Elixir follows standard Reflaxe compiler conventions (similar to Reflaxe.CPP):
reflaxe.elixir/
├── src/ # Compiler source (macro-time transpiler code)
│ └── reflaxe/elixir/ # ElixirCompiler.hx and helpers
├── std/ # Standard library (compile-time classpath)
│ ├── elixir/ # Elixir stdlib externs (IO, File, GenServer, etc.)
│ ├── phoenix/ # Phoenix framework externs (LiveView, Socket, etc.)
│ └── ecto/ # Ecto ORM externs (Schema, Changeset, Query)
├── lib/ # Elixir runtime support (Mix integration)
│ ├── haxe_compiler.ex # Mix compilation task
│ ├── haxe_watcher.ex # File watching for development
│ └── haxe_server.ex # Haxe compilation server wrapper
├── test/ # Compiler tests (snapshot testing)
└── examples/ # Example applications
└── todo-app/
└── src_haxe/ # User application code in Haxe
src/- The compiler that transforms Haxe TypedExpr → ElixirAST → transforms → printed Elixirstd/- Haxe externs and abstractions for Elixir/Phoenix/Ecto functionality (included via-lib reflaxe.elixiror vendoring)lib/- Elixir runtime files needed for Mix integration and compilation supportsrc_haxe/- User application code written in Haxe (in examples)
This separation follows Reflaxe conventions and ensures clear boundaries between compiler code, standard library, and user application code.
Reflaxe.Elixir uses a dual-ecosystem architecture:
- Compiler development: Build the Haxe→Elixir compiler
- Modern testing: tink_unittest + tink_testrunner with rich output
- Dependency management: lix with GitHub sources + locked versions
- Generated code testing: Validate compiled Elixir modules
- Phoenix integration: Test LiveView, Ecto, GenServer workflows
- Native tooling: Standard mix tasks and BEAM ecosystem
Reflaxe.Elixir source mapping is implemented but experimental: .ex.map emission and mix haxe.source_map lookup are available, with best-effort column granularity.
See docs/04-api-reference/SOURCE_MAPPING.md for current status and how to experiment.
import elixir.types.Term;
import phoenix.LiveSocket;
import phoenix.Phoenix.HandleEventResult;
import phoenix.Phoenix.MountResult;
import phoenix.Phoenix.Socket;
typedef CounterAssigns = { count: Int };
@:native("MyAppWeb.CounterLive")
@:liveview
@:hxx_mode("tsx")
class CounterLive {
public static function mount(_params: Term, _session: Term, socket: Socket<CounterAssigns>): MountResult<CounterAssigns> {
var liveSocket: LiveSocket<CounterAssigns> = socket;
return Ok(liveSocket.assign(_.count, 0));
}
@:native("handle_event")
public static function handle_event(event: String, _params: Term, socket: Socket<CounterAssigns>): HandleEventResult<CounterAssigns> {
var liveSocket: LiveSocket<CounterAssigns> = socket;
return switch (event) {
case "increment":
NoReply(liveSocket.assign(_.count, liveSocket.assigns.count + 1));
case _:
NoReply(liveSocket);
};
}
public static function render(assigns: CounterAssigns): String {
return <div class="counter">
<h1>${assigns.count}</h1>
<button phx-click="increment">+</button>
</div>;
}
}Template control flow in TSX mode:
typedef Item = { var name: String; };
public static function render(assigns: { var items: Array<Item>; }): String {
return <ul>
<for ${item in assigns.items}>
<li>${item.name}</li>
</for>
</ul>;
}TSX also supports :for directive sugar on elements:
return <ul>
<li :for ${item in assigns.items}>${item.name}</li>
</ul>;This lowers to a HEEx for block around the element so item stays Haxe-typed in the body.
If you need expression-level composition in balanced mode, phoenix.hxx.HeexTemplate.for_each/2
(or phoenix.hxx.H.each/2 / phoenix.hxx.H.for_each/2) lowers to the same HEEx for block.
Typed spread attrs in TSX mode:
return <section {assigns.attrs} data-testid="users"></section>;Compiles to HEEx spread attrs:
<section {@attrs} data-testid="users"></section>Notes:
- Inline markup is enabled by default for Phoenix-facing modules; opt out with
-D hxx_no_inline_markupor@:hxx_no_inline_markup. - Modes (TSX is named after TypeScript JSX to signal strict, expression-typed template authoring):
@:hxx_mode("tsx")(recommended): strict typed template authoring, no legacy string markers, no raw HEEx escapes.@:hxx_mode("balanced")(default): typed inline markup plus legacy template-string support for migration.@:hxx_mode("metal")(discouraged): closest to raw HEEx; useful only for edge escape-hatch scenarios.
- Recommended strict profile for new apps (compiler defaults remain permissive):
-D hxx_strict_phx_hook-D hxx_strict_phx_events-D hxx_strict_components-D hxx_strict_slots-D hxx_strict_attr_values
- Raw HEEx escape hatch:
@:allow_heex(or migration-only-D hxx_allow_raw_heex). - Legacy inline-markup rewrite escape hatch:
@:hxx_legacy. - Haxe inline markup requires a valid XML root tag name. Phoenix dot-components like
<.form>cannot be the root; wrap them in a normal element (e.g.<div>...</div>). - Haxe inline markup does not support fragment roots (
<> ... </>).
More: docs/02-user-guide/INLINE_MARKUP.md
Modes and comparison: docs/02-user-guide/HXX_SYNTAX_AND_COMPARISON.md
Compiles to:
defmodule CounterLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, :count, 0)}
end
def handle_event("increment", _params, socket) do
count = socket.assigns.count + 1
{:noreply, assign(socket, :count, count)}
end
def render(assigns) do
~H"""
<div class="counter">
<h1><%= @count %></h1>
<button phx-click="increment">+</button>
</div>
"""
end
endNote: prefer unqualified enum constructors when unambiguous (Ok(...), NoReply(...)); they compile to standard Elixir atom-tagged tuples ({:ok, ...}, {:noreply, ...}).
import ecto.Changeset;
// 1) Typed params accepted by the changeset boundary.
typedef UserParams = {
?name: String,
?email: String
}
// 2) Schema metadata drives generated Ecto schema + changeset pipeline.
@:native("MyApp.Accounts.User")
@:schema("users")
@:timestamps
@:changeset(cast(["name", "email"]), validate(["name", "email"]))
class User {
// 3) Fields compile to Ecto schema fields.
@:field @:primary_key public var id: Int;
@:field public var name: String;
@:field public var email: String;
}
class Users {
// 4) App code uses typed changesets and typed Result flow.
// `@:schema` auto-injects a typed `changeset<Params>(schema, params)` declaration.
public static function create(params: UserParams): haxe.functional.Result<User, Changeset<User, UserParams>> {
var changeset = User.changeset(new User(), params);
return MyApp.Repo.insert(changeset);
}
}Legacy compatibility form (still supported):
@:changeset(["name", "email"], ["name", "email"])Optional compatibility path (usually unnecessary):
class User {
// Keep this only when you intentionally want an explicit declaration.
extern public static function changeset(user: User, params: UserParams): Changeset<User, UserParams>;
}Compiles to:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :email, :string
timestamps()
end
def changeset(user, params) do
user
|> cast(params, [:name, :email])
|> validate_required([:name, :email])
end
end
defmodule Users do
def create(params) do
changeset = MyApp.Accounts.User.changeset(%MyApp.Accounts.User{}, params)
MyApp.Repo.insert(changeset)
end
endMore: docs/07-patterns/ECTO_INTEGRATION_PATTERNS.md
import elixir.types.Atom;
import elixir.types.GenServerCallbackResults.HandleCallResult;
import elixir.types.GenServerCallbackResults.InitResult;
import elixir.types.Term;
enum abstract CounterCall(Atom) to Atom {
var Get = "get";
var Increment = "increment";
}
@:genserver
class CounterServer {
public static function init(initial: Int): InitResult<Int> {
return Ok(initial);
}
@:native("handle_call")
public static function handle_call(request: CounterCall, _from: Term, state: Int): HandleCallResult<Int, Int> {
return switch (request) {
case Get:
Reply(state, state);
case Increment:
Reply(state + 1, state + 1);
}
}
}Compiles to:
defmodule CounterServer do
use GenServer
def init(initial) do
{:ok, initial}
end
def handle_call(:get, _from, state) do
{:reply, state, state}
end
def handle_call(:increment, _from, state) do
next_state = state + 1
{:reply, next_state, next_state}
end
endThe project uses a dual-ecosystem testing approach with self-referential library configuration:
npm test # Full suite (snapshots + Elixir validation + Mix)
npm run test:quick # Snapshot suite only
npm run test:mix # Mix/Elixir tests only
npm run test:generator # Generator + Mix task scaffolds
npm run test:update # Update expected snapshot outputs
npm run qa:sentinel # Todo-app build + boot probe (async)
npm run ci:guards # Guardrails (no app heuristics, etc.)Test Infrastructure:
- Complete Coverage:
npm testruns Haxe compiler tests, generator tests, AND Mix runtime tests - Snapshot Testing: Validates compiler output against expected Elixir code
- Generator Testing: Validates project templates and tooling
- Runtime Validation: Mix tests compile/run generated Elixir code
- Self-Referential Library: Tests use
-lib reflaxe.elixirviahaxe_libraries/reflaxe.elixir.hxml - Mix Integration: Tests real compilation in Phoenix projects
- Test Helper:
test/support/haxe_test_helper.exhandles project setup
For detailed testing documentation, see docs/03-compiler-development/TESTING_INFRASTRUCTURE.md
# Start file watching for instant feedback
mix haxe.watch
# In another terminal, make changes
vim src_haxe/MyModule.hx # Files auto-compile on save
# Or for compiler development:
vim src/reflaxe/elixir/ElixirCompiler.hx
npm test # Test compiler changesPerfect for AI-assisted development with fast feedback loops:
# Start watching with LLM-friendly output
mix haxe.watch --verbose
# LLM creates/modifies .hx files → automatic compilation
# Sub-second feedback enables rapid iteration- lix: Modern Haxe package manager with GitHub sources
- npm: JavaScript ecosystem integration and script orchestration
- Benefits: Project-specific Haxe versions, zero global conflicts
- Native Elixir tooling: Industry standard for BEAM development
- Phoenix ecosystem: Seamless LiveView, Ecto, OTP integration
- Generated code validation: Tests the actual output, not just compilation
All compilation targets exceed performance requirements:
- Basic compilation: 0.015ms (750x faster than 15ms target) ⚡
- Ecto Changesets: 0.006ms average (2500x faster) ⚡
- Migration DSL: 6.5μs per migration (2300x faster) ⚡
- OTP GenServer: 0.07ms average (214x faster) ⚡
- Phoenix LiveView: <1ms average (15x faster) ⚡
CI is the source of truth (see the CI badge above). Locally:
This validates that a brand-new project can install Reflaxe.Elixir from a vX.Y.Z release and compile a generated Phoenix app.
mkdir -p /tmp/reflaxe_elixir_verify && cd /tmp/reflaxe_elixir_verify
npm init -y
npm install --save-dev lix
npx lix scope create
# Pin a known-good Haxe toolchain (avoids relying on a global install).
npx lix download haxe 4.3.7
npx lix use haxe 4.3.7
# Install the latest GitHub release tag (recommended).
# If this fails (no `curl` / GitHub rate limit), pick a tag from the Releases page and set it manually.
REFLAXE_ELIXIR_TAG="$(curl -fsSL https://api.github.com/repos/fullofcaffeine/reflaxe.elixir/releases/latest | sed -n 's/.*\"tag_name\":[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -n 1)"
npx lix install "github:fullofcaffeine/reflaxe.elixir#${REFLAXE_ELIXIR_TAG}"
npx lix download
# Generate + compile a Phoenix app (compile-only smoke)
REFLAXE_ELIXIR_SRC="$(./node_modules/.bin/haxelib path reflaxe.elixir | tr -d '\r' | grep -E 'reflaxe\.elixir/.*/src/?$' | head -n 1)"
./node_modules/.bin/haxe -cp "$REFLAXE_ELIXIR_SRC" --run Run create my_app --type phoenix --no-interactive --skip-install
cd my_app
npm install --no-audit --no-fund
npx lix scope create
npx lix install "github:fullofcaffeine/reflaxe.elixir#${REFLAXE_ELIXIR_TAG}"
npx lix download
mix deps.get
mix compileMaintainers: this flow is also verified weekly in CI by the scheduled workflow README Release Smoke (scheduled).
npm run ci:guardsnpm testnpm run test:examplesnpm run test:examples-elixirnpm run ci:budgetsnpm run qa:sentinel(todo-app boot + Playwright smoke; non-blocking)
See docs/10-contributing/contributing.md for detailed development guide. Releases are published automatically via semantic versioning; see docs/10-contributing/RELEASING.md.
- Extend the AST pipeline (
src/reflaxe/elixir/ast/) in builder/transformer/printer layers - Add/adjust std externs in
std/when exposing Elixir/Phoenix/Ecto APIs - Add snapshot coverage under
test/snapshot/(and update intended outputs if needed) - Run
npm testandnpm run qa:sentinel
This repo uses Beads (bd) for dependency-aware task tracking and a lightweight plans/ directory for decision records.
- Issues live in
.beads/issues.jsonl(git-native export) and are edited withbd.- Common commands:
bd list,bd show <id>,bd edit <id>,bd update <id> --body-file <file>,bd close <id>
- Common commands:
- Plans live under
plans/:plans/active/while any related beads tasks are still openplans/archived/once all related beads tasks are closed
- Source-of-truth rule:
- Beads tasks must be decision-complete (files, algorithm, failure modes, tests, verification).
- Plans are historical context and cross-task coherence; tasks should not require reading a plan to implement.
See:
plans/README.mdfor the plan lifecycle and the task spec contract.beads/README.mdfor Beads usage
- Near-term priorities: ROADMAP.md
- Long-term direction: docs/08-roadmap/vision.md
- 1.0 readiness checklist: docs/06-guides/PRODUCTION_READINESS.md
GPL-3.0 - See LICENSE for details
