Skip to content

feat: Opal payload generator, boot file, and loader#69

Merged
ronaldtse merged 2 commits into
mainfrom
feat/opal-payload-and-boot
Jun 27, 2026
Merged

feat: Opal payload generator, boot file, and loader#69
ronaldtse merged 2 commits into
mainfrom
feat/opal-payload-and-boot

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

Adds Opal compatibility to unitsml so consumers like plurimath-js can compile the gem with -r unitsml/opal. unitsml is unique among the moxml-dependent gems: it bundles a large unitsdb YAML database that must be pre-serialized as a Ruby hash under Opal (no filesystem).

  • Adds lib/unitsml/opal/payload_generator.rb (Unitsml::Opal::PayloadGenerator) — a class that reads unitsdb-ruby's YAML data on MRI and emits a Ruby source file defining Unitsml::Unitsdb::Database::DATABASE as a frozen hash.
  • Adds lib/unitsml/opal/database_payload.rb — AUTO-GENERATED output (run via bundle exec rake unitsml:generate_opal_payload) committed to the gem so Opal consumers don't need a filesystem.
  • Adds lib/unitsml/opal.rb boot file eager-requiring every autoloaded entry point in dependency order, including upstream boot files (unitsdb/opal, mml/opal, omml/opal) and the payload.
  • Adopts the loader mechanism from fix/opal-database-payload-loader (load_opal_payload / opal_payload / reset_opal_payload) so tests can inject small payloads and the committed DATABASE constant is found via const_defined?(:DATABASE, false). Supersedes PR Add Opal database payload loader #65.
  • Converts lib/unitsml/unitsdb.rb from require_relative to Ruby autoload and normalizes __dir__-interpolated autoloads in the four nested model parent files to plain load-path-relative autoload declarations, per the global rule.
  • Removes a dangling autoload (Unitsml::Model::Namespace) whose target file never existed; MRI never tripped on it because nothing referenced the constant, but Opal eager-loads every autoload and would have crashed.
  • Adds a GHA workflow (.github/workflows/opal.yml) and three new specs covering boot file structure, payload sync, and PayloadGenerator output.

Out of scope (future PR)

The moxml gem itself doesn't yet ship an Opal boot file (moxml issue/PR TBD). The unitsml boot file stubs ox and friends when compiled by Opal::Builder for verification purposes; the gem's Opal consumer (plurimath-js) provides these at runtime.

Test plan

  • bundle exec rspec — 398 examples, 0 failures (was 380; +18 from opal specs)
  • bundle exec rubocop — clean on all changed files (pre-existing gemspec sort offense on main unchanged)
  • bundle exec rspec spec/unitsml/opal_boot_spec.rb — 5 examples, 0 failures
  • bundle exec rspec spec/unitsml/payload_sync_spec.rb — 4 examples, 0 failures
  • bundle exec rspec spec/unitsml/opal/payload_generator_spec.rb — 7 examples, 0 failures
  • bundle exec rake unitsml:generate_opal_payload regenerates the committed payload byte-for-byte
  • GHA opal workflow runs the opal specs on Ubuntu / Ruby 3.3 / Node 18

Under Opal, autoload declarations do not lazy-execute, so consumers
(plurimath-js) need an explicit boot file that eager-requires every
autoloaded entry point in dependency order. unitsml is unique among
the moxml-dependent gems: it ships a large unitsdb YAML database that
must be pre-serialized as a Ruby hash under Opal (no filesystem).

This change adds the full Opal toolchain for unitsml:

- lib/unitsml/opal.rb: boot file eager-requiring every autoloaded
  entry point. Requires upstream boot files (unitsdb/opal, mml/opal,
  omml/opal) and the payload file so Database.from_db can satisfy
  without a filesystem.

- lib/unitsml/opal/payload_generator.rb: Unitsml::Opal::PayloadGenerator
  class that reads unitsdb-ruby YAML data on MRI and emits a Ruby
  source file defining Unitsml::Unitsdb::Database::DATABASE as a
  frozen hash. Invoked via rake unitsml:generate_opal_payload.

- lib/unitsml/opal/database_payload.rb: AUTO-GENERATED output committed
  to the gem. Loaded by the boot file under Opal.

- lib/unitsml/unitsdb/database.rb: adopts the loader mechanism from
  fix/opal-database-payload-loader (load_opal_payload, opal_payload,
  reset_opal_payload) so tests can inject small payloads and the
  committed DATABASE constant is found via const_defined?(:DATABASE,
  false). Supersedes PR #65.

- lib/unitsml/unitsdb.rb, lib/unitsml/model.rb and the four nested
  model parent files: convert require_relative and __dir__-interpolated
  autoloads to plain load-path-relative autoload declarations, per
  the global rule. Removes a dangling autoload (Unitsml::Model::
  Namespace) whose target file never existed; MRI never tripped on
  it because nothing referenced the constant, but Opal eager-loads
  every autoload and would have crashed.

- Rakefile: adds the unitsml:generate_opal_payload task.

- Gemfile: adds nokogiri and opal so the boot-file spec can compile
  the boot file end-to-end via Opal::Builder with native deps
  stubbed.

- .rubocop.yml: excludes the generated payload file from lint.

- spec/unitsml/opal_boot_spec.rb: verifies the boot file structure
  and that Opal::Builder compiles it end-to-end with stubs.

- spec/unitsml/payload_sync_spec.rb: verifies the committed payload
  matches what the generator produces from the current unitsdb gem
  and that it round-trips through from_hash.

- spec/unitsml/opal/payload_generator_spec.rb: verifies the
  generator produces evaluable Ruby that reproduces the source
  database.

- spec/unitsml/unitsdb/database_spec.rb: extends coverage to the
  new loader mechanism (load_opal_payload, DATABASE fallback).

- .github/workflows/opal.yml: GHA workflow running the opal specs
  on Ubuntu/Ruby 3.3/Node 18.
Hash#inspect changed between Ruby 3.3 and 3.4 to add spaces around
"=>". The committed payload file is generated on whichever Ruby the
maintainer runs, but the payload_sync_spec runs on every supported
Ruby in CI -- so a 3.3 maintainer committing the file would fail CI
on 3.4, and vice versa.

Replace Hash#inspect with a recursive serializer that produces
byte-identical output across Rubies. Strings still use inspect (which
is stable for UTF-8 clean data).
@ronaldtse ronaldtse force-pushed the feat/opal-payload-and-boot branch from 5849040 to 87652e3 Compare June 27, 2026 15:32
@ronaldtse ronaldtse merged commit 4eacf1f into main Jun 27, 2026
19 checks passed
@ronaldtse ronaldtse deleted the feat/opal-payload-and-boot branch June 27, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant