Skip to content
Rymflux Documentation
Esc
navigateopen⌘Jpreview
On this page

ADR-014: Typed IPC bridge — `ipc/` wrappers + shared TypeScript contracts

ADR-014: Typed IPC bridge — ipc/ wrappers + shared TypeScript contracts

Status

Accepted

Context

All frontend data enters through the IPC bridge (frontend-architecture.md §1.1: no +server.js, no SSR, no build-time data). The surface is two halves — Tauri commands (frontend → Rust, api-specs/tauri-commands.md) and Tauri events (Rust → frontend, api-specs/tauri-events.md) — plus a one-time bootstrap that wires events into the state layer.

The backend speaks snake_case Rust (serde); the frontend speaks TypeScript. Payload shapes are canonical in data-models.md (TypeScript-first spec) and the event/command API specs. Without a deliberate typing discipline, invoke calls and listen handlers degrade into untyped any plumbing, and the TS mirror drifts from the canonical spec.

ADR-005 chose Tauri events as the inter-component channel; ADR-007’s “shared TypeScript types” mitigation names the goal but not the mechanism.

Decision

The IPC bridge is the only code that touches @tauri-apps/api, in three files under src/lib/ipc/:

  • commands.ts — one exported object per command group, matching tauri-commands.md exactly. Wrapper parameters are camelCase; Tauri v2 maps top-level camelCase argument keys to snake_case Rust parameters automatically, while nested object fields pass through for strict serde deserialization.
  • events.ts — one typed subscription function per event in tauri-events.md; each returns the listen unlisten function for teardown.
  • bootstrap.ts — a one-time boot sequence subscribing events and feeding them into the state layer (*.svelte.ts modules, per ADR-013).

src/lib/contracts/types.ts is the executable mirror of data-models.md plus the event/command payload types (PlaybackState, QueueState, SearchResultBatch, PluginStatus, PluginInfo). Contract types mirror the canonical snake_case field names verbatim; the code review step in CI diffs types.ts against data-models.md and tauri-events.md to catch drift.

Alternatives considered

  • Untyped invoke/listen (string names, any payloads). Zero ceremony, but every call site re-derives shapes, typos in command/event names surface at runtime, and the frontend has no compile-time view of the contract. Rejected — the contract surface is exactly where type safety pays off.
  • Runtime validation (zod) of every payload. Adds a validation layer and a dependency; the payloads already cross a serde boundary on the Rust side (strict deserialization). v1 uses compile-time typing + CI diff review instead.
  • Auto-generation from Rust (ts-rs / OpenAPI-style schema). The single-source-of-truth ideal. Deferred to v2: requires restructuring the Rust type definitions and a codegen step in the build; frontend-architecture §16.4 records this and says an ADR would accompany that change.

Consequences

Positive:

  • The frontend↔Rust contract is typed end-to-end; command/event payload shapes are checked at compile time and caught at review if the canonical spec changes.
  • One import surface (@rymflux/ipc) — components and state modules never touch @tauri-apps/api directly, keeping the vendored dependency confined.
  • The wrapper layer is the single place command argument mapping (camelCase → snake_case) happens; nested payload fields pass through for strict serde.
  • A clean migration path to codegen in v2: the mirror types.ts is replaced by generated output, and commands.ts/events.ts signatures are unchanged.

Negative:

  • Hand-synced mirror can drift despite the CI diff review; the review is human-dependent until v2 tooling exists.
  • Wrapper + contract boilerplate per command/event group is repetitive to author (one commands.ts object and one events.ts function per spec entry).
  • camelCase↔snake_case mapping is implicit (Tauri v2 convention); a nested field that must be renamed is easy to get wrong without the spec open.

References

  • frontend-architecture.md §4 (typed IPC bridge), §4.1–4.4, §16.4 (type parity tooling, v2)
  • api-specs/tauri-commands.md, api-specs/tauri-events.md, api-specs/data-models.md
  • ADR-005 (inter-component communication — Tauri events)
  • ADR-007 (search manager — shared TypeScript types mitigation)
  • ADR-013 (state layer — the consumer of bootstrap.ts)

Was this page helpful?