1use 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#[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#[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)]
117pub struct VectorStoreCapabilities {
118 pub supports_filter: bool,
120 pub supports_batch_search: bool,
122 pub supports_quantization: bool,
124 pub supports_update: bool,
126 pub max_dimensions: Option<u32>,
128 pub index_kinds: Vec<VectorIndexKind>,
130}
131
132#[derive(Clone, Debug, Default, Serialize, Deserialize)]
139pub struct VectorMetadataFilter {
140 pub namespaces: Vec<String>,
142 pub kinds: Vec<SubstrateKind>,
144 pub properties: Vec<(String, serde_json::Value)>,
146}
147
148impl VectorMetadataFilter {
149 pub fn is_empty(&self) -> bool {
151 self.namespaces.is_empty() && self.kinds.is_empty() && self.properties.is_empty()
152 }
153}
154
155#[derive(Clone, Debug, Serialize, Deserialize)]
156pub struct VectorRecord {
157 pub subject_id: Uuid,
158 pub kind: SubstrateKind,
159 pub namespace: String,
160 pub embedding: Vec<f32>,
161 pub updated_at: DateTime<Utc>,
162}
163
164#[derive(Clone, Debug, Serialize, Deserialize)]
165pub struct VectorSearchRequest {
166 pub query_embedding: Vec<f32>,
167 pub top_k: u32,
168 pub namespace: Option<String>,
169 pub kind: Option<SubstrateKind>,
170}
171
172#[derive(Clone, Debug, Serialize, Deserialize)]
173pub struct VectorSearchHit {
174 pub subject_id: Uuid,
175 pub score: khive_score::DeterministicScore,
176 pub rank: u32,
177}
178
179#[derive(Clone, Debug, Serialize, Deserialize)]
180pub struct VectorStoreInfo {
181 pub model_name: String,
182 pub dimensions: usize,
183 pub index_kind: VectorIndexKind,
184 pub entry_count: u64,
185 pub needs_rebuild: bool,
186 pub last_rebuild_at: Option<DateTime<Utc>>,
187}
188
189#[derive(Clone, Debug, Serialize, Deserialize)]
192pub struct TextDocument {
193 pub subject_id: Uuid,
194 pub kind: SubstrateKind,
195 pub namespace: String,
196 pub title: Option<String>,
197 pub body: String,
198 pub tags: Vec<String>,
199 pub metadata: Option<Value>,
200 pub updated_at: DateTime<Utc>,
201}
202
203#[derive(Clone, Debug, Default, Serialize, Deserialize)]
204pub struct TextFilter {
205 pub ids: Vec<Uuid>,
206 pub kinds: Vec<SubstrateKind>,
207 pub namespaces: Vec<String>,
208}
209
210#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
211#[serde(rename_all = "snake_case")]
212pub enum TextQueryMode {
213 Plain,
214 Phrase,
215}
216
217#[derive(Clone, Debug, Serialize, Deserialize)]
218pub struct TextSearchRequest {
219 pub query: String,
220 pub mode: TextQueryMode,
221 pub filter: Option<TextFilter>,
222 pub top_k: u32,
223 pub snippet_chars: usize,
224}
225
226#[derive(Clone, Debug, Serialize, Deserialize)]
227pub struct TextSearchHit {
228 pub subject_id: Uuid,
229 pub score: khive_score::DeterministicScore,
230 pub rank: u32,
231 pub title: Option<String>,
232 pub snippet: Option<String>,
233}
234
235#[derive(Clone, Debug, Serialize, Deserialize)]
236pub struct TextIndexStats {
237 pub document_count: u64,
238 pub needs_rebuild: bool,
239 pub last_rebuild_at: Option<DateTime<Utc>>,
240}
241
242#[derive(Clone, Debug, Serialize, Deserialize)]
243#[serde(rename_all = "snake_case")]
244pub enum IndexRebuildScope {
245 Full,
246 Entities(Vec<Uuid>),
247}
248
249#[derive(Clone, Debug, Serialize, Deserialize)]
252pub struct PageRequest {
253 pub offset: u64,
254 pub limit: u32,
255}
256
257impl Default for PageRequest {
258 fn default() -> Self {
259 Self {
260 offset: 0,
261 limit: 50,
262 }
263 }
264}
265
266#[derive(Clone, Debug, Serialize, Deserialize)]
267pub struct Page<T> {
268 pub items: Vec<T>,
269 pub total: Option<u64>,
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
276pub struct LinkId(pub Uuid);
277
278impl From<Uuid> for LinkId {
279 fn from(u: Uuid) -> Self {
280 Self(u)
281 }
282}
283
284impl From<LinkId> for Uuid {
285 fn from(l: LinkId) -> Uuid {
286 l.0
287 }
288}
289
290impl fmt::Display for LinkId {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 self.0.fmt(f)
293 }
294}
295
296#[derive(Clone, Debug, Serialize, Deserialize)]
298pub struct Edge {
299 pub id: LinkId,
300 pub source_id: Uuid,
301 pub target_id: Uuid,
302 pub relation: EdgeRelation,
303 pub weight: f64,
304 pub created_at: DateTime<Utc>,
305 pub metadata: Option<Value>,
306}
307
308#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
309#[serde(rename_all = "snake_case")]
310pub enum Direction {
311 #[default]
312 Out,
313 In,
314 Both,
315}
316
317#[derive(Clone, Debug, Default, Serialize, Deserialize)]
318pub struct TimeRange {
319 pub start: Option<DateTime<Utc>>,
320 pub end: Option<DateTime<Utc>>,
321}
322
323#[derive(Clone, Debug, Default, Serialize, Deserialize)]
324pub struct EdgeFilter {
325 pub ids: Vec<LinkId>,
326 pub source_ids: Vec<Uuid>,
327 pub target_ids: Vec<Uuid>,
328 pub relations: Vec<EdgeRelation>,
329 pub min_weight: Option<f64>,
330 pub max_weight: Option<f64>,
331 pub created_at: Option<TimeRange>,
332}
333
334#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
335#[serde(rename_all = "snake_case")]
336pub enum EdgeSortField {
337 CreatedAt,
338 Weight,
339 Relation,
340}
341
342#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
343#[serde(rename_all = "snake_case")]
344pub enum SortDirection {
345 Asc,
346 Desc,
347}
348
349#[derive(Clone, Debug, Serialize, Deserialize)]
350pub struct SortOrder<F> {
351 pub field: F,
352 pub direction: SortDirection,
353}
354
355#[derive(Clone, Debug, Serialize, Deserialize)]
356pub struct NeighborQuery {
357 pub direction: Direction,
358 pub relations: Option<Vec<EdgeRelation>>,
359 pub limit: Option<u32>,
360 pub min_weight: Option<f64>,
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
373pub struct NeighborHit {
374 #[serde(rename = "id")]
375 pub node_id: Uuid,
376 pub edge_id: Uuid,
377 pub relation: EdgeRelation,
378 pub weight: f64,
379 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub name: Option<String>,
381 #[serde(default, skip_serializing_if = "Option::is_none")]
382 pub kind: Option<String>,
383}
384
385#[derive(Clone, Debug, Default, Serialize, Deserialize)]
386pub struct TraversalOptions {
387 pub max_depth: usize,
388 pub direction: Direction,
389 pub relations: Option<Vec<EdgeRelation>>,
390 pub min_weight: Option<f64>,
391 pub limit: Option<u32>,
392}
393
394impl TraversalOptions {
395 pub fn new(max_depth: usize) -> Self {
396 Self {
397 max_depth,
398 ..Default::default()
399 }
400 }
401
402 pub fn with_direction(mut self, d: Direction) -> Self {
403 self.direction = d;
404 self
405 }
406}
407
408#[derive(Clone, Debug, Serialize, Deserialize)]
409pub struct TraversalRequest {
410 pub roots: Vec<Uuid>,
411 pub options: TraversalOptions,
412 pub include_roots: bool,
413}
414
415#[derive(Clone, Debug, Serialize, Deserialize)]
420pub struct PathNode {
421 #[serde(rename = "id")]
422 pub node_id: Uuid,
423 pub via_edge: Option<Uuid>,
424 pub depth: usize,
425 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub name: Option<String>,
427 #[serde(default, skip_serializing_if = "Option::is_none")]
428 pub kind: Option<String>,
429}
430
431#[derive(Clone, Debug, Serialize, Deserialize)]
432pub struct GraphPath {
433 pub root_id: Uuid,
434 pub nodes: Vec<PathNode>,
435 pub total_weight: f64,
436}