feat: add main features
Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
186
crates/app-tauri/src/events.rs
Normal file
186
crates/app-tauri/src/events.rs
Normal file
@ -0,0 +1,186 @@
|
||||
//! `TauriEventRelay` — bridges the domain [`EventBus`] to Tauri events
|
||||
//! (backend → frontend push channel, ARCHITECTURE §2 "Events").
|
||||
//!
|
||||
//! The relay subscribes to the bus and re-emits each [`DomainEvent`] as a Tauri
|
||||
//! event named [`DOMAIN_EVENT`], carrying a serialisable [`DomainEventDto`]
|
||||
//! payload (the domain event itself is deliberately not `Serialize`; the wire
|
||||
//! format is owned here, the infrastructure/presentation layer).
|
||||
//!
|
||||
//! High-frequency `PtyOutput` is intentionally *not* relayed through this global
|
||||
//! event; it goes through per-session [`crate::pty::PtyBridge`] channels instead.
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
use domain::events::DomainEvent;
|
||||
use infrastructure::TokioBroadcastEventBus;
|
||||
|
||||
/// Name of the Tauri event carrying relayed [`DomainEvent`]s.
|
||||
pub const DOMAIN_EVENT: &str = "domain://event";
|
||||
|
||||
/// Serialisable mirror of [`DomainEvent`] for the IPC wire (camelCase, tagged).
|
||||
///
|
||||
/// `type` is the discriminant; payload fields are flattened per variant. This is
|
||||
/// the single owner of the event wire format on the backend side.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum DomainEventDto {
|
||||
/// A project was created.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ProjectCreated {
|
||||
/// Project id (UUID string).
|
||||
project_id: String,
|
||||
},
|
||||
/// An agent was launched.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentLaunched {
|
||||
/// Agent id.
|
||||
agent_id: String,
|
||||
/// Session id.
|
||||
session_id: String,
|
||||
},
|
||||
/// An agent exited.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentExited {
|
||||
/// Agent id.
|
||||
agent_id: String,
|
||||
/// Exit code.
|
||||
code: i32,
|
||||
},
|
||||
/// A template was updated.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
TemplateUpdated {
|
||||
/// Template id.
|
||||
template_id: String,
|
||||
/// New version.
|
||||
version: u64,
|
||||
},
|
||||
/// A synchronized agent drifted from its template.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentDriftDetected {
|
||||
/// Agent id.
|
||||
agent_id: String,
|
||||
/// Current version.
|
||||
from: u64,
|
||||
/// Available version.
|
||||
to: u64,
|
||||
},
|
||||
/// A synchronized agent was brought up to date.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentSynced {
|
||||
/// Agent id.
|
||||
agent_id: String,
|
||||
/// Version synced to.
|
||||
to: u64,
|
||||
},
|
||||
/// A tab's layout changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
LayoutChanged {
|
||||
/// Project id.
|
||||
project_id: String,
|
||||
},
|
||||
/// A remote connection was established.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
RemoteConnected {
|
||||
/// Project id.
|
||||
project_id: String,
|
||||
},
|
||||
/// Git state changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
GitStateChanged {
|
||||
/// Project id.
|
||||
project_id: String,
|
||||
},
|
||||
/// Raw PTY output (normally routed to a per-session channel, not here).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
PtyOutput {
|
||||
/// Session id.
|
||||
session_id: String,
|
||||
/// Output bytes.
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<&DomainEvent> for DomainEventDto {
|
||||
fn from(e: &DomainEvent) -> Self {
|
||||
match e {
|
||||
DomainEvent::ProjectCreated { project_id } => Self::ProjectCreated {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
DomainEvent::AgentLaunched {
|
||||
agent_id,
|
||||
session_id,
|
||||
} => Self::AgentLaunched {
|
||||
agent_id: agent_id.to_string(),
|
||||
session_id: session_id.to_string(),
|
||||
},
|
||||
DomainEvent::AgentExited { agent_id, code } => Self::AgentExited {
|
||||
agent_id: agent_id.to_string(),
|
||||
code: *code,
|
||||
},
|
||||
DomainEvent::TemplateUpdated {
|
||||
template_id,
|
||||
version,
|
||||
} => Self::TemplateUpdated {
|
||||
template_id: template_id.to_string(),
|
||||
version: version.get(),
|
||||
},
|
||||
DomainEvent::AgentDriftDetected {
|
||||
agent_id,
|
||||
from,
|
||||
to,
|
||||
} => Self::AgentDriftDetected {
|
||||
agent_id: agent_id.to_string(),
|
||||
from: from.get(),
|
||||
to: to.get(),
|
||||
},
|
||||
DomainEvent::AgentSynced { agent_id, to } => Self::AgentSynced {
|
||||
agent_id: agent_id.to_string(),
|
||||
to: to.get(),
|
||||
},
|
||||
DomainEvent::LayoutChanged { project_id } => Self::LayoutChanged {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
DomainEvent::RemoteConnected { project_id } => Self::RemoteConnected {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
DomainEvent::GitStateChanged { project_id } => Self::GitStateChanged {
|
||||
project_id: project_id.to_string(),
|
||||
},
|
||||
DomainEvent::PtyOutput { session_id, bytes } => Self::PtyOutput {
|
||||
session_id: session_id.to_string(),
|
||||
bytes: bytes.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes the relay to the bus and spawns a background task that forwards
|
||||
/// every [`DomainEvent`] to the frontend as a [`DOMAIN_EVENT`] Tauri event.
|
||||
///
|
||||
/// Uses the bus's raw async broadcast receiver so the relay runs cooperatively
|
||||
/// on the Tokio runtime (no blocking thread). Returns immediately; the spawned
|
||||
/// task lives for the duration of the app.
|
||||
pub fn spawn_relay(app: AppHandle, bus: &TokioBroadcastEventBus) {
|
||||
use tokio::sync::broadcast::error::RecvError;
|
||||
|
||||
let mut rx = bus.raw_receiver();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
// Skip high-frequency PTY output on the global channel.
|
||||
if matches!(event, DomainEvent::PtyOutput { .. }) {
|
||||
continue;
|
||||
}
|
||||
let dto = DomainEventDto::from(&event);
|
||||
let _ = app.emit(DOMAIN_EVENT, dto);
|
||||
}
|
||||
// The bus dropped some events for this slow receiver; keep going.
|
||||
Err(RecvError::Lagged(_)) => continue,
|
||||
// The bus was dropped (app shutting down); stop the relay.
|
||||
Err(RecvError::Closed) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user