Architecture
How Moji separates providers, aggregation, ranking, interfaces, and downloads.
Moji uses separate pipelines for remote discovery and local conversion. A font query passes through enabled providers, aggregation and cache, normalization and ranking, presentation, and validated download. Local conversion is a separate pipeline: a local font passes through content-based format detection, WOFF2 encoding or decoding, output validation, and an atomic file commit.
Conversion does not enter provider discovery, aggregation, ranking, cache, download, or TUI code.
Providers own external contracts
Each provider implements the same interface but owns its request format, response parsing, and source-specific errors. GitHub, GetFonts, DaFont, and SearXNG live in separate modules so changes to one external contract do not tangle the others.
Providers emit:
- result events containing a normalized
Resultrecord; - searching and done status;
- throttled status with an optional retry delay; and
- failed status with a typed cause.
Aggregation owns concurrency and resilience
The aggregator starts one worker per selected provider. It forwards events as they arrive, applies timeout and retry policies, respects provider retry delays, and isolates provider panics.
This boundary enables partial success. A failed provider does not invalidate a result already returned by another source.
Ranking owns font interpretation
The ranking package converts filename conventions into a family, weight, format, and style. It also owns weight filtering and family selection. The CLI and TUI use the same functions instead of implementing their own filename rules.
Interfaces own presentation
The app package chooses the interface:
- real stdin and stdout terminals receive the live Bubble Tea interface;
- redirected output receives a stable table;
--jsonreceives structured results; andgetperforms non-interactive selection and optional download; andconvertperforms non-interactive local font conversion without opening the TUI or loading provider configuration.
Provider failure descriptions and result identity are shared domain rules, so all interfaces present consistent outcomes.
Downloads own file safety
The download package is the only path that turns a remote result into a final file. It validates the transport, content, and destination before an atomic rename. Presentation layers only choose a result and report the outcome.
Conversion owns local font transformation
The font conversion package detects supported containers from their bytes, not their filenames. It wraps the WOFF2 codec and permits only container changes that preserve the font's TrueType or CFF outline technology.
Conversion and downloads share the same cross-platform no-replace commit primitive. Both expose a completed temporary file atomically and refuse to replace a path created by another process.