1#![forbid(unsafe_code)]
44#![warn(missing_docs)]
45
46#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
47pub mod api;
48pub mod approval_gate;
49pub mod audit_log;
50pub mod batch_status;
51pub mod builder;
52pub mod checkpoint;
53pub mod circuit_breaker;
54#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
55pub mod cli;
56pub mod cost_tracking;
57pub mod dag;
58pub mod error;
59pub mod event_bus;
60pub mod execution_trace;
61#[cfg(not(target_arch = "wasm32"))]
62pub mod executor;
63#[cfg(not(target_arch = "wasm32"))]
64pub mod fan_pattern;
65pub mod fanout;
66pub mod monitoring;
67pub mod notification_sink;
68pub mod notification_system;
69pub mod parallel_steps;
70pub mod patterns;
71pub mod pause_resume;
72#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
73pub mod persistence;
74#[cfg(not(target_arch = "wasm32"))]
75pub mod queue;
76pub mod resource_pool;
77pub mod retry_policy;
78#[cfg(not(target_arch = "wasm32"))]
79pub mod scheduler;
80pub mod sla;
81pub mod sla_tracking;
82pub mod state_machine;
83pub mod step_condition;
84pub mod step_conditions;
85pub mod step_result;
86pub mod task;
87pub mod task_dependency;
88pub mod task_graph;
89pub mod task_priority_queue;
90pub mod task_template;
91pub mod templates;
92pub mod triggers;
93pub mod utils;
94pub mod validation;
95pub mod versioning;
96pub mod webhook;
97pub mod webhook_trigger;
98#[cfg(not(target_arch = "wasm32"))]
99pub mod websocket;
100pub mod workflow;
101pub mod workflow_audit;
102pub mod workflow_bundle;
103pub mod workflow_checkpoint;
104pub mod workflow_compose;
105pub mod workflow_dashboard;
106pub mod workflow_diff;
107pub mod workflow_health_check;
108pub mod workflow_import_export;
109pub mod workflow_log;
110pub mod workflow_marketplace;
111pub mod workflow_metrics;
112pub mod workflow_migration;
113pub mod workflow_retry;
114pub mod workflow_simulation;
115pub mod workflow_snapshot;
116pub mod workflow_template;
117pub mod workflow_throttle;
118pub mod workflow_version;
119
120pub use batch_status::{BatchStatusWriter, FlushResult, StatusUpdate};
122pub use builder::{
123 QcTaskBuilder, TaskBuilder, TranscodeTaskBuilder, TransferTaskBuilder, WorkflowBuilder,
124};
125pub use circuit_breaker::{
126 CircuitBreaker, CircuitBreakerConfig, CircuitBreakerMetrics, CircuitBreakerRegistry,
127 CircuitBreakerSummary, CircuitDecision, CircuitState,
128};
129#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
130pub use cli::Cli;
131pub use dag::{
132 audio_normalize, ingest_transcode, subtitle_burn, BranchEvaluator, BranchNode, BranchType,
133 DagError, DagRunStatus, DagWorkflowEngine, NodeId, NodeStatus, WorkflowDag, WorkflowEdge,
134 WorkflowNode, WorkflowTemplate,
135};
136pub use error::{Result, WorkflowError};
137pub use event_bus::{
138 BusEvent, EventBus, EventBusConfig, EventBusStats, EventFilter, SubscriptionId,
139};
140#[cfg(not(target_arch = "wasm32"))]
141pub use executor::{
142 parse_condition, DefaultTaskExecutor, ExecutionContext, TaskExecutor, WorkflowControl,
143 WorkflowExecutor,
144};
145pub use monitoring::{MonitoringService, SystemStatistics, TaskMetrics, WorkflowMetrics};
146pub use parallel_steps::{
147 execute_step, ParallelStepError, ParallelSteps, StepResult, StepType, WorkflowStage,
148 WorkflowStep,
149};
150pub use patterns::*;
151#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
152pub use persistence::PersistenceManager;
153#[cfg(not(target_arch = "wasm32"))]
154pub use queue::{QueueStatistics, TaskQueue};
155pub use retry_policy::{ExponentialRetryPolicy, RetryDecision, RetryPolicyState};
156#[cfg(not(target_arch = "wasm32"))]
157pub use scheduler::{
158 Clock, FileWatcher, ScheduledWorkflow, SystemClock, Trigger, WorkflowScheduler,
159};
160pub use task::{
161 AnalysisType, HttpMethod, NotificationChannel, RetryPolicy, Task, TaskId, TaskPriority,
162 TaskResult, TaskState, TaskType, TransferProtocol,
163};
164pub use task_priority_queue::{PriorityEntry, PriorityLevel, TaskPriorityQueue};
165pub use utils::{
166 calculate_parallelism, clone_workflow, estimate_workflow_duration, expand_env_vars,
167 expand_template, find_critical_path, format_duration, generate_task_name,
168 get_workflow_statistics, merge_configs, normalize_paths, parse_duration, sanitize_task_name,
169 WorkflowStatistics,
170};
171pub use validation::{
172 ComplexityAnalyzer, ComplexityLevel, ComplexityMetrics, TaskValidator, ValidationReport,
173 ValidationRule, WorkflowValidator,
174};
175pub use webhook::{WebhookConfig, WebhookEvent, WebhookNotifier, WorkflowContext};
176#[cfg(not(target_arch = "wasm32"))]
177pub use websocket::{WebSocketManager, WebSocketState, WorkflowEvent};
178pub use workflow::{Edge, Workflow, WorkflowConfig, WorkflowId, WorkflowState};
179pub use workflow_import_export::{
180 deserialize_bundle, export_workflow, import_workflow, serialize_bundle, BundleFormat,
181 DagBundle, WorkflowBundle, WorkflowDefinition,
182};
183pub use workflow_migration::{
184 FieldChange, MigrationError, MigrationRegistry, MigrationStep, SchemaVersion,
185};
186pub use workflow_simulation::{
187 quick_simulate, ConditionSource, EvaluatedCondition, SimulatedOutcome, SimulationConfig,
188 SimulationResult, SimulationSummary, WorkflowSimulator,
189};
190
191#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
193pub struct WorkflowEngine {
194 persistence: std::sync::Arc<PersistenceManager>,
195 scheduler: std::sync::Arc<WorkflowScheduler>,
196 monitoring: std::sync::Arc<MonitoringService>,
197 executor: std::sync::Arc<dyn TaskExecutor>,
198}
199
200#[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
201impl WorkflowEngine {
202 pub fn new(db_path: impl AsRef<std::path::Path>) -> Result<Self> {
208 let persistence = std::sync::Arc::new(PersistenceManager::new(db_path)?);
209 let scheduler = std::sync::Arc::new(WorkflowScheduler::new());
210 let monitoring = std::sync::Arc::new(MonitoringService::new());
211 let executor = std::sync::Arc::new(DefaultTaskExecutor);
212
213 Ok(Self {
214 persistence,
215 scheduler,
216 monitoring,
217 executor,
218 })
219 }
220
221 pub fn in_memory() -> Result<Self> {
227 let persistence = std::sync::Arc::new(PersistenceManager::in_memory()?);
228 let scheduler = std::sync::Arc::new(WorkflowScheduler::new());
229 let monitoring = std::sync::Arc::new(MonitoringService::new());
230 let executor = std::sync::Arc::new(DefaultTaskExecutor);
231
232 Ok(Self {
233 persistence,
234 scheduler,
235 monitoring,
236 executor,
237 })
238 }
239
240 #[must_use]
242 pub fn persistence(&self) -> &std::sync::Arc<PersistenceManager> {
243 &self.persistence
244 }
245
246 #[must_use]
248 pub fn scheduler(&self) -> &std::sync::Arc<WorkflowScheduler> {
249 &self.scheduler
250 }
251
252 #[must_use]
254 pub fn monitoring(&self) -> &std::sync::Arc<MonitoringService> {
255 &self.monitoring
256 }
257
258 #[must_use]
260 pub fn executor(&self) -> &std::sync::Arc<dyn TaskExecutor> {
261 &self.executor
262 }
263
264 pub async fn submit_workflow(&self, workflow: &Workflow) -> Result<WorkflowId> {
270 tokio::task::block_in_place(|| self.persistence.save_workflow(workflow))?;
276 Ok(workflow.id)
277 }
278
279 pub async fn execute_workflow(&self, workflow_id: WorkflowId) -> Result<()> {
285 let mut workflow =
289 tokio::task::block_in_place(|| self.persistence.load_workflow(workflow_id))?;
290
291 let executor = WorkflowExecutor::new(self.executor.clone());
292
293 self.monitoring
294 .start_workflow(workflow_id, workflow.name.clone(), workflow.tasks.len());
295
296 let result = executor.execute(&mut workflow).await?;
297
298 self.monitoring
299 .complete_workflow(workflow_id, result.state == WorkflowState::Completed);
300
301 tokio::task::block_in_place(|| self.persistence.save_workflow(&workflow))?;
302
303 Ok(())
304 }
305
306 pub async fn schedule_workflow(
312 &self,
313 workflow: Workflow,
314 trigger: Trigger,
315 ) -> Result<WorkflowId> {
316 let workflow_id = workflow.id;
317 tokio::task::block_in_place(|| self.persistence.save_workflow(&workflow))?;
318 self.scheduler.add_schedule(workflow, trigger).await?;
319 Ok(workflow_id)
320 }
321
322 pub async fn start(&self) -> Result<()> {
328 self.scheduler.start().await?;
329 tracing::info!("Workflow engine started");
330 Ok(())
331 }
332
333 pub async fn stop(&self) -> Result<()> {
339 self.scheduler.stop().await?;
340 tracing::info!("Workflow engine stopped");
341 Ok(())
342 }
343
344 pub async fn process_schedules(&self) -> Result<Vec<WorkflowId>> {
346 let ready_workflows = self.scheduler.check_schedules().await;
347 let mut executed = Vec::new();
348
349 for workflow in ready_workflows {
350 let workflow_id = workflow.id;
351 tokio::task::block_in_place(|| self.persistence.save_workflow(&workflow))?;
352
353 let engine = Self {
355 persistence: self.persistence.clone(),
356 scheduler: self.scheduler.clone(),
357 monitoring: self.monitoring.clone(),
358 executor: self.executor.clone(),
359 };
360
361 tokio::spawn(async move {
362 if let Err(e) = engine.execute_workflow(workflow_id).await {
363 tracing::error!("Scheduled workflow execution failed: {}", e);
364 }
365 });
366
367 executed.push(workflow_id);
368 }
369
370 Ok(executed)
371 }
372
373 #[must_use]
375 pub fn api_state(&self) -> api::ApiState {
376 api::ApiState {
377 persistence: self.persistence.clone(),
378 scheduler: self.scheduler.clone(),
379 monitoring: self.monitoring.clone(),
380 executor: self.executor.clone(),
381 active_workflows: std::sync::Arc::new(tokio::sync::RwLock::new(
382 std::collections::HashMap::new(),
383 )),
384 }
385 }
386
387 pub fn api_router(&self) -> axum::Router {
389 api::create_router(self.api_state())
390 }
391}
392
393#[cfg(all(test, feature = "sqlite"))]
394mod tests {
395 use super::*;
396 use std::time::Duration;
397
398 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
399 async fn test_workflow_engine_creation() {
400 let engine = tokio::task::block_in_place(WorkflowEngine::in_memory);
405 assert!(engine.is_ok());
406 }
407
408 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
409 async fn test_submit_workflow() {
410 let engine =
411 tokio::task::block_in_place(WorkflowEngine::in_memory).expect("should succeed in test");
412 let workflow = Workflow::new("test-workflow");
413
414 let workflow_id = engine
415 .submit_workflow(&workflow)
416 .await
417 .expect("should succeed in test");
418 assert_eq!(workflow_id, workflow.id);
419 }
420
421 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
422 async fn test_execute_workflow() {
423 let engine =
424 tokio::task::block_in_place(WorkflowEngine::in_memory).expect("should succeed in test");
425 let mut workflow = Workflow::new("test-workflow");
426
427 let task = Task::new(
428 "wait-task",
429 TaskType::Wait {
430 duration: Duration::from_millis(10),
431 },
432 );
433 workflow.add_task(task);
434
435 let workflow_id = engine
436 .submit_workflow(&workflow)
437 .await
438 .expect("should succeed in test");
439 let result = engine.execute_workflow(workflow_id).await;
440 assert!(result.is_ok());
441 }
442
443 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
444 async fn test_schedule_workflow() {
445 let engine =
446 tokio::task::block_in_place(WorkflowEngine::in_memory).expect("should succeed in test");
447 let workflow = Workflow::new("test-workflow");
448 let trigger = Trigger::Manual;
449
450 let workflow_id = engine
451 .schedule_workflow(workflow, trigger)
452 .await
453 .expect("should succeed in test");
454
455 let schedules = engine.scheduler.list_schedules().await;
456 assert_eq!(schedules.len(), 1);
457 assert_eq!(schedules[0].0, workflow_id);
458 }
459
460 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
461 async fn test_engine_start_stop() {
462 let engine =
463 tokio::task::block_in_place(WorkflowEngine::in_memory).expect("should succeed in test");
464
465 engine.start().await.expect("should succeed in test");
466 engine.stop().await.expect("should succeed in test");
467 }
468}