Skip to main content

pe_tasks/
registry.rs

1//! # TaskRegistry trait — async CRUD + queries + dependencies.
2//!
3//! The registry is the storage backend for tasks. The library provides
4//! [`InMemoryTaskRegistry`](crate::in_memory::InMemoryTaskRegistry);
5//! users implement this trait for persistent backends (SurrealDB, Postgres, etc.).
6
7use pe_core::PeError;
8
9use crate::dependency::TaskDependency;
10use crate::task::{Task, TaskId, TaskPriority, TaskStatus, TaskType};
11
12/// Async task storage with CRUD, queries, and dependency management.
13///
14/// All operations are async and return `Result<T, PeError>`.
15/// Implementations must be `Send + Sync` for use across async tasks.
16#[async_trait::async_trait]
17pub trait TaskRegistry: Send + Sync {
18    // === CRUD ===
19
20    /// Create a new task. Returns the created task with generated ID.
21    async fn create(&self, task: Task) -> Result<Task, PeError>;
22
23    /// Get a task by ID. Returns None if not found or deleted.
24    async fn get(&self, id: &TaskId) -> Result<Option<Task>, PeError>;
25
26    /// Update an existing task. Returns the updated task.
27    async fn update(&self, task: &Task) -> Result<Task, PeError>;
28
29    /// Soft-delete a task (sets deleted_at). Returns true if found.
30    async fn delete(&self, id: &TaskId) -> Result<bool, PeError>;
31
32    /// Restore a soft-deleted task. Returns true if found in trash.
33    async fn restore(&self, id: &TaskId) -> Result<bool, PeError>;
34
35    // === Status ===
36
37    /// Update task status (with lifecycle validation).
38    async fn update_status(
39        &self,
40        id: &TaskId,
41        status: TaskStatus,
42        result: Option<serde_json::Value>,
43        error: Option<String>,
44    ) -> Result<Task, PeError>;
45
46    // === Queries ===
47
48    /// List tasks matching a filter.
49    async fn list(&self, filter: &TaskFilter) -> Result<Vec<Task>, PeError>;
50
51    /// Get direct subtasks of a parent.
52    async fn get_subtasks(&self, parent_id: &TaskId) -> Result<Vec<Task>, PeError>;
53
54    /// Get the full subtask tree (recursive) rooted at a task.
55    async fn get_tree(&self, root_id: &TaskId) -> Result<Vec<Task>, PeError>;
56
57    // === Dependencies ===
58
59    /// Add a dependency edge. Rejects if it would create a cycle.
60    async fn add_dependency(&self, dep: TaskDependency) -> Result<(), PeError>;
61
62    /// Remove a dependency edge.
63    async fn remove_dependency(
64        &self,
65        task_id: &TaskId,
66        depends_on_id: &TaskId,
67    ) -> Result<(), PeError>;
68
69    /// Get all dependencies of a task (what it depends on).
70    async fn get_dependencies(&self, task_id: &TaskId) -> Result<Vec<TaskDependency>, PeError>;
71
72    /// Get all dependents of a task (what depends on it).
73    async fn get_dependents(&self, task_id: &TaskId) -> Result<Vec<TaskDependency>, PeError>;
74
75    /// Get all tasks that are ready to execute (no incomplete Blocks deps).
76    async fn get_ready_tasks(&self) -> Result<Vec<Task>, PeError>;
77}
78
79/// Filter for listing tasks.
80///
81/// All fields are optional — `None` means "don't filter on this field".
82///
83/// # Example
84///
85/// ```
86/// use pe_tasks::registry::TaskFilter;
87/// use pe_tasks::TaskStatus;
88///
89/// let filter = TaskFilter::default().with_status(TaskStatus::Pending).with_limit(10);
90/// ```
91#[derive(Clone, Debug, Default)]
92pub struct TaskFilter {
93    pub status: Option<TaskStatus>,
94    pub task_type: Option<TaskType>,
95    pub priority: Option<TaskPriority>,
96    pub agent_id: Option<String>,
97    pub assignee: Option<String>,
98    pub parent_id: Option<TaskId>,
99    pub tag: Option<String>,
100    pub include_deleted: bool,
101    pub limit: usize,
102}
103
104impl TaskFilter {
105    /// Builder: filter by status.
106    #[must_use]
107    pub fn with_status(mut self, status: TaskStatus) -> Self {
108        self.status = Some(status);
109        self
110    }
111
112    /// Builder: filter by task type.
113    #[must_use]
114    pub fn with_task_type(mut self, tt: TaskType) -> Self {
115        self.task_type = Some(tt);
116        self
117    }
118
119    /// Builder: filter by priority.
120    #[must_use]
121    pub fn with_priority(mut self, priority: TaskPriority) -> Self {
122        self.priority = Some(priority);
123        self
124    }
125
126    /// Builder: filter by agent.
127    #[must_use]
128    pub fn with_agent(mut self, agent_id: impl Into<String>) -> Self {
129        self.agent_id = Some(agent_id.into());
130        self
131    }
132
133    /// Builder: filter by assignee.
134    #[must_use]
135    pub fn with_assignee(mut self, assignee: impl Into<String>) -> Self {
136        self.assignee = Some(assignee.into());
137        self
138    }
139
140    /// Builder: filter by parent.
141    #[must_use]
142    pub fn with_parent(mut self, parent_id: impl Into<TaskId>) -> Self {
143        self.parent_id = Some(parent_id.into());
144        self
145    }
146
147    /// Builder: filter by tag.
148    #[must_use]
149    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
150        self.tag = Some(tag.into());
151        self
152    }
153
154    /// Builder: set result limit.
155    #[must_use]
156    pub fn with_limit(mut self, limit: usize) -> Self {
157        self.limit = limit;
158        self
159    }
160
161    /// Builder: include deleted tasks.
162    #[must_use]
163    pub fn including_deleted(mut self) -> Self {
164        self.include_deleted = true;
165        self
166    }
167}