Skip to main content

khive_storage/
types.rs

1//! Shared types used across storage capability traits.
2
3use std::fmt;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use uuid::Uuid;
9
10use khive_types::{EdgeRelation, SubstrateKind};
11
12use crate::error::StorageError;
13
14pub type StorageResult<T> = Result<T, StorageError>;
15
16#[derive(Clone, Debug, Default, Serialize, Deserialize)]
17pub struct BatchWriteSummary {
18    pub attempted: u64,
19    pub affected: u64,
20    pub failed: u64,
21    #[serde(default, skip_serializing_if = "String::is_empty")]
22    pub first_error: String,
23}
24
25#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
26#[serde(rename_all = "snake_case")]
27pub enum DeleteMode {
28    Soft,
29    Hard,
30}
31
32// -- SQL primitives --
33
34#[derive(Clone, Debug, Serialize, Deserialize)]
35#[serde(rename_all = "snake_case")]
36pub enum SqlValue {
37    Null,
38    Bool(bool),
39    Integer(i64),
40    Float(f64),
41    Text(String),
42    Blob(Vec<u8>),
43    Json(Value),
44    Uuid(Uuid),
45    Timestamp(DateTime<Utc>),
46}
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct SqlStatement {
50    pub sql: String,
51    pub params: Vec<SqlValue>,
52    pub label: Option<String>,
53}
54
55#[derive(Clone, Debug, Serialize, Deserialize)]
56pub struct SqlColumn {
57    pub name: String,
58    pub value: SqlValue,
59}
60
61#[derive(Clone, Debug, Serialize, Deserialize)]
62pub struct SqlRow {
63    pub columns: Vec<SqlColumn>,
64}
65
66impl SqlRow {
67    pub fn get(&self, name: &str) -> Option<&SqlValue> {
68        self.columns
69            .iter()
70            .find(|c| c.name == name)
71            .map(|c| &c.value)
72    }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
76#[serde(rename_all = "snake_case")]
77pub enum SqlIsolation {
78    Default,
79    ReadCommitted,
80    RepeatableRead,
81    Serializable,
82}
83
84#[derive(Clone, Debug, Serialize, Deserialize)]
85pub struct SqlTxOptions {
86    pub read_only: bool,
87    pub isolation: SqlIsolation,
88    pub label: Option<String>,
89}
90
91impl Default for SqlTxOptions {
92    fn default() -> Self {
93        Self {
94            read_only: false,
95            isolation: SqlIsolation::Default,
96            label: None,
97        }
98    }
99}
100
101// -- Vector types --
102
103#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
104#[serde(rename_all = "snake_case")]
105pub enum VectorIndexKind {
106    Hnsw,
107    SqliteVec,
108    Flat,
109}
110
111#[derive(Clone, Debug, Serialize, Deserialize)]
112pub struct VectorRecord {
113    pub subject_id: Uuid,
114    pub kind: SubstrateKind,
115    pub namespace: String,
116    pub embedding: Vec<f32>,
117    pub updated_at: DateTime<Utc>,
118}
119
120#[derive(Clone, Debug, Serialize, Deserialize)]
121pub struct VectorSearchRequest {
122    pub query_embedding: Vec<f32>,
123    pub top_k: u32,
124    pub namespace: Option<String>,
125    pub kind: Option<SubstrateKind>,
126}
127
128#[derive(Clone, Debug, Serialize, Deserialize)]
129pub struct VectorSearchHit {
130    pub subject_id: Uuid,
131    pub score: khive_score::DeterministicScore,
132    pub rank: u32,
133}
134
135#[derive(Clone, Debug, Serialize, Deserialize)]
136pub struct VectorStoreInfo {
137    pub model_name: String,
138    pub dimensions: usize,
139    pub index_kind: VectorIndexKind,
140    pub entry_count: u64,
141    pub needs_rebuild: bool,
142    pub last_rebuild_at: Option<DateTime<Utc>>,
143}
144
145// -- Text search types --
146
147#[derive(Clone, Debug, Serialize, Deserialize)]
148pub struct TextDocument {
149    pub subject_id: Uuid,
150    pub kind: SubstrateKind,
151    pub namespace: String,
152    pub title: Option<String>,
153    pub body: String,
154    pub tags: Vec<String>,
155    pub metadata: Option<Value>,
156    pub updated_at: DateTime<Utc>,
157}
158
159#[derive(Clone, Debug, Default, Serialize, Deserialize)]
160pub struct TextFilter {
161    pub ids: Vec<Uuid>,
162    pub kinds: Vec<SubstrateKind>,
163    pub namespaces: Vec<String>,
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
167#[serde(rename_all = "snake_case")]
168pub enum TextQueryMode {
169    Plain,
170    Phrase,
171}
172
173#[derive(Clone, Debug, Serialize, Deserialize)]
174pub struct TextSearchRequest {
175    pub query: String,
176    pub mode: TextQueryMode,
177    pub filter: Option<TextFilter>,
178    pub top_k: u32,
179    pub snippet_chars: usize,
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize)]
183pub struct TextSearchHit {
184    pub subject_id: Uuid,
185    pub score: khive_score::DeterministicScore,
186    pub rank: u32,
187    pub title: Option<String>,
188    pub snippet: Option<String>,
189}
190
191#[derive(Clone, Debug, Serialize, Deserialize)]
192pub struct TextIndexStats {
193    pub document_count: u64,
194    pub needs_rebuild: bool,
195    pub last_rebuild_at: Option<DateTime<Utc>>,
196}
197
198#[derive(Clone, Debug, Serialize, Deserialize)]
199#[serde(rename_all = "snake_case")]
200pub enum IndexRebuildScope {
201    Full,
202    Entities(Vec<Uuid>),
203}
204
205// -- Pagination --
206
207#[derive(Clone, Debug, Serialize, Deserialize)]
208pub struct PageRequest {
209    pub offset: u64,
210    pub limit: u32,
211}
212
213impl Default for PageRequest {
214    fn default() -> Self {
215        Self {
216            offset: 0,
217            limit: 50,
218        }
219    }
220}
221
222#[derive(Clone, Debug, Serialize, Deserialize)]
223pub struct Page<T> {
224    pub items: Vec<T>,
225    pub total: Option<u64>,
226}
227
228// -- Graph types --
229
230/// A type-safe link ID (wraps Uuid).
231#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
232pub struct LinkId(pub Uuid);
233
234impl From<Uuid> for LinkId {
235    fn from(u: Uuid) -> Self {
236        Self(u)
237    }
238}
239
240impl From<LinkId> for Uuid {
241    fn from(l: LinkId) -> Uuid {
242        l.0
243    }
244}
245
246impl fmt::Display for LinkId {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        self.0.fmt(f)
249    }
250}
251
252/// A directed edge in the graph.
253#[derive(Clone, Debug, Serialize, Deserialize)]
254pub struct Edge {
255    pub id: LinkId,
256    pub source_id: Uuid,
257    pub target_id: Uuid,
258    pub relation: EdgeRelation,
259    pub weight: f64,
260    pub created_at: DateTime<Utc>,
261    pub metadata: Option<Value>,
262}
263
264#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
265#[serde(rename_all = "snake_case")]
266pub enum Direction {
267    #[default]
268    Out,
269    In,
270    Both,
271}
272
273#[derive(Clone, Debug, Default, Serialize, Deserialize)]
274pub struct TimeRange {
275    pub start: Option<DateTime<Utc>>,
276    pub end: Option<DateTime<Utc>>,
277}
278
279#[derive(Clone, Debug, Default, Serialize, Deserialize)]
280pub struct EdgeFilter {
281    pub ids: Vec<LinkId>,
282    pub source_ids: Vec<Uuid>,
283    pub target_ids: Vec<Uuid>,
284    pub relations: Vec<EdgeRelation>,
285    pub min_weight: Option<f64>,
286    pub max_weight: Option<f64>,
287    pub created_at: Option<TimeRange>,
288}
289
290#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
291#[serde(rename_all = "snake_case")]
292pub enum EdgeSortField {
293    CreatedAt,
294    Weight,
295    Relation,
296}
297
298#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
299#[serde(rename_all = "snake_case")]
300pub enum SortDirection {
301    Asc,
302    Desc,
303}
304
305#[derive(Clone, Debug, Serialize, Deserialize)]
306pub struct SortOrder<F> {
307    pub field: F,
308    pub direction: SortDirection,
309}
310
311#[derive(Clone, Debug, Serialize, Deserialize)]
312pub struct NeighborQuery {
313    pub direction: Direction,
314    pub relations: Option<Vec<EdgeRelation>>,
315    pub limit: Option<u32>,
316    pub min_weight: Option<f64>,
317}
318
319#[derive(Clone, Debug, Serialize, Deserialize)]
320pub struct NeighborHit {
321    pub node_id: Uuid,
322    pub edge_id: Uuid,
323    pub relation: EdgeRelation,
324    pub weight: f64,
325}
326
327#[derive(Clone, Debug, Default, Serialize, Deserialize)]
328pub struct TraversalOptions {
329    pub max_depth: usize,
330    pub direction: Direction,
331    pub relations: Option<Vec<EdgeRelation>>,
332    pub min_weight: Option<f64>,
333    pub limit: Option<u32>,
334}
335
336impl TraversalOptions {
337    pub fn new(max_depth: usize) -> Self {
338        Self {
339            max_depth,
340            ..Default::default()
341        }
342    }
343
344    pub fn with_direction(mut self, d: Direction) -> Self {
345        self.direction = d;
346        self
347    }
348}
349
350#[derive(Clone, Debug, Serialize, Deserialize)]
351pub struct TraversalRequest {
352    pub roots: Vec<Uuid>,
353    pub options: TraversalOptions,
354    pub include_roots: bool,
355}
356
357#[derive(Clone, Debug, Serialize, Deserialize)]
358pub struct PathNode {
359    pub node_id: Uuid,
360    pub via_edge: Option<Uuid>,
361    pub depth: usize,
362}
363
364#[derive(Clone, Debug, Serialize, Deserialize)]
365pub struct GraphPath {
366    pub root_id: Uuid,
367    pub nodes: Vec<PathNode>,
368    pub total_weight: f64,
369}