reflaxe.ruby is a Reflaxe-based Haxe target that emits readable,
idiomatic Ruby from typed Haxe. It also includes RailsHx, a Rails-native typed
authoring layer for ActiveRecord, ActionController, ActionView/HHX, routing,
Turbo, ActionCable, generators, migrations, tests, and installed-gem companion
contracts such as DeviseHx.
The current 0.1.0-beta.2 baseline supports executable Ruby smoke
fixtures, shared hxruby runtime files and gem packaging, Ruby interop
metadata, typed extension/mixin contracts, and a RailsHx dogfood app with typed
models, relations, migrations, controllers, params, HHX templates, Haxe-owned
routes, Devise-backed sessions, Turbo Streams, Haxe-authored browser code,
Rails tests, Playwright, and production smoke coverage.
The project intentionally uses 0.x beta versioning until the compiler and RailsHx framework layer are production-ready.
There are two first-class layers:
- RubyHx / pure Ruby: compile Haxe to normal Ruby, consume Ruby gems through typed externs/contracts, author Ruby modules/concerns from Haxe, and keep generated Ruby readable to Ruby developers.
- RailsHx: a Rails-native typed authoring layer on top of the same Ruby compiler. Haxe/HHX is source of truth; Rails still owns runtime tasks such as
db:migrate,test, Zeitwerk, and assets.
Rails is the flagship framework target because it is the dominant Ruby framework, but it is not the only intended use of haxe.ruby. Other framework layers can live in this monorepo or in separate repos that consume the published haxelib/gem and add their own typed std/macros/generators.
Install repository dependencies and run the default compiler/test suite:
npm install
npm testnpm test is intentionally broad: it runs compiler snapshots, smoke tests,
example compilation, package checks, strict-boundary policy checks, and fast
RailsHx materialization checks. Full Rails runtime/browser/production lanes are
documented below.
Compile a Haxe entrypoint by setting ruby_output and loading the compiler macros:
haxe \
-D ruby_output=out/ruby \
-D reflaxe_runtime \
-cp src \
-cp examples/hello_world \
-cp vendor/reflaxe/src \
--macro "reflaxe.ruby.CompilerBootstrap.Start()" \
--macro "reflaxe.ruby.CompilerInit.Start()" \
-main Main
ruby out/ruby/run.rbFor a guided public entrypoint, start with:
- Pure Ruby: examples/hello_world, Ruby Extension Interop, and examples/ruby_extensions.
- RailsHx: examples/todoapp_rails for a full app, examples/rails_routes_dsl for focused Haxe-owned route snapshots, and examples/rails_interop_app for gradual adoption from existing Ruby/ERB.
- DeviseHx: docs/railshx-devisehx-design.md for the companion-layer design and examples/todoapp_rails for the current app-local auth contract in a real Rails flow.
- Documentation map: docs/README.md.
ruby_output=<dir>: output directory used by Reflaxe.reflaxe_runtime: emits/copies sharedhxrubyruntime helpers.reflaxe_ruby_profile=ruby_first|portable: declares the Ruby profile contract; default isruby_first. The legacyidiomaticvalue remains accepted.reflaxe_ruby_rails: writes app-owned code underapp/haxe_genand emits a Rails autoload initializer.reflaxe_ruby_rails_output_root=<path>: overrides the Rails output root; default isapp/haxe_gen. Use safe relative paths such asengines/blog/app/haxe_genfor engine/plugin output; see RailsHx Engines And Plugins Guide.reflaxe_ruby_strict_examples: rejects raw__ruby__injection in repo examples/snapshots.reflaxe_ruby_strict: rejects raw__ruby__injection in user/project sources.reflaxe_ruby_strict_policy=auto|on|off: policy hook for strict user boundaries.
See Ruby Profiles for the profile contract: both profiles should emit idiomatic Ruby where safe, portable preserves Haxe semantics first, and ruby_first is the Ruby-first default. Profiles are semantic guardrails in one compiler pipeline, not separate backends.
Interop is typed through metadata and small std surfaces:
@:native("RubyName")maps Haxe symbols to Ruby constants or methods.@:rubyRequire("json")emitsrequire "json".@:rubyRequireRelative("./support/foo")emitsrequire_relative "./support/foo".@:rubyKwargslowers trailing object literals into Ruby keyword args.@:rubyBlockArglowers trailing function args into Ruby blocks.@:rubyMixin,@:rubyInclude,@:rubyPrepend, and@:rubyExtendmodel Ruby module extension APIs as typed Haxe contracts while emitting normal Rubyinclude/prepend/extend.@:rubyPatch(ReceiverType)plus Haxeusingmodels monkey-patched receiver APIs, including ActiveSupport-style extensions, as typed Haxe calls that lower to direct Ruby receiver dispatch.@:rubyModule("Name")and@:rubyConcern("Name")let Haxe author Ruby modules and ActiveSupport::Concern-style modules directly.ruby.Symbol.of("ready")lowers to:ready.
Rails/ActiveSupport facades are typed std contracts over real Rails APIs:
using rails.active_support.ObjectPresence;
using rails.active_support.StringFilters;
var normalized = " Ship typed Rails ".squish();
var maybeTitle = normalized.presence();
if (maybeTitle.present()) trace(maybeTitle);Generated Ruby requires the matching ActiveSupport core extension files and calls the patched receivers directly.
Raw __ruby__ injection exists as an escape hatch, but examples and
production-style code should prefer typed externs, generated contracts, or
std/runtime wrappers. The strict boundary defines enforce that policy.
See Ruby Extension Interop for examples ranging from simple module include/extend through existing gem wrapping, gradual adoption, Haxe-owned libraries, and metaprogramming-heavy contract generation. For installed Rails gems such as Devise, see RailsHx Gem Layers: Ruby/Bundler owns the runtime gem, while RailsHx provides typed contracts, macros, generators, and reusable companion packages when a gem is common enough. The reusable DeviseHx layer starts from the folded design review, which was produced from the GPT 5.5 Pro design prompt.
Rails mode is enabled with -D reflaxe_ruby_rails. It emits Haxe-owned app files under app/haxe_gen, plus:
config/initializers/hxruby_autoload.rb- Rails-friendly constant/file paths for Zeitwerk
- typed
rails.active_record.Base<T>model classes - generated ActiveRecord schema metadata via
Model.__hx_rails_schema - generated Rails migrations from Haxe-authored
@:railsMigration(...)classes - typed
rails.action_controller.Basecontroller classes ParamsMacro.requirePermit(...)for strong params- model metadata for
@:belongsTo,@:hasMany,@:hasOne,@:validates,@:railsEnum, and typed callback metadata such as@:beforeValidation
The canonical RailsHx end-to-end example is examples/todoapp_rails. The mixed Rails adoption example is examples/rails_interop_app.
RailsHx is not a Rails replacement. Rails still owns runtime execution:
bin/rails db:migrate, bin/rails test, Zeitwerk, assets, ActionCable,
Devise/Warden, and app boot remain ordinary Rails. RailsHx owns typed Haxe/HHX
source, compile-time validation, generated Rails-shaped artifacts, and a better
developer workflow around those artifacts.
Run the generated Rails todo app locally:
rake todoapp:startThen open http://127.0.0.1:3000/. The app now demonstrates a protected
Devise-backed board, guest sign-in, user-scoped todos, admin-only user
management, typed ActiveRecord relations, Haxe-authored HHX views,
Turbo Streams chat, Haxe-authored browser code, Rails request/model tests, and
Playwright UX checks.
For the RailsHx development loop, start the app with the integrated watcher:
rake todoapp:start:watch
# or:
WATCH=1 rake todoapp:start
# or:
rake 'todoapp:start[watch]'That prepares the app once, runs Rails and the RailsHx watcher together, and refreshes generated Rails files when Haxe/HHX or Haxe-authored JS changes.
For a real-browser RailsHx smoke, run the Playwright sentinel lane:
rake todoapp:playwrightThat prepares the generated Rails app, boots Rails on a dedicated port, runs examples/todoapp_rails/e2e/*.spec.ts, and tears the server down.
The browser lane also compiles the optional Haxe-authored Playwright spec from
examples/todoapp_rails/e2e_haxe/** into disposable ES-module specs under
examples/todoapp_rails/e2e/generated/**; vanilla TypeScript specs remain
first-class. For the lightweight compile/output-shape check without booting
Rails, run npm run test:haxe-playwright.
For the tutorial-style walkthrough of the generated skeleton and todoapp patterns, see RailsHx Skeleton And Todoapp Tutorial.
For gradual adoption of an existing Rails app or a quick Ruby/ERB PoC, use typed boundaries instead of an all-at-once rewrite:
npm run test:rails-interopThat lane proves Haxe can render existing ERB through Template.external("path") : Template<TLocals>, call existing Ruby through typed externs, and let legacy ERB consume generated Haxe services/partials as normal Rails artifacts. See docs/railshx-gradual-adoption.md.
To force the generated Rails runtime apps to install their bundles and execute Rails tests, run:
rake test:rails:runtimeThis is the mandatory runtime lane for Rails coverage across the supported Ruby matrix (3.2, 3.3, 4.0). Plain rake test/npm test keeps the compiler loop fast and still syntax-checks generated Rails artifacts; the Rails runtime lanes skip only when Rails gems are unavailable in a local environment. CI runs the underlying npm script, so missing generated-app Rails gems become hard failures there.
For a Rails app adoption scaffold, generate the RailsHx source layout, compile config, rake hook, dev process files, and a small typed starter app:
rake rails:app ARGS="--output path/to/rails-app --name MyApp"The generated starter includes a typed HomeController, HHX layout, HHX home
page, Haxe-owned root route, route-helper extern placeholder, Haxe-authored
client JS, CSS/importmap wiring, app-local Rake tasks, bin/railshx-* helpers,
and docs/railshx/gem_layers.md for deterministic-first installed-gem
wrapping. Rails-facing generators are implemented in Ruby and exposed through
bin/rails generate hxruby:*, repository Rake wrappers, and installed-app
hxruby rake tasks, following the same host-framework-native generator lesson
as PhoenixHx Mix tasks. npm remains repo infrastructure for Lix, Playwright,
semantic-release, and Node-based CI scripts; the RailsHx user-facing path is
Rake/Rails.
In an installed Rails app, prefer the Rails generator entrypoints:
bin/rails generate hxruby:install MyApp
bin/rails generate hxruby:routes
bin/rails generate hxruby:controller Todos index show --templates
bin/rails generate hxruby:mailer UserMailer welcome
bin/rails generate hxruby:scaffold Todo title:String isCompleted:Bool --controller
bin/rails generate hxruby:scaffold Todo title:String --controller --skip-tests
bin/rails generate hxruby:adopt --service LegacyPriceFormatter --template legacy/badge --locals label:String,tone:String
bin/rails generate hxruby:adopt --service RbsPriceFormatter --rbs sig/rbs_price_formatter.rbs
bin/rails generate hxruby:adopt --schema --discover
bin/rails generate hxruby:adopt --schema --models Todo,User
bin/rails generate hxruby:adopt --migrations --discover
bin/rails generate hxruby:adopt --discoverInside a generated RailsHx app, the recommended development flow is one command:
bundle exec rake hxruby:startFor the edit loop, run Rails and both Haxe watchers together:
bundle exec rake hxruby:start:watch
# or:
WATCH=1 bundle exec rake hxruby:start
# or:
bundle exec rake 'hxruby:start[watch]'Generated apps also include bin/railshx-dev, which starts Rails, the server Haxe watcher, and the client Haxe watcher through foreman or overmind when either tool is installed. Without those tools it falls back to bundle exec rake hxruby:start:watch. If you need the lower-level pieces for CI debugging, use bundle exec rake hxruby:compile, bundle exec rake hxruby:compile:client, bundle exec rake hxruby:watch, and bundle exec rake hxruby:watch:client directly.
For Rails tasks that consume generated artifacts, use the RailsHx-prefixed compile-then-delegate tasks:
bundle exec rake hxruby:db:migrate # compile Haxe migrations, then rails db:migrate
bundle exec rake hxruby:db:prepare # compile Haxe artifacts, then rails db:prepare
bundle exec rake hxruby:test # compile server/client artifacts, then rails test
bundle exec rake hxruby:rails TASK=zeitwerk:check
bundle exec rake hxruby:doctor # non-mutating health report for Haxe/build files/manifests
bundle exec rake hxruby:check # compile and ruby -c generated Ruby
bundle exec rake hxruby:clean # remove manifest-owned generated artifactsRaw bin/rails db:migrate, bin/rails test, and other Rails tasks still work
when artifacts are already current. The hxruby:* variants are the safer daily
path because they refresh generated Ruby, ERB, migrations, route files, and
client JS before Rails consumes them.
Use hxruby:doctor when onboarding or debugging a generated app: it verifies
Haxe availability, build files, JSON manifests, Rails command availability, and
configured output roots without mutating the app. It also reports manifest output
drift/missing files, stale Haxe-owned route externs, duplicate Rails migration
timestamps/classes, and likely Haxe-authored client JS/importmap gaps. Use
hxruby:check in CI for a fast generated-artifact gate; add CLIENT=1,
ROUTES=1, or ZEITWERK=1 when that CI lane should also compile Haxe-authored
JavaScript, sync route externs, or delegate to Rails' zeitwerk:check.
For production builds, compile Haxe/HHX before the normal Rails build/release steps so generated app/haxe_gen/**, generated ActionView templates, generated db/migrate/** files, and config/initializers/hxruby_autoload.rb exist in the release artifact:
bin/railshx-prod
# or, in CI/buildpacks:
RAILS_ENV=production bundle exec rake hxruby:productionhxruby:production runs the server Haxe compile, client Haxe compile, rails zeitwerk:check, and rails assets:precompile with production defaults. It intentionally fails closed if required Haxe build files are missing, so releases do not accidentally ship stale generated Ruby, ERB, migrations, or JavaScript.
The canonical dogfood app has a production smoke that exercises the same shape end to end:
rake todoapp:productionThat command compiles Haxe/HHX, compiles Haxe-authored JS, materializes the generated Rails app, runs Rails migrations/tests, runs zeitwerk:check, precompiles production assets, creates test/.generated/rails_integration_release.tgz, and verifies the release artifact includes generated RailsHx files.
RailsHx is still an experimental beta framework layer with a credible
production path, not yet a production-ready contract. Production readiness is
tracked by the haxe.ruby-bjv bead epic and documented in
docs/railshx-production-readiness.md.
The RailsHx work is tracked in docs/railshx-roadmap.md, covering typed ActiveRecord, migrations, controllers, routes, generators, and integration tests inspired by the Phoenix/Ecto implementation in ../haxe.elixir.codex. Start with docs/railshx-generator-workflows.md for app-facing generator commands, generated artifacts, runtime handoff, diagnostics, and CI gates. See docs/railshx-generators-and-tasks-design.md for the generated app skeleton and Rake/Rails task contract, docs/railshx-routing-design.md for Haxe-owned and Rails-owned route source-of-truth modes, docs/railshx-controller-guide.md for typed controllers/params, docs/railshx-action-mailer-guide.md for typed ActionMailer classes and HHX mail templates, docs/railshx-active-job-guide.md for typed ActiveJob classes and enqueue helpers, docs/railshx-active-storage-guide.md for typed ActiveStorage refs, docs/railshx-turbo-guide.md for typed Turbo client and server-side stream helpers, docs/railshx-action-cable-guide.md for typed ActionCable channels/subscriptions, docs/railshx-instrumentation-guide.md for typed ActiveSupport instrumentation, docs/railshx-components-guide.md for typed Rails-native components, docs/railshx-engines-plugins-guide.md for engine/plugin output roots and host-app consumption, docs/railshx-gradual-adoption.md for mixed Ruby/ERB and Haxe adoption patterns, and docs/railshx-haxe-authored-testing-design.md for the optional Haxe-authored Ruby/JS test-layer design.
Useful tooling:
rake rails:routes ARGS="--input routes.txt --output src_haxe/routes/Routes.hx"
rake rails:controller ARGS="Todos index show --templates --output tmp/todo"
rake rails:scaffold ARGS="--model Todo --fields title:String,isCompleted:Bool --validate title --controller --output tmp/todo"
rake rails:adopt ARGS="--service LegacyPriceFormatter --template legacy/badge --locals label:String,tone:String --output tmp/rails_app"
rake rails:app ARGS="--output tmp/rails_app --name TodoApp"rake test:rails:integration materializes a generated Rails app and always syntax-checks Ruby files. It runs rails db:migrate and rails test when Rails gems are installed. rake test:rails:runtime sets REQUIRE_RAILS=1, installs generated app bundles when needed, and makes both Rails integration and mixed-interop runtime execution mandatory.
RailsHx routing supports both ownership directions. Existing Rails apps can keep Rails-owned config/routes.rb and generate typed Haxe externs from rails routes. Greenfield RailsHx apps can use Haxe-owned @:railsRoutes sources that emit normal config/routes.rb; Rails still remains the route-helper naming oracle by feeding rails routes back into Routes.hx. See docs/railshx-routing-design.md and the focused examples/rails_routes_dsl snapshot fixture.
Devise routes follow the same principle. A Haxe-owned route file can declare
DeviseRoutes.deviseFor(UserAuth.scope) for the supported no-options MVP; the
compiler emits ordinary devise_for :users, validates the Devise mapping by
booting Rails, and still generates typed route helpers from actual
rails routes output. More complex Devise route options remain Rails-owned
until their typed DSL phases land.
See docs/compatibility-matrix.md.
The CI contract targets:
- Haxe
4.3.7 - Node
20 - Ruby
3.2,3.3, and4.0
Local Ruby 2.6 can still run some non-Rails smoke tests, but it is not the supported runtime baseline for Rails-oriented work.
Use rbenv for local Rails/compiler work. The repo pins Ruby with .ruby-version; install that version and initialize rbenv in your shell:
brew install rbenv ruby-build
rbenv install
eval "$(rbenv init - zsh)"
ruby -vFor mandatory Rails runtime integration, use the pinned Ruby and let the generated apps install their own bundles:
rake test:rails:runtimerake test/npm test will run Rails runtime checks when generated app bundles are already available. rake test:rails:runtime makes missing Rails gems a hard failure and installs the generated bundles first, matching the dedicated CI lane. Runtime logs are stage-labeled (compiler, materialization, migration, request tests, and browser stages) so CI failures point at the failing boundary.
npm test
npm run test:examples-compile
rake format:haxe:check
rake security:gitleaks
rake test:snapshots
rake test:strict_boundaries
rake test:rails:runtime
rake ci:version_sync
rake ci:release_contracts
rake package:gem:testSnapshot tests compile with reflaxe_ruby_strict_examples, compare committed
Ruby output, reject CRLF/trailing-newline/path leaks, and compile each snapshot
case twice to catch non-deterministic output.
npm run test:examples-compile compiles every examples/*/Main.hx entrypoint
and known example client builds, then verifies each example has an explicit
snapshot/smoke/runtime/browser coverage contract. This keeps examples useful as
living documentation and as an additional compiler QA lane.
Snapshots are the primary compiler/codegen contract: they show the exact Ruby, ERB, JS, migration, initializer, and runtime-support artifacts that Rails/Ruby users should be able to review as hand-written-looking output. Smoke tests are supporting gates for focused invariants such as required files, syntax checks, negative Haxe compile failures, package/generator flows, and thin Rails consumption seams. Runtime Rails tests should prove that Rails can load/render/ migrate/deliver/subscribe to generated artifacts; they should not broadly retest Rails itself unless RailsHx adds custom runtime behavior. See RailsHx Testing Strategy for the snapshot-vs-smoke decision rules.
Install the repo-managed pre-commit hook:
haxelib install formatter
brew install gitleaks # or use another gitleaks install method
rake hooks:installThe hook runs a staged gitleaks scan and formats staged .hx files with haxe-formatter. CI runs the full Haxe formatter check and a dedicated gitleaks workflow, so local hooks catch the same class of issues before review.
Build the release zip locally with:
rake package:haxelib:buildValidate the package contents, compile the extracted examples/hello_world fixture, and smoke-test an installed -lib reflaxe.ruby consumer with:
rake package:haxelib:testSemantic-release runs the same package builder during release preparation and attaches dist/reflaxe.ruby-*.zip to the GitHub release.
The incubated DeviseHx Haxe API currently ships inside this haxelib package
under std/devisehx/**. Its release contract is documented in
DeviseHx Release Lane: Rails apps keep
the Devise gem in their own Bundler environment, while reflaxe.ruby ships the
typed Haxe companion API and checks package contents in
npm run test:haxelib-package.
Build the hxruby runtime gem locally with:
rake package:gem:buildValidate the gem contents, runtime require path, rake task registration, and local gem install behavior with:
rake package:gem:testThe gem exposes require "hxruby" for runtime helpers and require "hxruby/tasks" for Rails-oriented rake tasks:
bin/rails generate hxruby:install MyApp
bin/rails generate hxruby:routes
bin/rails generate hxruby:scaffold Todo title:String isCompleted:Bool --controller
bin/rails generate hxruby:adopt --service LegacyPriceFormatter --template legacy/badge --locals label:String,tone:String
bin/rails generate hxruby:adopt --service RbsPriceFormatter --rbs sig/rbs_price_formatter.rbs
bin/rails generate hxruby:adopt --schema --discover
bin/rails generate hxruby:adopt --schema --models Todo,User
bin/rails generate hxruby:adopt --migrations --discover
rake hxruby:compile
rake hxruby:compile:client
rake hxruby:db:migrate
rake hxruby:db:prepare
rake hxruby:db:rollback
rake hxruby:start
rake hxruby:start:watch
rake hxruby:routes
rake hxruby:doctor
rake hxruby:check
rake hxruby:clean
rake hxruby:test
rake hxruby:rails TASK=zeitwerk:check
rake hxruby:production
rake hxruby:watch
rake hxruby:watch:client
rake hxruby:gen:app
rake hxruby:gen:adopt SERVICE=LegacyPriceFormatter TEMPLATE=legacy/badge LOCALS=label:String,tone:String
rake hxruby:gen:adopt SERVICE=RbsPriceFormatter RBS=sig/rbs_price_formatter.rbs
rake hxruby:gen:routes
rake hxruby:gen:model MODEL=Todo FIELDS=title:String CONTROLLER=1
rake hxruby:gen:mailer MAILER=UserMailer ACTION=welcome
rake hxruby:gen:template PATH=controllers/todos/_card LOCALS=title:String,count:Int
rake hxruby:gen:test NAME=models/todoGreenfield app/scaffold generators default to Haxe-owned routes. Pass
--routes=haxe|snippet|rails|none to choose the route source-of-truth mode:
haxe emits typed @:railsRoutes, rails keeps route helper extern generation
for an existing Rails-owned config/routes.rb, snippet writes reviewable
instructions, and none leaves route files untouched.
Scaffolds also generate a small Haxe-authored Rails model test by default under
test_haxe/**. The Haxe test compiles through @:railsTest into normal
Minitest output under test/generated/**, so the starter app exercises both
typed source and Rails-native test artifacts. Use --skip-tests only when an
existing test layout owns that boundary already.
When --controller is enabled, the scaffold composes the controller generator's
typed HHX view path too: the index action renders Template.of(IndexView) with
typed locals, and the compiler emits ordinary Rails ERB under app/views/**.
Plain require "hxruby" has no gem runtime dependencies. The task entrypoint requires rake, which is available in the supported CI Rubies and normal Rails applications.
For DeviseHx, hxruby is the generator bridge rather than an auth runtime: it
exposes bin/rails generate hxruby:adopt --gem devise, writes deterministic
inventory/contracts/docs under app ownership, and does not add a Devise runtime
dependency to the hxruby gem. See
DeviseHx Release Lane for the split
criteria before publishing a standalone devisehx or hxruby-devise package.
Semantic-release builds the gem during release preparation and attaches dist/hxruby-*.gem to the GitHub release.
The std/runtime gap report is generated from docs/stdlib-inventory.json.
npm run test:gap-report
UPDATE_GAP_REPORT=1 npm run test:gap-reportSee docs/gap-report-guidance.md for how to update inventory entries and interpret remaining gaps.
src/reflaxe/ruby: compiler, build context, naming, macros, and Ruby AST printer.std: additive Ruby/Rails Haxe APIs and target std surfaces.std/_std: upstream Haxe std overrides that must take classpath precedence.runtime/hxruby: shared Ruby runtime helpers copied into generated output.examples: executable compiler/Rails fixtures.scripts/ci: smoke, snapshot, inventory, release, and hardening checks.scripts/rails: Rails-oriented generators.scripts/hooks,scripts/lint,scripts/security: local hook installer, Haxe formatter guard, and secret scanning wrapper.test/snapshots: committed generated Ruby contracts.