Architecture overview
The layers, which crate sits where, how modules plug into the platform, and how the Tauri host connects the application to the desktop.
Origin splits a desktop application into layers that only depend downwards. Tauri is the bottom layer, the host, and nothing above it knows it exists. The binding rules are in ARCHITECTURE.md; Architecture in the code explains the reasoning.
Layers
PRODUCT APP examples/demo, later other independent products
↓ composition root
DRIVING ADAPTERS Tauri host · MCP server · CLI · headless — all equal peers
APPLICATION MODULES ApplicationModule implementations
↓
CONNECTORS external service integrations
↓
ORIGIN PLATFORM events, secrets, settings, storage, http, auth, accounts,
connector, sync, jobs, mcp-core, ai, telemetry, app
TOOLING manifest, xtask — build time, not runtime
↓
PLATFORM CONTRACTS origin-platform, origin-domain (ports)
↓
TAURI HOST host/origin-tauri, adapters/origin-*-tauri
Where each crate sits
| Folder | Crates | Role |
|---|---|---|
crates/ |
origin-domain, origin-platform |
error model, domain primitives and the OS contracts (ports) |
crates/ |
origin-events, origin-secrets, origin-settings, origin-storage, origin-http, origin-auth, origin-accounts, origin-connector, origin-sync, origin-jobs, origin-mcp-core, origin-ai, origin-telemetry |
platform mechanisms, each behind a trait |
crates/ |
origin-app |
ApplicationBuilder, modules, service registry |
crates/ |
origin-manifest, origin-xtask |
app.toml, code generation, architecture checks |
adapters/ |
origin-storage-sqlite, origin-secrets-system, origin-http-reqwest, origin-auth-loopback, origin-mcp-stdio, origin-mcp-http, origin-process-std, origin-workspace-fs, origin-workspace-watch, origin-notifications-tauri |
concrete implementations of the contracts |
host/ |
origin-tauri |
plugin wiring, tray, IPC commands, event bridge |
frontend/ |
@casoon/origin-client, @casoon/origin-ui |
the typed IPC client and shared Svelte 5 components |
The crate reference links each one to crates.io and docs.rs.
cargo xtask validate checks the dependency direction mechanically:
| Layer | May depend on | Must never depend on |
|---|---|---|
crates/* |
other crates/*, third-party libs |
tauri*, adapters/*, host/*, examples/* |
adapters/* |
crates/*, platform libs |
other adapters/*, examples/* |
host/* |
crates/*, adapters/*, tauri* |
examples/* |
examples/* |
everything | — |
Modules and the platform
A module is a feature area compiled into the product. It implements one trait from
origin-app:
pub trait ApplicationModule: fmt::Debug + Send + Sync + 'static {
fn id(&self) -> &'static str;
fn register(&self, registry: &mut ModuleRegistry) -> Result<()>;
}
In register, a module reads the Platform (registry.platform()), provides its
services (registry.provide(Arc::new(...))) and subscribes to events. Other code
resolves those services by type with require::<T>(). There is no dynamic plugin
loading.
Platform is what every module may rely on. Some services are always present; the
others exist only when the composition root wired them, so a missing capability is
absent rather than switched off:
| Always present | Only when the product wires it |
|---|---|
clock, events, storage, cache, secrets, settings, notifications, confirmation, jobs, sync, accounts, connectors |
tray, opener, http, workspace_fs, workspace_watcher, process_runner, global_shortcuts |
Optional services are read through methods such as platform.http() or
platform.opener(), which return a configuration error naming what is missing instead
of panicking.
The Tauri integration
origin-tauri is the only crate that turns an Application into a desktop program. A
product’s main.rs uses three entry points:
fn main() {
let config = HostConfig::new("dev.origin.demo");
origin_tauri::builder(&config)
.invoke_handler(origin_handler![my_product_command])
.setup(move |app| {
let application = my_product::build(app.handle())?;
origin_tauri::attach(app.handle(), application, &config)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("failed to start");
}
builder(&config) returns a tauri::Builder with the Origin plugin set:
single-instance first (a second launch focuses the main window), then notifications and
the opener, then window state. Single-instance and window state follow HostConfig;
both are on by default.
attach(app, application, &config) hands the assembled application to the shell.
It:
- wraps the application in
OriginStateand registers it as Tauri-managed state, - starts the sync engine on Tauri’s async runtime, stoppable through
OriginState::shutdown, - forwards every
PlatformEventfrom the in-process bus to the webview onorigin://platform-event— one way only; the frontend observes, and anything it wants to cause goes through a command, - installs the tray (Show window, Quit) when the product enabled it.
origin_handler![...] builds the single invoke handler Tauri allows: Origin’s own
commands plus the product’s. Calling tauri::generate_handler! directly would drop the
platform commands. The platform commands are:
| Area | Commands |
|---|---|
| App | origin_app_info, origin_health |
| Settings | origin_setting_get, origin_setting_set, origin_settings_customised |
| Opener | origin_open_url |
| Accounts and connectors | origin_accounts, origin_account_disconnect, origin_connectors |
| Jobs | origin_jobs, origin_job_cancel |
| Sync | origin_sync_status, origin_sync_now |
Commands resolve state, call the domain and translate errors. A failure reaches the
frontend as CommandError, a transparent wrapper around the stable ErrorContract,
never as a raw rusqlite, reqwest or tauri error.
Default wiring
origin_tauri::defaults holds the standard adapter constructions a composition root
calls instead of repeating them. Each one can be replaced individually:
| Function | Provides |
|---|---|
storage |
SQLite in the platform data directory for the app id (origin.sqlite3), resolved through origin_platform::paths so a headless run reaches the same file |
secret_store |
the OS credential store, scoped to the app id |
notifications |
native notifications through tauri-plugin-notification |
http_client |
one reqwest client with a name/version (app id) user agent |
opener |
TauriOpener, which accepts only http:// and https:// URLs |
The frontend side
@casoon/origin-client is the only package that imports @tauri-apps/api. Views call
typed functions (appInfo(), settings.get(), …) and subscribe with
onPlatformEvent; cargo xtask validate fails on a direct Tauri import anywhere else.
The types crossing the boundary are generated from their Rust definitions, see
The manifest.
Driving the core without Tauri
The host is one driving adapter among several. The same Application answers an MCP
client over stdio (origin-mcp-stdio) or authenticated loopback HTTP
(origin-mcp-http), and the whole application is testable with
ApplicationBuilder::in_memory() and no desktop session — see
AI integration and Testing.