pub mod postgres;
pub mod sqlite;
pub use self::postgres::PostgresPersistence;
pub use self::sqlite::SqlitePersistence;
use crate::error::CoreError;
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct InstanceRecord {
pub instance_id: String,
pub tenant_id: String,
pub definition_version: i32,
pub status: String,
pub checkpoint_id: Option<String>,
pub attempt: i32,
pub max_attempts: i32,
pub created_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub finished_at: Option<DateTime<Utc>>,
#[sqlx(default)]
pub input: Option<Vec<u8>>,
pub output: Option<Vec<u8>>,
pub error: Option<String>,
pub sleep_until: Option<DateTime<Utc>>,
#[sqlx(default)]
pub termination_reason: Option<String>,
#[sqlx(default)]
pub exit_code: Option<i32>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct CheckpointRecord {
pub id: i64,
pub instance_id: String,
pub checkpoint_id: String,
pub state: Vec<u8>,
pub created_at: DateTime<Utc>,
#[sqlx(default)]
pub is_compensatable: bool,
#[sqlx(default)]
pub compensation_step_id: Option<String>,
#[sqlx(default)]
pub compensation_data: Option<Vec<u8>>,
#[sqlx(default)]
pub compensation_state: Option<String>,
#[sqlx(default)]
pub compensation_order: i32,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct EventRecord {
#[sqlx(default)]
pub id: Option<i64>,
pub instance_id: String,
pub event_type: String,
pub checkpoint_id: Option<String>,
pub payload: Option<Vec<u8>>,
pub created_at: DateTime<Utc>,
pub subtype: Option<String>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct SignalRecord {
pub instance_id: String,
pub signal_type: String,
pub payload: Option<Vec<u8>>,
pub created_at: DateTime<Utc>,
pub acknowledged_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct CustomSignalRecord {
pub instance_id: String,
pub checkpoint_id: String,
pub payload: Option<Vec<u8>>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EventSortOrder {
#[default]
Desc,
Asc,
}
#[derive(Debug, Clone, Default)]
pub struct ListEventsFilter {
pub event_type: Option<String>,
pub subtype: Option<String>,
pub created_after: Option<DateTime<Utc>>,
pub created_before: Option<DateTime<Utc>>,
pub payload_contains: Option<String>,
pub scope_id: Option<String>,
pub parent_scope_id: Option<String>,
pub root_scopes_only: bool,
pub sort_order: EventSortOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepStatus {
Running,
Completed,
Failed,
}
#[derive(Debug, Clone)]
pub struct StepSummaryRecord {
pub step_id: String,
pub step_name: Option<String>,
pub step_type: String,
pub status: StepStatus,
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub duration_ms: Option<i64>,
pub inputs: Option<serde_json::Value>,
pub outputs: Option<serde_json::Value>,
pub error: Option<serde_json::Value>,
pub scope_id: Option<String>,
pub parent_scope_id: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ListStepSummariesFilter {
pub sort_order: EventSortOrder,
pub status: Option<StepStatus>,
pub step_type: Option<String>,
pub scope_id: Option<String>,
pub parent_scope_id: Option<String>,
pub root_scopes_only: bool,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ErrorHistoryRecord {
pub id: i64,
pub instance_id: String,
pub checkpoint_id: Option<String>,
pub step_id: Option<String>,
pub error_code: String,
pub error_message: String,
pub category: String,
pub severity: String,
pub retry_hint: Option<String>,
pub retry_after_ms: Option<i64>,
pub attributes: Option<serde_json::Value>,
pub cause_error_id: Option<i64>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct CompensationLogRecord {
pub id: i64,
pub instance_id: String,
pub checkpoint_id: String,
pub compensation_step_id: String,
pub attempt_number: i32,
pub started_at: DateTime<Utc>,
pub finished_at: Option<DateTime<Utc>>,
pub success: Option<bool>,
pub error_message: Option<String>,
pub error_id: Option<i64>,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct WakeEntry {
pub id: i64,
pub instance_id: String,
pub checkpoint_id: String,
pub wake_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
use async_trait::async_trait;
use chrono::{DateTime, Utc};
#[allow(missing_docs)]
#[async_trait]
pub trait Persistence: Send + Sync {
async fn register_instance(&self, instance_id: &str, tenant_id: &str) -> Result<(), CoreError>;
async fn get_instance(&self, instance_id: &str) -> Result<Option<InstanceRecord>, CoreError>;
async fn update_instance_status(
&self,
instance_id: &str,
status: &str,
started_at: Option<DateTime<Utc>>,
) -> Result<(), CoreError>;
async fn update_instance_checkpoint(
&self,
instance_id: &str,
checkpoint_id: &str,
) -> Result<(), CoreError>;
async fn complete_instance(
&self,
instance_id: &str,
output: Option<&[u8]>,
error: Option<&str>,
) -> Result<(), CoreError>;
async fn complete_instance_extended(
&self,
instance_id: &str,
status: &str,
output: Option<&[u8]>,
error: Option<&str>,
_stderr: Option<&str>,
_checkpoint_id: Option<&str>,
) -> Result<(), CoreError> {
let error_for_basic = if status == "failed" { error } else { None };
let output_for_basic = if status == "completed" { output } else { None };
self.complete_instance(instance_id, output_for_basic, error_for_basic)
.await
}
async fn complete_instance_if_running(
&self,
instance_id: &str,
status: &str,
output: Option<&[u8]>,
error: Option<&str>,
stderr: Option<&str>,
checkpoint_id: Option<&str>,
) -> Result<bool, CoreError> {
self.complete_instance_extended(instance_id, status, output, error, stderr, checkpoint_id)
.await?;
Ok(true)
}
#[allow(clippy::too_many_arguments)]
async fn complete_instance_with_termination(
&self,
instance_id: &str,
status: &str,
termination_reason: Option<&str>,
exit_code: Option<i32>,
output: Option<&[u8]>,
error: Option<&str>,
stderr: Option<&str>,
checkpoint_id: Option<&str>,
) -> Result<(), CoreError> {
let _ = termination_reason;
let _ = exit_code;
self.complete_instance_extended(instance_id, status, output, error, stderr, checkpoint_id)
.await
}
#[allow(clippy::too_many_arguments)]
async fn complete_instance_with_termination_if_running(
&self,
instance_id: &str,
status: &str,
termination_reason: Option<&str>,
exit_code: Option<i32>,
output: Option<&[u8]>,
error: Option<&str>,
stderr: Option<&str>,
checkpoint_id: Option<&str>,
) -> Result<bool, CoreError> {
self.complete_instance_with_termination(
instance_id,
status,
termination_reason,
exit_code,
output,
error,
stderr,
checkpoint_id,
)
.await?;
Ok(true)
}
async fn update_instance_metrics(
&self,
_instance_id: &str,
_memory_peak_bytes: Option<u64>,
_cpu_usage_usec: Option<u64>,
) -> Result<(), CoreError> {
Ok(())
}
async fn update_instance_stderr(
&self,
_instance_id: &str,
_stderr: &str,
) -> Result<(), CoreError> {
Ok(())
}
async fn store_instance_input(
&self,
_instance_id: &str,
_input: &[u8],
) -> Result<(), CoreError> {
Ok(())
}
async fn save_checkpoint(
&self,
instance_id: &str,
checkpoint_id: &str,
state: &[u8],
) -> Result<(), CoreError>;
async fn load_checkpoint(
&self,
instance_id: &str,
checkpoint_id: &str,
) -> Result<Option<CheckpointRecord>, CoreError>;
async fn list_checkpoints(
&self,
instance_id: &str,
checkpoint_id: Option<&str>,
limit: i64,
offset: i64,
created_after: Option<DateTime<Utc>>,
created_before: Option<DateTime<Utc>>,
) -> Result<Vec<CheckpointRecord>, CoreError>;
async fn count_checkpoints(
&self,
instance_id: &str,
checkpoint_id: Option<&str>,
created_after: Option<DateTime<Utc>>,
created_before: Option<DateTime<Utc>>,
) -> Result<i64, CoreError>;
async fn insert_event(&self, event: &EventRecord) -> Result<(), CoreError>;
async fn insert_signal(
&self,
instance_id: &str,
signal_type: &str,
payload: &[u8],
) -> Result<(), CoreError>;
async fn get_pending_signal(
&self,
instance_id: &str,
) -> Result<Option<SignalRecord>, CoreError>;
async fn acknowledge_signal(&self, instance_id: &str) -> Result<(), CoreError>;
async fn insert_custom_signal(
&self,
instance_id: &str,
checkpoint_id: &str,
payload: &[u8],
) -> Result<(), CoreError>;
async fn take_pending_custom_signal(
&self,
instance_id: &str,
checkpoint_id: &str,
) -> Result<Option<CustomSignalRecord>, CoreError>;
async fn save_retry_attempt(
&self,
instance_id: &str,
checkpoint_id: &str,
attempt: i32,
error_message: Option<&str>,
) -> Result<(), CoreError>;
async fn list_instances(
&self,
tenant_id: Option<&str>,
status: Option<&str>,
limit: i64,
offset: i64,
) -> Result<Vec<InstanceRecord>, CoreError>;
async fn health_check_db(&self) -> Result<bool, CoreError>;
async fn count_active_instances(&self) -> Result<i64, CoreError>;
async fn set_instance_sleep(
&self,
instance_id: &str,
sleep_until: DateTime<Utc>,
) -> Result<(), CoreError>;
async fn clear_instance_sleep(&self, instance_id: &str) -> Result<(), CoreError>;
async fn get_sleeping_instances_due(
&self,
limit: i64,
) -> Result<Vec<InstanceRecord>, CoreError>;
async fn list_events(
&self,
instance_id: &str,
filter: &ListEventsFilter,
limit: i64,
offset: i64,
) -> Result<Vec<EventRecord>, CoreError>;
async fn count_events(
&self,
instance_id: &str,
filter: &ListEventsFilter,
) -> Result<i64, CoreError>;
async fn list_step_summaries(
&self,
instance_id: &str,
filter: &ListStepSummariesFilter,
limit: i64,
offset: i64,
) -> Result<Vec<StepSummaryRecord>, CoreError>;
async fn count_step_summaries(
&self,
instance_id: &str,
filter: &ListStepSummariesFilter,
) -> Result<i64, CoreError>;
#[allow(clippy::too_many_arguments)]
async fn record_error(
&self,
_instance_id: &str,
_checkpoint_id: Option<&str>,
_step_id: Option<&str>,
_error_code: &str,
_error_message: &str,
_category: &str,
_severity: &str,
_retry_hint: Option<&str>,
_retry_after_ms: Option<i64>,
_attributes: Option<&serde_json::Value>,
_cause_error_id: Option<i64>,
) -> Result<i64, CoreError> {
Ok(0)
}
async fn get_last_error(
&self,
_instance_id: &str,
) -> Result<Option<ErrorHistoryRecord>, CoreError> {
Ok(None)
}
async fn list_errors(
&self,
_instance_id: &str,
_limit: i64,
_offset: i64,
) -> Result<Vec<ErrorHistoryRecord>, CoreError> {
Ok(vec![])
}
async fn register_compensatable_checkpoint(
&self,
_instance_id: &str,
_checkpoint_id: &str,
_compensation_step_id: &str,
_compensation_data: Option<&[u8]>,
_compensation_order: i32,
) -> Result<(), CoreError> {
Ok(())
}
async fn get_compensatable_checkpoints(
&self,
_instance_id: &str,
) -> Result<Vec<CheckpointRecord>, CoreError> {
Ok(vec![])
}
async fn set_checkpoint_compensation_state(
&self,
_instance_id: &str,
_checkpoint_id: &str,
_state: &str,
) -> Result<(), CoreError> {
Ok(())
}
async fn set_instance_compensation_state(
&self,
_instance_id: &str,
_state: &str,
_reason: Option<&str>,
) -> Result<(), CoreError> {
Ok(())
}
async fn log_compensation_attempt(
&self,
_instance_id: &str,
_checkpoint_id: &str,
_compensation_step_id: &str,
_success: bool,
_error_message: Option<&str>,
_error_id: Option<i64>,
) -> Result<(), CoreError> {
Ok(())
}
async fn count_pending_compensations(&self, _instance_id: &str) -> Result<i64, CoreError> {
Ok(0)
}
async fn all_compensations_succeeded(&self, _instance_id: &str) -> Result<bool, CoreError> {
Ok(true)
}
async fn get_terminal_instances_older_than(
&self,
_older_than: DateTime<Utc>,
_limit: i64,
) -> Result<Vec<String>, CoreError> {
Ok(vec![])
}
async fn delete_instances_batch(&self, _instance_ids: &[String]) -> Result<u64, CoreError> {
Ok(0)
}
}