Skip to content

ADR-002: Rust-First Core with Thin Host Adapters

The hot path in Palamedes is dominated by work that benefits from a native implementation:

Status: Accepted Date: 2026-03-17

Context

  • parsing and traversing source files
  • extracting messages
  • transforming macro syntax into runtime calls
  • reading and writing catalogs
  • validating and normalizing ICU message structures

If that path is split across many TypeScript layers and utility packages, the project pays several costs:

  • semantic logic gets duplicated
  • boundaries become chatty
  • performance improvements are harder to realize end-to-end
  • it becomes unclear which layer actually owns i18n behavior

At the same time, Palamedes still lives in JavaScript application ecosystems. Some responsibilities are naturally host-specific:

  • config loading
  • file watching
  • bundler integration
  • framework hooks
  • CLI UX

That means Palamedes needs a strict ownership split rather than a vague "native helpers under a JavaScript toolchain" model.

Decision

Palamedes adopts a Rust-first architecture.

The architectural rule is:

  • Rust owns semantic i18n logic
  • TypeScript owns host integration

Rust is the primary home for:

  • macro transformation
  • message extraction
  • catalog reading and updating
  • ICU parsing and validation
  • compiled catalog preparation
  • other host-neutral i18n semantics

TypeScript is the primary home for:

  • project config discovery
  • filesystem traversal and watch orchestration
  • Vite and Next.js adapter wiring
  • CLI UX and reporting
  • packaging and host-facing API ergonomics

The TypeScript layer should call coarse native operations rather than rebuilding semantic pipelines locally.

Alternatives Considered

1. TypeScript-first architecture with native helpers

This would keep JavaScript as the main execution model and use Rust only for selected hotspots.

Rejected because it preserves duplicated semantics and encourages the exact split-brain architecture Palamedes is trying to avoid.

2. Move everything possible into Rust

This would also push config loading, bundler integration, and other host-specific concerns into the native layer.

Rejected because those concerns are defined by JavaScript ecosystem APIs and become awkward when forced into the core.

3. Keep separate semantic pipelines per package

This would let CLI, bundler adapters, and low-level packages each own more local semantics.

Rejected because it weakens the product model and makes correctness harder to maintain.

Consequences

  • Native APIs should be shaped around meaningful domain operations instead of small helper calls.
  • TypeScript packages should increasingly become orchestration layers over the same core semantics.
  • Performance work should prefer end-to-end coarse operations over isolated helper acceleration.
  • Architectural discussions should start by asking whether a concern is semantic and host-neutral or host-specific and orchestration-oriented.