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 Revised: 2026-08-11

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 specific to those hosts:

  • JavaScript-host config loading
  • bundler integration
  • framework hooks
  • packaging and host-facing API ergonomics

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

Rust also owns the native CLI's config discovery and validation, source-file traversal and watch orchestration, command-line UX, and reporting. TypeScript is the primary home for:

  • Vite and Next.js adapter wiring
  • JavaScript-host configuration integration
  • 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 JavaScript-host config loading, bundler integration, framework hooks, and packaging concerns into the native layer.

Rejected because those concerns are defined by JavaScript ecosystem APIs and become awkward when forced into the core. Native CLI config, file watching, and command-line UX remain Rust responsibilities because they do not depend on those host APIs.

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.