1use std::collections::BTreeMap;
4
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use uuid::Uuid;
9
10use khive_types::{EventKind, EventOutcome, RefResolution, SubstrateKind};
11
12use crate::capability::StorageCapability;
13use crate::error::StorageError;
14use crate::types::{BatchWriteSummary, Page, PageRequest, SqlValue, StorageResult};
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct Event {
20 pub id: Uuid,
21 pub namespace: String,
22 pub verb: String,
23 pub substrate: SubstrateKind,
24 pub actor: String,
25 pub kind: EventKind,
26 pub outcome: EventOutcome,
27 pub payload: Value,
28 pub payload_schema_version: u32,
29 pub profile_state_version: Option<u64>,
30 pub duration_us: i64,
31 pub target_id: Option<Uuid>,
32 pub session_id: Option<Uuid>,
33 pub aggregate_kind: Option<String>,
34 pub aggregate_id: Option<Uuid>,
35 pub created_at: i64,
36 #[serde(default)]
38 pub op_index: Option<u32>,
39 #[serde(default)]
41 pub ref_resolution: Option<RefResolution>,
42}
43
44impl Event {
45 pub fn new(
47 namespace: impl Into<String>,
48 verb: impl Into<String>,
49 kind: EventKind,
50 substrate: SubstrateKind,
51 actor: impl Into<String>,
52 ) -> Self {
53 let operation = crate::operation_context::current_operation_attribution();
54 Self {
55 id: Uuid::new_v4(),
56 namespace: namespace.into(),
57 verb: verb.into(),
58 substrate,
59 actor: actor.into(),
60 kind,
61 outcome: EventOutcome::Success,
62 payload: Value::Object(Default::default()),
63 payload_schema_version: 1,
64 profile_state_version: None,
65 duration_us: 0,
66 target_id: None,
67 session_id: None,
68 aggregate_kind: None,
69 aggregate_id: None,
70 created_at: chrono::Utc::now().timestamp_micros(),
71 op_index: operation.map(|operation| operation.op_index),
72 ref_resolution: operation.map(|operation| operation.ref_resolution),
73 }
74 }
75
76 pub fn with_outcome(mut self, o: EventOutcome) -> Self {
78 self.outcome = o;
79 self
80 }
81
82 pub fn with_payload(mut self, payload: Value) -> Self {
84 self.payload = payload;
85 self
86 }
87
88 pub fn with_payload_schema_version(mut self, version: u32) -> Self {
90 self.payload_schema_version = version;
91 self
92 }
93
94 pub fn with_profile_state_version(mut self, version: u64) -> Self {
96 self.profile_state_version = Some(version);
97 self
98 }
99
100 pub fn with_duration_us(mut self, us: i64) -> Self {
102 self.duration_us = us;
103 self
104 }
105
106 pub fn with_target(mut self, id: Uuid) -> Self {
108 self.target_id = Some(id);
109 self
110 }
111
112 pub fn with_session_id(mut self, id: Uuid) -> Self {
114 self.session_id = Some(id);
115 self
116 }
117
118 pub fn with_aggregate(mut self, kind: impl Into<String>, id: Uuid) -> Self {
120 self.aggregate_kind = Some(kind.into());
121 self.aggregate_id = Some(id);
122 self
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
131#[serde(rename_all = "snake_case")]
132pub enum ReferentKind {
133 Entity,
134 Note,
135 Edge,
136}
137
138impl ReferentKind {
139 pub const fn name(self) -> &'static str {
141 match self {
142 Self::Entity => "entity",
143 Self::Note => "note",
144 Self::Edge => "edge",
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
151#[serde(rename_all = "snake_case")]
152pub enum ObservationRole {
153 Candidate,
154 Selected,
155 Target,
156 Signal,
157}
158
159impl ObservationRole {
160 pub const fn name(self) -> &'static str {
162 match self {
163 Self::Candidate => "candidate",
164 Self::Selected => "selected",
165 Self::Target => "target",
166 Self::Signal => "signal",
167 }
168 }
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct EventObservation {
174 pub event_id: Uuid,
175 pub entity_id: Uuid,
176 pub referent_kind: ReferentKind,
177 pub role: ObservationRole,
178 pub position: u32,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct EventView {
184 pub event: Event,
185 pub observations: Vec<EventObservation>,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(rename_all = "snake_case")]
191pub enum EventGroupBy {
192 Verb,
194 Kind,
196 Actor,
198}
199
200impl EventGroupBy {
201 pub const fn column(self) -> &'static str {
203 match self {
204 Self::Verb => "verb",
205 Self::Kind => "kind",
206 Self::Actor => "actor",
207 }
208 }
209}
210
211#[derive(Clone, Debug, Default, Serialize, Deserialize)]
216pub struct EventFilter {
217 pub ids: Vec<Uuid>,
218 #[serde(default)]
220 pub target_id: Option<Uuid>,
221 pub kinds: Vec<EventKind>,
222 pub verbs: Vec<String>,
223 pub substrates: Vec<SubstrateKind>,
224 pub actors: Vec<String>,
225 pub after: Option<i64>,
226 pub before: Option<i64>,
227 pub session_id: Option<Uuid>,
228 pub observed: Vec<Uuid>,
229 pub selected: Vec<Uuid>,
230 pub payload_proposal_id: Option<Uuid>,
231 #[serde(default)]
233 pub outcome: Option<EventOutcome>,
234 #[serde(default)]
236 pub payload_equalities: Vec<(String, SqlValue)>,
237}
238
239impl EventFilter {
240 pub fn outcome(mut self, outcome: EventOutcome) -> Self {
242 self.outcome = Some(outcome);
243 self
244 }
245
246 pub fn payload_eq(mut self, path: impl Into<String>, value: SqlValue) -> Self {
254 self.payload_equalities.push((path.into(), value));
255 self
256 }
257}
258
259#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
261pub struct EventOrderKey {
262 pub created_at_us: i64,
263 pub physical_id: String,
264}
265
266#[derive(Clone, Debug, Serialize, Deserialize)]
268pub struct EventPageQuery {
269 pub since_us: i64,
270 pub until_us: i64,
271 pub kinds: Vec<EventKind>,
272 pub actors: Vec<String>,
273 pub exclude_namespaces: Vec<String>,
274 pub after: Option<EventOrderKey>,
275 pub max_rows: u32,
276}
277
278#[derive(Clone, Debug, Serialize, Deserialize)]
280pub struct EventPageRow {
281 pub event: Event,
282 pub order_key: EventOrderKey,
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize)]
291pub struct EventPageWindow {
292 pub rows: Vec<EventPageRow>,
293 #[serde(default)]
294 pub budget_stop: Option<EventOrderKey>,
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
302#[serde(rename_all = "snake_case")]
303pub enum EventAppendDisposition {
304 Inserted,
306 AlreadyPresentIdentical,
309 IdentityConflict,
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317pub struct IdempotentEventBatchResult {
318 pub rows: Vec<EventAppendDisposition>,
319}
320
321#[async_trait]
323pub trait EventStore: Send + Sync + 'static {
324 async fn append_event(&self, event: Event) -> StorageResult<()>;
326 async fn append_events(&self, events: Vec<Event>) -> StorageResult<BatchWriteSummary>;
328 async fn get_event(&self, id: Uuid) -> StorageResult<Option<Event>>;
330 async fn query_events(
332 &self,
333 filter: EventFilter,
334 page: PageRequest,
335 ) -> StorageResult<Page<Event>>;
336 async fn query_event_page(&self, query: EventPageQuery) -> StorageResult<EventPageWindow> {
340 let _ = query;
341 Err(StorageError::Unsupported {
342 capability: StorageCapability::Events,
343 operation: "query_event_page".into(),
344 message: "this EventStore backend does not implement query_event_page".into(),
345 })
346 }
347
348 async fn count_events(&self, filter: EventFilter) -> StorageResult<u64>;
350
351 async fn count_events_grouped(
355 &self,
356 filter: EventFilter,
357 group_by: EventGroupBy,
358 ) -> StorageResult<BTreeMap<String, u64>> {
359 let _ = (filter, group_by);
360 Err(StorageError::Unsupported {
361 capability: StorageCapability::Events,
362 operation: "count_events_grouped".into(),
363 message: "this EventStore backend does not implement grouped counts".into(),
364 })
365 }
366
367 fn preflight_event(&self, event: &Event) -> StorageResult<()> {
375 let _ = event;
376 Err(StorageError::Unsupported {
377 capability: StorageCapability::Events,
378 operation: "preflight_event".into(),
379 message: "this EventStore backend does not implement preflight_event".into(),
380 })
381 }
382
383 async fn append_events_idempotent(
395 &self,
396 events: Vec<Event>,
397 ) -> StorageResult<IdempotentEventBatchResult> {
398 let _ = events;
399 Err(StorageError::Unsupported {
400 capability: StorageCapability::Events,
401 operation: "append_events_idempotent".into(),
402 message: "this EventStore backend does not implement append_events_idempotent".into(),
403 })
404 }
405
406 fn supports_idempotent_audit_batch(&self) -> bool {
417 false
418 }
419}
420
421#[cfg(test)]
422#[path = "event/tests.rs"]
423mod tests;