Skip to main content

eventdbx/
store.rs

1use std::{
2    cmp::Ordering as StdOrdering,
3    collections::{BTreeMap, BTreeSet, HashMap, hash_map::Entry},
4    convert::TryInto,
5    fmt, fs, io,
6    path::{Path, PathBuf},
7    str::{self, FromStr},
8    thread,
9    time::{Duration, Instant},
10};
11
12use chrono::{DateTime, Utc};
13use parking_lot::{Mutex, MutexGuard};
14use rocksdb::{
15    DBIterator, DBWithThreadMode, Direction, IteratorMode, MultiThreaded, Options, WriteBatch,
16};
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19
20use json_patch::Patch;
21use metrics::{counter, histogram};
22use tracing::warn;
23
24#[cfg(unix)]
25use std::os::fd::AsRawFd;
26
27use super::{
28    encryption::{self, Encryptor},
29    error::{EventError, Result},
30    merkle::{compute_merkle_root, empty_root},
31    schema::MAX_EVENT_NOTE_LENGTH,
32    validation::MAX_EVENT_TYPE_LENGTH,
33};
34use crate::{
35    filter::FilterExpr,
36    snowflake::{MAX_WORKER_ID, SnowflakeGenerator, SnowflakeId},
37};
38
39const SEP: u8 = 0x1F;
40const PREFIX_EVENT: &str = "evt";
41const PREFIX_EVENT_ARCHIVED: &str = "evt-arch";
42const PREFIX_META: &str = "meta";
43const PREFIX_STATE: &str = "state";
44const PREFIX_META_ARCHIVED: &str = "meta-arch";
45const PREFIX_STATE_ARCHIVED: &str = "state-arch";
46const PREFIX_SNAPSHOT: &str = "snapshot";
47const PREFIX_SNAPSHOT_ID: &str = "snapidx";
48const PREFIX_META_CREATED_INDEX: &str = "meta-created";
49const PREFIX_META_UPDATED_INDEX: &str = "meta-updated";
50const PREFIX_META_CREATED_INDEX_ARCHIVED: &str = "meta-created-arch";
51const PREFIX_META_UPDATED_INDEX_ARCHIVED: &str = "meta-updated-arch";
52const PREFIX_REF_SOURCE: &str = "refsrc";
53const PREFIX_REF_TARGET: &str = "reftgt";
54const LOCK_RETRY_ATTEMPTS: usize = 5;
55const LOCK_RETRY_BACKOFF: Duration = Duration::from_millis(100);
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct EventRecord {
59    pub aggregate_type: String,
60    pub aggregate_id: String,
61    pub event_type: String,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub event_type_raw: Option<String>,
64    pub payload: Value,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub extensions: Option<Value>,
67    pub metadata: EventMetadata,
68    pub version: u64,
69    pub hash: String,
70    pub merkle_root: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct EventMetadata {
75    pub event_id: SnowflakeId,
76    pub created_at: DateTime<Utc>,
77    pub issued_by: Option<ActorClaims>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub note: Option<String>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ActorClaims {
84    pub group: String,
85    pub user: String,
86}
87
88fn ensure_event_type_lengths(event_type: &str, event_type_raw: Option<&String>) -> Result<()> {
89    if event_type.len() > MAX_EVENT_TYPE_LENGTH {
90        return Err(EventError::InvalidSchema(format!(
91            "event_type cannot exceed {} characters",
92            MAX_EVENT_TYPE_LENGTH
93        )));
94    }
95    if let Some(raw) = event_type_raw {
96        if raw.len() > MAX_EVENT_TYPE_LENGTH {
97            return Err(EventError::InvalidSchema(format!(
98                "event_type_raw cannot exceed {} characters",
99                MAX_EVENT_TYPE_LENGTH
100            )));
101        }
102    }
103    Ok(())
104}
105
106impl EventRecord {
107    /// Render the event for user-facing output by inlining extensions into metadata
108    /// and omitting the raw event type.
109    pub fn to_output_value(&self) -> serde_json::Result<Value> {
110        let mut value = serde_json::to_value(self)?;
111        let Some(object) = value.as_object_mut() else {
112            return Ok(value);
113        };
114
115        object.remove("event_type_raw");
116
117        if let Some(extensions) = object.remove("extensions") {
118            let metadata_entry = object
119                .entry("metadata")
120                .or_insert_with(|| Value::Object(serde_json::Map::new()));
121            if let Some(meta) = metadata_entry.as_object_mut() {
122                if let Some(ext_map) = extensions.as_object() {
123                    for (key, value) in ext_map {
124                        meta.insert(key.clone(), value.clone());
125                    }
126                } else {
127                    meta.insert("@extensions".to_string(), extensions);
128                }
129            } else if let Some(ext_map) = extensions.as_object() {
130                object.insert("metadata".into(), Value::Object(ext_map.clone()));
131            } else {
132                let mut meta = serde_json::Map::new();
133                meta.insert("@extensions".into(), extensions);
134                object.insert("metadata".into(), Value::Object(meta));
135            }
136        }
137
138        Ok(value)
139    }
140}
141
142fn normalize_extension_key(key: &str) -> String {
143    let mut result = String::new();
144    let mut chars = key.chars().peekable();
145    if let Some(first) = chars.next() {
146        if first == '@' {
147            result.push('@');
148        } else {
149            result.push(first.to_ascii_lowercase());
150        }
151    }
152
153    let mut last_char = result.chars().last().unwrap_or('@');
154    for ch in chars {
155        if ch.is_ascii_uppercase() && last_char != '@' && last_char != '_' {
156            result.push('_');
157        }
158        result.push(
159            if ch.is_ascii_alphabetic() {
160                ch.to_ascii_lowercase()
161            } else {
162                ch
163            },
164        );
165        last_char = ch;
166    }
167
168    result
169}
170
171impl<'a> Transaction<'a> {
172    pub fn append(&mut self, input: AppendEvent) -> Result<EventRecord> {
173        if let Some(note) = input.note.as_ref() {
174            if note.chars().count() > MAX_EVENT_NOTE_LENGTH {
175                return Err(EventError::InvalidSchema(format!(
176                    "event note cannot exceed {} characters",
177                    MAX_EVENT_NOTE_LENGTH
178                )));
179            }
180        }
181        ensure_event_type_lengths(&input.event_type, input.event_type_raw.as_ref())?;
182        let tenant = input.tenant.clone();
183        let references = input.reference_targets.clone();
184        let key = AggregateKey::new(input.aggregate_type.clone(), input.aggregate_id.clone());
185        let event_id = self.store.next_event_id();
186        let (meta, state) = self.ensure_cache(&key)?;
187        if meta.archived {
188            return Err(EventError::AggregateArchived);
189        }
190        let record = apply_event(meta, state, input, event_id);
191        let stored_record = self.store.encode_record(&record)?;
192        let event_value = serde_json::to_vec(&stored_record)?;
193
194        self.pending_events.push(PendingEvent {
195            key: event_key(&record.aggregate_type, &record.aggregate_id, record.version),
196            value: event_value,
197        });
198        self.pending_records.push(record.clone());
199
200        self.pending_refs.push(PendingRefUpdate {
201            tenant,
202            aggregate_type: record.aggregate_type.clone(),
203            aggregate_id: record.aggregate_id.clone(),
204            references,
205        });
206        Ok(record)
207    }
208
209    pub fn commit(mut self) -> Result<Vec<EventRecord>> {
210        if self.pending_events.is_empty()
211            && self.meta_cache.is_empty()
212            && self.state_cache.is_empty()
213        {
214            return Ok(Vec::new());
215        }
216
217        let pending_events = std::mem::take(&mut self.pending_events);
218        let meta_cache = std::mem::take(&mut self.meta_cache);
219        let state_cache = std::mem::take(&mut self.state_cache);
220        let records = std::mem::take(&mut self.pending_records);
221
222        let mut batch = WriteBatch::default();
223
224        for pending in pending_events {
225            batch_put(&mut batch, pending.key, pending.value)?;
226        }
227
228        for meta in meta_cache.into_values() {
229            let previous = self
230                .store
231                .load_meta(&meta.aggregate_type, &meta.aggregate_id)?;
232            self.store.sync_timestamp_indexes(
233                &mut batch,
234                AggregateIndex::Active,
235                previous.as_ref(),
236                Some(&meta),
237            );
238            let key = meta_key(&meta.aggregate_type, &meta.aggregate_id);
239            batch_put(&mut batch, key, serde_json::to_vec(&meta)?)?;
240        }
241
242        for (key, state) in state_cache {
243            let (aggregate_type, aggregate_id) = key.parts();
244            let state_bytes = self.store.encode_state_map(&state)?;
245            batch_put(
246                &mut batch,
247                state_key(aggregate_type, aggregate_id),
248                state_bytes,
249            )?;
250        }
251
252        for pending in self.pending_refs {
253            update_reference_index_inner(
254                &self.store.db,
255                &mut batch,
256                &pending.tenant,
257                &pending.aggregate_type,
258                &pending.aggregate_id,
259                &pending.references,
260            )?;
261        }
262
263        self.store.write_batch(batch)?;
264        Ok(records)
265    }
266
267    fn ensure_cache(
268        &mut self,
269        key: &AggregateKey,
270    ) -> Result<(&mut AggregateMeta, &mut BTreeMap<String, String>)> {
271        if !self.meta_cache.contains_key(key) {
272            let (aggregate_type, aggregate_id) = key.parts();
273            let meta = self
274                .store
275                .load_meta(aggregate_type, aggregate_id)?
276                .unwrap_or_else(|| {
277                    AggregateMeta::new(aggregate_type.to_string(), aggregate_id.to_string())
278                });
279            self.meta_cache.insert(key.clone(), meta);
280        }
281
282        if !self.state_cache.contains_key(key) {
283            let (aggregate_type, aggregate_id) = key.parts();
284            let state = self.store.load_state_map(aggregate_type, aggregate_id)?;
285            self.state_cache.insert(key.clone(), state);
286        }
287
288        let meta = self
289            .meta_cache
290            .get_mut(key)
291            .expect("meta cache missing after insertion");
292        let state = self
293            .state_cache
294            .get_mut(key)
295            .expect("state cache missing after insertion");
296        Ok((meta, state))
297    }
298}
299
300fn metadata_to_extensions_map(metadata: &Value) -> BTreeMap<String, Value> {
301    match metadata.as_object() {
302        Some(map) => map
303            .iter()
304            .map(|(key, value)| (normalize_extension_key(key), value.clone()))
305            .collect(),
306        None => {
307            let mut fallback = BTreeMap::new();
308            fallback.insert("@extensions".to_string(), metadata.clone());
309            fallback
310        }
311    }
312}
313
314fn apply_event(
315    meta: &mut AggregateMeta,
316    state: &mut BTreeMap<String, String>,
317    input: AppendEvent,
318    event_id: SnowflakeId,
319) -> EventRecord {
320    let AppendEvent {
321        aggregate_type,
322        aggregate_id,
323        event_type,
324        event_type_raw,
325        payload,
326        metadata,
327        issued_by,
328        note,
329        ..
330    } = input;
331
332    debug_assert_eq!(&meta.aggregate_type, &aggregate_type);
333    debug_assert_eq!(&meta.aggregate_id, &aggregate_id);
334
335    let event_time = Utc::now();
336    let version = meta.version + 1;
337    meta.record_write(event_time);
338    let payload_map = payload_to_map(&payload);
339    let hash = hash_event(
340        &aggregate_type,
341        &aggregate_id,
342        version,
343        &event_type,
344        &payload_map,
345    );
346
347    meta.event_hashes.push(hash.clone());
348    meta.version = version;
349    meta.merkle_root = compute_merkle_root(&meta.event_hashes);
350
351    for (key, value) in payload_map {
352        state.insert(key, value);
353    }
354
355    if let Some(metadata_value) = metadata.as_ref() {
356        let incoming = metadata_to_extensions_map(metadata_value);
357        for (key, value) in incoming {
358            meta.extensions.insert(key, value);
359        }
360    }
361
362    EventRecord {
363        aggregate_type,
364        aggregate_id,
365        event_type,
366        event_type_raw,
367        payload,
368        extensions: metadata,
369        metadata: EventMetadata {
370            event_id,
371            created_at: event_time,
372            issued_by,
373            note,
374        },
375        version,
376        hash,
377        merkle_root: meta.merkle_root.clone(),
378    }
379}
380
381fn batch_put(batch: &mut WriteBatch, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
382    batch.put(key, value);
383    Ok(())
384}
385
386fn decode_pointer_segment(segment: &str) -> String {
387    let mut result = String::with_capacity(segment.len());
388    let mut chars = segment.chars();
389    while let Some(ch) = chars.next() {
390        if ch == '~' {
391            if let Some(next) = chars.next() {
392                match next {
393                    '0' => result.push('~'),
394                    '1' => result.push('/'),
395                    other => {
396                        result.push('~');
397                        result.push(other);
398                    }
399                }
400            } else {
401                result.push('~');
402            }
403        } else {
404            result.push(ch);
405        }
406    }
407    result
408}
409
410fn top_level_key_from_path(path: &str) -> std::result::Result<String, String> {
411    if !path.starts_with('/') {
412        return Err(format!("patch path '{}' must start with '/'", path));
413    }
414    let trimmed = &path[1..];
415    if trimmed.is_empty() {
416        return Err("patch operations targeting the document root are not supported".into());
417    }
418    let segment = trimmed
419        .split('/')
420        .next()
421        .expect("split returns at least one segment");
422    Ok(decode_pointer_segment(segment))
423}
424
425fn collect_top_level_keys(patch: &Value) -> std::result::Result<BTreeSet<String>, String> {
426    let array = patch
427        .as_array()
428        .ok_or_else(|| "patch must be an array".to_string())?;
429    let mut keys = BTreeSet::new();
430    for entry in array {
431        let op = entry
432            .get("op")
433            .and_then(Value::as_str)
434            .ok_or_else(|| "patch entry is missing 'op'".to_string())?;
435        if op != "add" && op != "replace" && op != "remove" {
436            return Err(format!("unsupported patch operation '{}'", op));
437        }
438        let path = entry
439            .get("path")
440            .and_then(Value::as_str)
441            .ok_or_else(|| "patch entry is missing 'path'".to_string())?;
442        let key = top_level_key_from_path(path)?;
443        keys.insert(key);
444    }
445    Ok(keys)
446}
447
448fn parse_state_value(raw: &str) -> Value {
449    if let Ok(parsed) = serde_json::from_str(raw) {
450        return parsed;
451    }
452    Value::String(raw.to_string())
453}
454
455fn state_map_to_value(map: &BTreeMap<String, String>) -> Value {
456    let mut object = serde_json::Map::new();
457    for (key, value) in map {
458        object.insert(key.clone(), parse_state_value(value));
459    }
460    Value::Object(object)
461}
462
463pub fn select_state_field(map: &BTreeMap<String, String>, path: &str) -> Option<Value> {
464    if path.is_empty() {
465        return None;
466    }
467
468    let mut segments = path.split('.');
469    let first = segments.next()?;
470    if first.is_empty() {
471        return None;
472    }
473
474    let mut current = match map.get(first) {
475        Some(raw) => parse_state_value(raw),
476        None => return None,
477    };
478
479    for segment in segments {
480        if segment.is_empty() {
481            return None;
482        }
483        current = match &current {
484            Value::Object(object) => object.get(segment)?.clone(),
485            Value::Array(array) => {
486                let index = segment.parse::<usize>().ok()?;
487                array.get(index)?.clone()
488            }
489            _ => return None,
490        };
491    }
492
493    Some(current)
494}
495
496#[derive(Debug, Clone)]
497pub struct AppendEvent {
498    pub aggregate_type: String,
499    pub aggregate_id: String,
500    pub event_type: String,
501    pub event_type_raw: Option<String>,
502    pub payload: Value,
503    pub metadata: Option<Value>,
504    pub issued_by: Option<ActorClaims>,
505    pub note: Option<String>,
506    /// Tenant that owns the aggregate/event (used for storage and quotas).
507    pub tenant: String,
508    /// (canonical_target, path) pairs for reference updates; path is where the ref lives on the aggregate.
509    pub reference_targets: Vec<(String, String)>,
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct AggregateState {
514    pub aggregate_type: String,
515    pub aggregate_id: String,
516    pub version: u64,
517    pub state: BTreeMap<String, String>,
518    #[serde(default, skip_serializing_if = "BTreeMap::is_empty", flatten)]
519    pub extensions: BTreeMap<String, Value>,
520    pub merkle_root: String,
521    #[serde(default, skip_serializing_if = "Option::is_none")]
522    pub created_at: Option<DateTime<Utc>>,
523    #[serde(default, skip_serializing_if = "Option::is_none")]
524    pub updated_at: Option<DateTime<Utc>>,
525    #[serde(default, skip_serializing_if = "is_false")]
526    pub archived: bool,
527}
528
529#[derive(Debug, Clone)]
530struct AggregateSummary {
531    aggregate_type: String,
532    aggregate_id: String,
533    version: u64,
534    merkle_root: String,
535    archived: bool,
536    created_at: Option<DateTime<Utc>>,
537    updated_at: Option<DateTime<Utc>>,
538    extensions: BTreeMap<String, Value>,
539}
540
541#[derive(Debug, Clone, Copy, Default, Serialize)]
542pub struct StoreCounts {
543    pub active_aggregates: usize,
544    pub archived_aggregates: usize,
545    pub active_events: u64,
546    pub archived_events: u64,
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq)]
550pub enum CursorIndex {
551    Active,
552    Archived,
553}
554
555impl CursorIndex {
556    fn code(self) -> char {
557        match self {
558            CursorIndex::Active => 'a',
559            CursorIndex::Archived => 'r',
560        }
561    }
562
563    fn from_str_tag(tag: &str) -> Result<Self> {
564        match tag {
565            "a" | "active" => Ok(CursorIndex::Active),
566            "r" | "archived" => Ok(CursorIndex::Archived),
567            other => Err(EventError::InvalidCursor(format!(
568                "unknown cursor segment '{other}'"
569            ))),
570        }
571    }
572}
573
574impl fmt::Display for CursorIndex {
575    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576        match self {
577            CursorIndex::Active => write!(f, "active"),
578            CursorIndex::Archived => write!(f, "archived"),
579        }
580    }
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
584enum AggregateCursorKind {
585    Natural,
586    Timestamp {
587        kind: TimestampIndexKind,
588        descending: bool,
589        timestamp_ms: i64,
590    },
591}
592
593#[derive(Debug, Clone, PartialEq, Eq)]
594pub struct AggregateCursor {
595    index: CursorIndex,
596    aggregate_type: String,
597    aggregate_id: String,
598    kind: AggregateCursorKind,
599}
600
601impl AggregateCursor {
602    pub fn new(
603        index: CursorIndex,
604        aggregate_type: impl Into<String>,
605        aggregate_id: impl Into<String>,
606    ) -> Self {
607        Self {
608            index,
609            aggregate_type: aggregate_type.into(),
610            aggregate_id: aggregate_id.into(),
611            kind: AggregateCursorKind::Natural,
612        }
613    }
614
615    pub fn new_timestamp(
616        index: CursorIndex,
617        aggregate_type: impl Into<String>,
618        aggregate_id: impl Into<String>,
619        kind: TimestampIndexKind,
620        descending: bool,
621        timestamp_ms: i64,
622    ) -> Self {
623        Self {
624            index,
625            aggregate_type: aggregate_type.into(),
626            aggregate_id: aggregate_id.into(),
627            kind: AggregateCursorKind::Timestamp {
628                kind,
629                descending,
630                timestamp_ms,
631            },
632        }
633    }
634
635    pub fn index(&self) -> CursorIndex {
636        self.index
637    }
638
639    pub fn aggregate_type(&self) -> &str {
640        &self.aggregate_type
641    }
642
643    pub fn aggregate_id(&self) -> &str {
644        &self.aggregate_id
645    }
646
647    pub fn encode(&self) -> String {
648        match &self.kind {
649            AggregateCursorKind::Natural => format!(
650                "{}:{}:{}",
651                self.index.code(),
652                self.aggregate_type,
653                self.aggregate_id
654            ),
655            AggregateCursorKind::Timestamp {
656                kind,
657                descending,
658                timestamp_ms,
659            } => format!(
660                "ts:{}:{}:{}:{}:{}:{}",
661                kind.as_str(),
662                if *descending { "desc" } else { "asc" },
663                self.index.code(),
664                timestamp_ms,
665                self.aggregate_type,
666                self.aggregate_id
667            ),
668        }
669    }
670
671    pub fn is_timestamp(&self) -> bool {
672        matches!(self.kind, AggregateCursorKind::Timestamp { .. })
673    }
674
675    pub fn timestamp_components(&self) -> Option<(TimestampIndexKind, bool, i64)> {
676        match self.kind {
677            AggregateCursorKind::Timestamp {
678                kind,
679                descending,
680                timestamp_ms,
681            } => Some((kind, descending, timestamp_ms)),
682            _ => None,
683        }
684    }
685
686    pub fn as_timestamp_key(
687        &self,
688    ) -> Option<(TimestampIndexKind, bool, i64, CursorIndex, &str, &str)> {
689        match self.kind {
690            AggregateCursorKind::Timestamp {
691                kind,
692                descending,
693                timestamp_ms,
694            } => Some((
695                kind,
696                descending,
697                timestamp_ms,
698                self.index,
699                self.aggregate_type(),
700                self.aggregate_id(),
701            )),
702            _ => None,
703        }
704    }
705}
706
707impl fmt::Display for AggregateCursor {
708    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709        write!(f, "{}", self.encode())
710    }
711}
712
713impl std::str::FromStr for AggregateCursor {
714    type Err = EventError;
715
716    fn from_str(value: &str) -> Result<Self> {
717        let trimmed = value.trim();
718        if trimmed.is_empty() {
719            return Err(EventError::InvalidCursor("cursor cannot be empty".into()));
720        }
721        if trimmed.starts_with("ts:") {
722            let mut segments = trimmed.split(':');
723            segments.next(); // consume "ts"
724            let kind_str = segments
725                .next()
726                .ok_or_else(|| EventError::InvalidCursor("timestamp cursor missing kind".into()))?;
727            let kind = TimestampIndexKind::from_str_token(kind_str).ok_or_else(|| {
728                EventError::InvalidCursor(format!("unknown timestamp cursor kind '{kind_str}'"))
729            })?;
730            let order = segments.next().ok_or_else(|| {
731                EventError::InvalidCursor("timestamp cursor missing order".into())
732            })?;
733            let descending = match order {
734                "asc" => false,
735                "desc" => true,
736                _ => {
737                    return Err(EventError::InvalidCursor(format!(
738                        "timestamp cursor order must be 'asc' or 'desc', found '{order}'"
739                    )));
740                }
741            };
742            let index_tag = segments.next().ok_or_else(|| {
743                EventError::InvalidCursor("timestamp cursor missing scope".into())
744            })?;
745            let index = CursorIndex::from_str_tag(index_tag)?;
746            let ts_str = segments.next().ok_or_else(|| {
747                EventError::InvalidCursor("timestamp cursor missing timestamp".into())
748            })?;
749            let timestamp_ms = ts_str.parse::<i64>().map_err(|_| {
750                EventError::InvalidCursor(format!(
751                    "timestamp cursor contains invalid timestamp '{ts_str}'"
752                ))
753            })?;
754            let aggregate_type = segments.next().ok_or_else(|| {
755                EventError::InvalidCursor("timestamp cursor missing aggregate_type".into())
756            })?;
757            if aggregate_type.is_empty() {
758                return Err(EventError::InvalidCursor(
759                    "timestamp cursor aggregate_type cannot be empty".into(),
760                ));
761            }
762            let aggregate_id = segments.next().ok_or_else(|| {
763                EventError::InvalidCursor("timestamp cursor missing aggregate_id".into())
764            })?;
765            if aggregate_id.is_empty() {
766                return Err(EventError::InvalidCursor(
767                    "timestamp cursor aggregate_id cannot be empty".into(),
768                ));
769            }
770            if segments.next().is_some() {
771                return Err(EventError::InvalidCursor(
772                    "timestamp cursor has too many segments".into(),
773                ));
774            }
775            return Ok(Self::new_timestamp(
776                index,
777                aggregate_type,
778                aggregate_id,
779                kind,
780                descending,
781                timestamp_ms,
782            ));
783        }
784        let mut segments = trimmed.split(':');
785        let Some(index_tag) = segments.next() else {
786            return Err(EventError::InvalidCursor("cursor is incomplete".into()));
787        };
788        let index = CursorIndex::from_str_tag(index_tag)?;
789        let aggregate_type = segments
790            .next()
791            .ok_or_else(|| EventError::InvalidCursor("cursor missing aggregate_type".into()))?;
792        if aggregate_type.is_empty() {
793            return Err(EventError::InvalidCursor(
794                "cursor aggregate_type cannot be empty".into(),
795            ));
796        }
797        let aggregate_id = segments
798            .next()
799            .ok_or_else(|| EventError::InvalidCursor("cursor missing aggregate_id".into()))?;
800        if aggregate_id.is_empty() {
801            return Err(EventError::InvalidCursor(
802                "cursor aggregate_id cannot be empty".into(),
803            ));
804        }
805        if segments.next().is_some() {
806            return Err(EventError::InvalidCursor(
807                "cursor has too many segments".into(),
808            ));
809        }
810
811        Ok(Self::new(index, aggregate_type, aggregate_id))
812    }
813}
814
815pub fn ensure_timestamp_cursor(
816    cursor: &AggregateCursor,
817    kind: TimestampIndexKind,
818    descending: bool,
819    scope: AggregateQueryScope,
820) -> Result<()> {
821    let Some((cursor_kind, cursor_desc, _, cursor_index, _, _)) = cursor.as_timestamp_key() else {
822        return Err(EventError::InvalidCursor(
823            "timestamp cursors must use the 'ts:' prefix (ts:<field>:<order>:...)".into(),
824        ));
825    };
826    if cursor_kind != kind {
827        return Err(EventError::InvalidCursor(format!(
828            "timestamp cursor targets '{}' but sort is '{}'",
829            cursor_kind.as_str(),
830            kind.as_str()
831        )));
832    }
833    if cursor_desc != descending {
834        return Err(EventError::InvalidCursor(
835            "timestamp cursor order does not match the requested sort direction".into(),
836        ));
837    }
838    match scope {
839        AggregateQueryScope::ActiveOnly => {
840            if cursor_index != CursorIndex::Active {
841                return Err(EventError::InvalidCursor(
842                    "timestamp cursor must reference active aggregates in this scope".into(),
843                ));
844            }
845        }
846        AggregateQueryScope::ArchivedOnly => {
847            if cursor_index != CursorIndex::Archived {
848                return Err(EventError::InvalidCursor(
849                    "timestamp cursor must reference archived aggregates in this scope".into(),
850                ));
851            }
852        }
853        AggregateQueryScope::IncludeArchived => {}
854    }
855    Ok(())
856}
857
858#[derive(Debug, Clone, PartialEq, Eq)]
859pub struct EventCursor {
860    index: CursorIndex,
861    aggregate_type: String,
862    aggregate_id: String,
863    version: u64,
864}
865
866impl EventCursor {
867    pub fn new(
868        index: CursorIndex,
869        aggregate_type: impl Into<String>,
870        aggregate_id: impl Into<String>,
871        version: u64,
872    ) -> Self {
873        Self {
874            index,
875            aggregate_type: aggregate_type.into(),
876            aggregate_id: aggregate_id.into(),
877            version,
878        }
879    }
880
881    pub fn index(&self) -> CursorIndex {
882        self.index
883    }
884
885    pub fn aggregate_type(&self) -> &str {
886        &self.aggregate_type
887    }
888
889    pub fn aggregate_id(&self) -> &str {
890        &self.aggregate_id
891    }
892
893    pub fn version(&self) -> u64 {
894        self.version
895    }
896
897    pub fn encode(&self) -> String {
898        format!(
899            "{}:{}:{}:{}",
900            self.index.code(),
901            self.aggregate_type,
902            self.aggregate_id,
903            self.version
904        )
905    }
906}
907
908impl fmt::Display for EventCursor {
909    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
910        write!(
911            f,
912            "{}:{}:{}:{}",
913            self.index.code(),
914            self.aggregate_type,
915            self.aggregate_id,
916            self.version
917        )
918    }
919}
920
921impl std::str::FromStr for EventCursor {
922    type Err = EventError;
923
924    fn from_str(value: &str) -> Result<Self> {
925        let trimmed = value.trim();
926        if trimmed.is_empty() {
927            return Err(EventError::InvalidCursor("cursor cannot be empty".into()));
928        }
929        let mut segments = trimmed.split(':');
930        let Some(index_tag) = segments.next() else {
931            return Err(EventError::InvalidCursor("cursor is incomplete".into()));
932        };
933        let index = CursorIndex::from_str_tag(index_tag)?;
934        let aggregate_type = segments
935            .next()
936            .ok_or_else(|| EventError::InvalidCursor("cursor missing aggregate_type".into()))?;
937        if aggregate_type.is_empty() {
938            return Err(EventError::InvalidCursor(
939                "cursor aggregate_type cannot be empty".into(),
940            ));
941        }
942        let aggregate_id = segments
943            .next()
944            .ok_or_else(|| EventError::InvalidCursor("cursor missing aggregate_id".into()))?;
945        if aggregate_id.is_empty() {
946            return Err(EventError::InvalidCursor(
947                "cursor aggregate_id cannot be empty".into(),
948            ));
949        }
950        let version_str = segments
951            .next()
952            .ok_or_else(|| EventError::InvalidCursor("cursor missing version".into()))?;
953        if segments.next().is_some() {
954            return Err(EventError::InvalidCursor(
955                "cursor has too many segments".into(),
956            ));
957        }
958        let version = version_str.parse::<u64>().map_err(|_| {
959            EventError::InvalidCursor(format!("cursor version '{}' is not a number", version_str))
960        })?;
961
962        Ok(Self::new(index, aggregate_type, aggregate_id, version))
963    }
964}
965
966impl StoreCounts {
967    pub fn total_aggregates(&self) -> usize {
968        self.active_aggregates + self.archived_aggregates
969    }
970
971    pub fn total_events(&self) -> u64 {
972        self.active_events + self.archived_events
973    }
974}
975
976#[derive(Debug, Clone, Copy, PartialEq, Eq)]
977pub enum AggregateSortField {
978    AggregateType,
979    AggregateId,
980    Archived,
981    CreatedAt,
982    UpdatedAt,
983}
984
985#[derive(Debug, Clone, Copy, PartialEq, Eq)]
986pub struct AggregateSort {
987    pub field: AggregateSortField,
988    pub descending: bool,
989}
990
991#[derive(Debug, Clone, Copy, PartialEq, Eq)]
992pub enum AggregateQueryScope {
993    ActiveOnly,
994    ArchivedOnly,
995    IncludeArchived,
996}
997
998#[derive(Debug, Clone, Copy, PartialEq, Eq)]
999pub enum EventSortField {
1000    AggregateType,
1001    AggregateId,
1002    EventType,
1003    Version,
1004    EventId,
1005}
1006
1007#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1008pub struct EventSort {
1009    pub field: EventSortField,
1010    pub descending: bool,
1011}
1012
1013#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1014pub enum EventQueryScope<'a> {
1015    All,
1016    AggregateType(&'a str),
1017    Aggregate {
1018        aggregate_type: &'a str,
1019        aggregate_id: &'a str,
1020    },
1021}
1022
1023#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1024pub enum EventArchiveScope {
1025    ActiveOnly,
1026    ArchivedOnly,
1027    IncludeArchived,
1028}
1029
1030#[derive(Clone, Copy, PartialEq, Eq)]
1031enum AggregateIndex {
1032    Active,
1033    Archived,
1034}
1035
1036#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1037pub enum TimestampIndexKind {
1038    CreatedAt,
1039    UpdatedAt,
1040}
1041
1042impl From<AggregateIndex> for CursorIndex {
1043    fn from(value: AggregateIndex) -> Self {
1044        match value {
1045            AggregateIndex::Active => CursorIndex::Active,
1046            AggregateIndex::Archived => CursorIndex::Archived,
1047        }
1048    }
1049}
1050
1051impl From<CursorIndex> for AggregateIndex {
1052    fn from(value: CursorIndex) -> Self {
1053        match value {
1054            CursorIndex::Active => AggregateIndex::Active,
1055            CursorIndex::Archived => AggregateIndex::Archived,
1056        }
1057    }
1058}
1059
1060impl AggregateSortField {
1061    pub fn as_str(self) -> &'static str {
1062        match self {
1063            AggregateSortField::AggregateType => "aggregate_type",
1064            AggregateSortField::AggregateId => "aggregate_id",
1065            AggregateSortField::Archived => "archived",
1066            AggregateSortField::CreatedAt => "created_at",
1067            AggregateSortField::UpdatedAt => "updated_at",
1068        }
1069    }
1070
1071    pub fn compare(self, lhs: &AggregateState, rhs: &AggregateState) -> StdOrdering {
1072        match self {
1073            AggregateSortField::AggregateType => lhs.aggregate_type.cmp(&rhs.aggregate_type),
1074            AggregateSortField::AggregateId => lhs.aggregate_id.cmp(&rhs.aggregate_id),
1075            AggregateSortField::Archived => lhs.archived.cmp(&rhs.archived),
1076            AggregateSortField::CreatedAt => lhs.created_at.cmp(&rhs.created_at),
1077            AggregateSortField::UpdatedAt => lhs.updated_at.cmp(&rhs.updated_at),
1078        }
1079    }
1080}
1081
1082impl AggregateSort {
1083    pub fn compare(self, lhs: &AggregateState, rhs: &AggregateState) -> StdOrdering {
1084        let ordering = self.field.compare(lhs, rhs);
1085        if self.descending {
1086            ordering.reverse()
1087        } else {
1088            ordering
1089        }
1090    }
1091
1092    pub fn parse_directives(raw: &str) -> std::result::Result<Vec<Self>, String> {
1093        let trimmed = raw.trim();
1094        if trimmed.is_empty() {
1095            return Err("sort specification cannot be empty".to_string());
1096        }
1097
1098        let mut directives = Vec::new();
1099        for segment in trimmed.split(',') {
1100            let spec = segment.trim();
1101            if spec.is_empty() {
1102                return Err("sort segments cannot be empty".to_string());
1103            }
1104            directives.push(Self::parse_single(spec)?);
1105        }
1106
1107        Ok(directives)
1108    }
1109
1110    fn parse_single(spec: &str) -> std::result::Result<Self, String> {
1111        let mut parts = spec.split(':');
1112        let field_str = parts
1113            .next()
1114            .ok_or_else(|| "missing sort field".to_string())?
1115            .trim();
1116
1117        if field_str.is_empty() {
1118            return Err("sort field cannot be empty".to_string());
1119        }
1120
1121        let field = field_str.parse::<AggregateSortField>()?;
1122        let descending = match parts.next() {
1123            Some(order) => match order.trim().to_ascii_lowercase().as_str() {
1124                "asc" => false,
1125                "desc" => true,
1126                other => {
1127                    return Err(format!(
1128                        "invalid sort order '{other}' (expected 'asc' or 'desc')"
1129                    ));
1130                }
1131            },
1132            None => false,
1133        };
1134
1135        if parts.next().is_some() {
1136            return Err("sort specification contains too many ':' separators".to_string());
1137        }
1138
1139        Ok(Self { field, descending })
1140    }
1141}
1142
1143impl EventSortField {
1144    pub fn as_str(self) -> &'static str {
1145        match self {
1146            EventSortField::AggregateType => "aggregate_type",
1147            EventSortField::AggregateId => "aggregate_id",
1148            EventSortField::EventType => "event_type",
1149            EventSortField::Version => "version",
1150            EventSortField::EventId => "event_id",
1151        }
1152    }
1153
1154    pub fn compare(self, lhs: &EventRecord, rhs: &EventRecord) -> StdOrdering {
1155        match self {
1156            EventSortField::AggregateType => lhs.aggregate_type.cmp(&rhs.aggregate_type),
1157            EventSortField::AggregateId => lhs.aggregate_id.cmp(&rhs.aggregate_id),
1158            EventSortField::EventType => lhs.event_type.cmp(&rhs.event_type),
1159            EventSortField::Version => lhs.version.cmp(&rhs.version),
1160            EventSortField::EventId => lhs.metadata.event_id.cmp(&rhs.metadata.event_id),
1161        }
1162    }
1163}
1164
1165impl EventSort {
1166    pub fn compare(self, lhs: &EventRecord, rhs: &EventRecord) -> StdOrdering {
1167        let ordering = self.field.compare(lhs, rhs);
1168        if self.descending {
1169            ordering.reverse()
1170        } else {
1171            ordering
1172        }
1173    }
1174}
1175
1176impl std::str::FromStr for AggregateSortField {
1177    type Err = String;
1178
1179    fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
1180        let normalized = value.trim().to_ascii_lowercase();
1181        match normalized.as_str() {
1182            "aggregate_type" | "aggregatetype" => Ok(AggregateSortField::AggregateType),
1183            "aggregate_id" | "aggregateid" => Ok(AggregateSortField::AggregateId),
1184            "archived" => Ok(AggregateSortField::Archived),
1185            "created_at" | "createdat" => Ok(AggregateSortField::CreatedAt),
1186            "updated_at" | "updatedat" => Ok(AggregateSortField::UpdatedAt),
1187            _ => Err(format!("unsupported aggregate sort field '{value}'")),
1188        }
1189    }
1190}
1191
1192impl std::str::FromStr for EventSortField {
1193    type Err = String;
1194
1195    fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
1196        let normalized = value.trim().to_ascii_lowercase();
1197        match normalized.as_str() {
1198            "aggregate_type" | "aggregatetype" => Ok(EventSortField::AggregateType),
1199            "aggregate_id" | "aggregateid" => Ok(EventSortField::AggregateId),
1200            "event_type" | "eventtype" => Ok(EventSortField::EventType),
1201            "version" => Ok(EventSortField::Version),
1202            "event_id" | "eventid" => Ok(EventSortField::EventId),
1203            _ => Err(format!("unsupported event sort field '{value}'")),
1204        }
1205    }
1206}
1207
1208impl AggregateIndex {
1209    fn meta_prefix(self) -> &'static str {
1210        match self {
1211            AggregateIndex::Active => PREFIX_META,
1212            AggregateIndex::Archived => PREFIX_META_ARCHIVED,
1213        }
1214    }
1215
1216    fn state_prefix(self) -> &'static str {
1217        match self {
1218            AggregateIndex::Active => PREFIX_STATE,
1219            AggregateIndex::Archived => PREFIX_STATE_ARCHIVED,
1220        }
1221    }
1222
1223    fn event_prefix(self) -> &'static str {
1224        match self {
1225            AggregateIndex::Active => PREFIX_EVENT,
1226            AggregateIndex::Archived => PREFIX_EVENT_ARCHIVED,
1227        }
1228    }
1229}
1230
1231impl TimestampIndexKind {
1232    fn prefix(self, index: AggregateIndex) -> &'static str {
1233        match (self, index) {
1234            (TimestampIndexKind::CreatedAt, AggregateIndex::Active) => PREFIX_META_CREATED_INDEX,
1235            (TimestampIndexKind::CreatedAt, AggregateIndex::Archived) => {
1236                PREFIX_META_CREATED_INDEX_ARCHIVED
1237            }
1238            (TimestampIndexKind::UpdatedAt, AggregateIndex::Active) => PREFIX_META_UPDATED_INDEX,
1239            (TimestampIndexKind::UpdatedAt, AggregateIndex::Archived) => {
1240                PREFIX_META_UPDATED_INDEX_ARCHIVED
1241            }
1242        }
1243    }
1244
1245    pub fn as_str(self) -> &'static str {
1246        match self {
1247            TimestampIndexKind::CreatedAt => "created_at",
1248            TimestampIndexKind::UpdatedAt => "updated_at",
1249        }
1250    }
1251
1252    fn from_str_token(value: &str) -> Option<Self> {
1253        match value {
1254            "created_at" | "created" => Some(TimestampIndexKind::CreatedAt),
1255            "updated_at" | "updated" => Some(TimestampIndexKind::UpdatedAt),
1256            _ => None,
1257        }
1258    }
1259}
1260
1261#[derive(Debug)]
1262struct TimestampIndexEntry {
1263    aggregate_type: String,
1264    aggregate_id: String,
1265    timestamp_ms: i64,
1266}
1267
1268struct TimestampIndexIterator<'a> {
1269    iter: DBIterator<'a>,
1270    prefix: Vec<u8>,
1271    kind: TimestampIndexKind,
1272    index: AggregateIndex,
1273    finished: bool,
1274    had_error: bool,
1275    start_time: Instant,
1276}
1277
1278impl<'a> TimestampIndexIterator<'a> {
1279    fn new(
1280        store: &'a EventStore,
1281        kind: TimestampIndexKind,
1282        index: AggregateIndex,
1283        descending: bool,
1284    ) -> Self {
1285        let prefix = timestamp_index_prefix(kind, index);
1286        let mut start_key = prefix.clone();
1287        let direction = if descending {
1288            start_key.push(u8::MAX);
1289            Direction::Reverse
1290        } else {
1291            Direction::Forward
1292        };
1293        let iter = store
1294            .db
1295            .iterator(IteratorMode::From(start_key.as_slice(), direction));
1296        Self {
1297            iter,
1298            prefix,
1299            kind,
1300            index,
1301            finished: false,
1302            had_error: false,
1303            start_time: Instant::now(),
1304        }
1305    }
1306
1307    fn next_entry(&mut self) -> Option<TimestampIndexEntry> {
1308        if self.finished {
1309            return None;
1310        }
1311        while let Some(item) = self.iter.next() {
1312            let (key, _) = match item {
1313                Ok(pair) => pair,
1314                Err(_) => {
1315                    self.had_error = true;
1316                    continue;
1317                }
1318            };
1319            if !key.starts_with(self.prefix.as_slice()) {
1320                self.finished = true;
1321                break;
1322            }
1323            if let Some(entry) = parse_timestamp_index_entry(self.prefix.as_slice(), &key) {
1324                return Some(entry);
1325            }
1326        }
1327        None
1328    }
1329
1330    fn mark_error(&mut self) {
1331        self.had_error = true;
1332    }
1333}
1334
1335impl Drop for TimestampIndexIterator<'_> {
1336    fn drop(&mut self) {
1337        let duration = self.start_time.elapsed().as_secs_f64();
1338        let status = if self.had_error { "err" } else { "ok" };
1339        record_store_op(
1340            timestamp_index_metric(self.kind, self.index),
1341            status,
1342            duration,
1343        );
1344    }
1345}
1346
1347fn compare_aggregate_sort_keys(
1348    lhs: &AggregateState,
1349    rhs: &AggregateState,
1350    keys: &[AggregateSort],
1351) -> StdOrdering {
1352    for key in keys {
1353        let ordering = key.compare(lhs, rhs);
1354        if ordering != StdOrdering::Equal {
1355            return ordering;
1356        }
1357    }
1358    StdOrdering::Equal
1359}
1360
1361fn compare_event_sort_keys(
1362    lhs: &EventRecord,
1363    rhs: &EventRecord,
1364    keys: &[EventSort],
1365) -> StdOrdering {
1366    for key in keys {
1367        let ordering = key.compare(lhs, rhs);
1368        if ordering != StdOrdering::Equal {
1369            return ordering;
1370        }
1371    }
1372    StdOrdering::Equal
1373}
1374
1375fn aggregate_sort_matches_key_order(keys: &[AggregateSort]) -> bool {
1376    if keys.is_empty() {
1377        return true;
1378    }
1379    const NATURAL_ORDER: [AggregateSortField; 2] = [
1380        AggregateSortField::AggregateType,
1381        AggregateSortField::AggregateId,
1382    ];
1383    keys.iter().enumerate().all(|(idx, key)| {
1384        !key.descending
1385            && NATURAL_ORDER
1386                .get(idx)
1387                .map(|field| key.field == *field)
1388                .unwrap_or(false)
1389    })
1390}
1391
1392pub fn timestamp_sort_hint(keys: &[AggregateSort]) -> Option<(TimestampIndexKind, bool)> {
1393    if keys.len() != 1 {
1394        return None;
1395    }
1396    let directive = keys[0];
1397    let kind = match directive.field {
1398        AggregateSortField::CreatedAt => TimestampIndexKind::CreatedAt,
1399        AggregateSortField::UpdatedAt => TimestampIndexKind::UpdatedAt,
1400        _ => return None,
1401    };
1402    Some((kind, directive.descending))
1403}
1404
1405fn event_sort_matches_key_order(keys: &[EventSort]) -> bool {
1406    if keys.is_empty() {
1407        return true;
1408    }
1409    const NATURAL_ORDER: [EventSortField; 3] = [
1410        EventSortField::AggregateType,
1411        EventSortField::AggregateId,
1412        EventSortField::Version,
1413    ];
1414    keys.iter().enumerate().all(|(idx, key)| {
1415        !key.descending
1416            && NATURAL_ORDER
1417                .get(idx)
1418                .map(|field| key.field == *field)
1419                .unwrap_or(false)
1420    })
1421}
1422
1423fn is_false(value: &bool) -> bool {
1424    !*value
1425}
1426
1427#[derive(Debug, Clone, Serialize, Deserialize)]
1428struct AggregateMeta {
1429    aggregate_type: String,
1430    aggregate_id: String,
1431    version: u64,
1432    event_hashes: Vec<String>,
1433    merkle_root: String,
1434    #[serde(default)]
1435    archived: bool,
1436    #[serde(default)]
1437    archived_at: Option<DateTime<Utc>>,
1438    #[serde(default)]
1439    archive_comment: Option<String>,
1440    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1441    extensions: BTreeMap<String, Value>,
1442    #[serde(default, skip_serializing_if = "Option::is_none")]
1443    created_at: Option<DateTime<Utc>>,
1444    #[serde(default, skip_serializing_if = "Option::is_none")]
1445    updated_at: Option<DateTime<Utc>>,
1446}
1447
1448impl AggregateMeta {
1449    fn new(aggregate_type: String, aggregate_id: String) -> Self {
1450        Self {
1451            aggregate_type,
1452            aggregate_id,
1453            version: 0,
1454            event_hashes: Vec::new(),
1455            merkle_root: empty_root(),
1456            archived: false,
1457            archived_at: None,
1458            archive_comment: None,
1459            extensions: BTreeMap::new(),
1460            created_at: None,
1461            updated_at: None,
1462        }
1463    }
1464
1465    fn record_write(&mut self, event_time: DateTime<Utc>) {
1466        if self.version == 0 && self.created_at.is_none() {
1467            self.created_at = Some(event_time);
1468        }
1469        self.updated_at = Some(event_time);
1470    }
1471}
1472
1473#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1474struct AggregateKey {
1475    aggregate_type: String,
1476    aggregate_id: String,
1477}
1478
1479impl AggregateKey {
1480    fn new(aggregate_type: impl Into<String>, aggregate_id: impl Into<String>) -> Self {
1481        Self {
1482            aggregate_type: aggregate_type.into(),
1483            aggregate_id: aggregate_id.into(),
1484        }
1485    }
1486
1487    fn parts(&self) -> (&str, &str) {
1488        (&self.aggregate_type, &self.aggregate_id)
1489    }
1490}
1491
1492struct PendingEvent {
1493    key: Vec<u8>,
1494    value: Vec<u8>,
1495}
1496
1497struct PendingRefUpdate {
1498    tenant: String,
1499    aggregate_type: String,
1500    aggregate_id: String,
1501    references: Vec<(String, String)>,
1502}
1503
1504#[derive(Debug, Clone, Serialize, Deserialize)]
1505struct StoredReferencePath {
1506    target: String,
1507    path: String,
1508}
1509
1510#[derive(Debug, Clone, Serialize, Deserialize)]
1511struct StoredReferrer {
1512    aggregate_type: String,
1513    aggregate_id: String,
1514    path: String,
1515}
1516
1517pub struct EventStore {
1518    db: DBWithThreadMode<MultiThreaded>,
1519    write_lock: Mutex<()>,
1520    read_only: bool,
1521    encryptor: Option<Encryptor>,
1522    id_generator: Mutex<SnowflakeGenerator>,
1523}
1524
1525pub struct Transaction<'a> {
1526    store: &'a EventStore,
1527    _guard: MutexGuard<'a, ()>,
1528    pending_events: Vec<PendingEvent>,
1529    pending_records: Vec<EventRecord>,
1530    pending_refs: Vec<PendingRefUpdate>,
1531    meta_cache: BTreeMap<AggregateKey, AggregateMeta>,
1532    state_cache: BTreeMap<AggregateKey, BTreeMap<String, String>>,
1533}
1534
1535#[derive(Debug, Clone, Serialize, Deserialize)]
1536pub struct SnapshotRecord {
1537    #[serde(default, skip_serializing_if = "Option::is_none")]
1538    pub snapshot_id: Option<SnowflakeId>,
1539    pub aggregate_type: String,
1540    pub aggregate_id: String,
1541    pub version: u64,
1542    pub state: BTreeMap<String, String>,
1543    pub merkle_root: String,
1544    pub created_at: DateTime<Utc>,
1545    pub comment: Option<String>,
1546}
1547
1548#[derive(Debug, Clone)]
1549pub struct AggregatePositionEntry {
1550    pub aggregate_type: String,
1551    pub aggregate_id: String,
1552    pub version: u64,
1553}
1554
1555#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1556enum LockStatus {
1557    NotLocked,
1558    LockedBy(Option<u32>),
1559    Unknown,
1560}
1561
1562fn open_db_with_lock_handling(
1563    path: &Path,
1564    options: &Options,
1565    read_only: bool,
1566) -> Result<DBWithThreadMode<MultiThreaded>> {
1567    let lock_path = path.join("LOCK");
1568    let mut last_error = None;
1569
1570    for attempt in 0..=LOCK_RETRY_ATTEMPTS {
1571        match try_open_db(path, options, read_only) {
1572            Ok(db) => return Ok(db),
1573            Err(err) => {
1574                let message = err.to_string();
1575                last_error = Some(message.clone());
1576                if !is_lock_error_message(&message) {
1577                    return Err(EventError::Storage(message));
1578                }
1579
1580                let status = lock_status(&lock_path)?;
1581                match status {
1582                    LockStatus::NotLocked => {
1583                        cleanup_stale_lock(&lock_path);
1584                    }
1585                    LockStatus::LockedBy(pid_opt) => {
1586                        if pid_opt.map_or(false, pid_running) {
1587                            if attempt == LOCK_RETRY_ATTEMPTS {
1588                                return Err(EventError::Storage(store_in_use_message(
1589                                    path, pid_opt,
1590                                )));
1591                            }
1592                        } else {
1593                            cleanup_stale_lock(&lock_path);
1594                        }
1595                    }
1596                    LockStatus::Unknown => {
1597                        if attempt == LOCK_RETRY_ATTEMPTS {
1598                            return Err(EventError::Storage(store_in_use_message(path, None)));
1599                        }
1600                    }
1601                }
1602            }
1603        }
1604
1605        if attempt < LOCK_RETRY_ATTEMPTS {
1606            thread::sleep(LOCK_RETRY_BACKOFF);
1607        }
1608    }
1609
1610    Err(EventError::Storage(
1611        last_error.unwrap_or_else(|| "failed to open event store".into()),
1612    ))
1613}
1614
1615fn try_open_db(
1616    path: &Path,
1617    options: &Options,
1618    read_only: bool,
1619) -> std::result::Result<DBWithThreadMode<MultiThreaded>, rocksdb::Error> {
1620    if read_only {
1621        DBWithThreadMode::<MultiThreaded>::open_for_read_only(options, path, false)
1622    } else {
1623        DBWithThreadMode::<MultiThreaded>::open(options, path)
1624    }
1625}
1626
1627fn lock_status(lock_path: &Path) -> io::Result<LockStatus> {
1628    if !lock_path.exists() {
1629        return Ok(LockStatus::NotLocked);
1630    }
1631
1632    #[cfg(unix)]
1633    {
1634        let file = std::fs::File::open(lock_path)?;
1635        let mut flock = libc::flock {
1636            l_type: libc::F_WRLCK as libc::c_short,
1637            l_whence: libc::SEEK_SET as libc::c_short,
1638            l_start: 0,
1639            l_len: 0,
1640            l_pid: 0,
1641        };
1642        let status = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETLK, &mut flock) };
1643        if status == -1 {
1644            return Ok(LockStatus::Unknown);
1645        }
1646        if flock.l_type == libc::F_UNLCK as libc::c_short {
1647            return Ok(LockStatus::NotLocked);
1648        }
1649        return Ok(LockStatus::LockedBy(Some(flock.l_pid as u32)));
1650    }
1651
1652    #[cfg(not(unix))]
1653    {
1654        let _ = lock_path;
1655        Ok(LockStatus::Unknown)
1656    }
1657}
1658
1659fn pid_running(pid: u32) -> bool {
1660    #[cfg(unix)]
1661    {
1662        let result = unsafe { libc::kill(pid as libc::pid_t, 0) };
1663        if result == 0 {
1664            return true;
1665        }
1666        if let Some(errno) = io::Error::last_os_error().raw_os_error() {
1667            return errno != libc::ESRCH;
1668        }
1669        return false;
1670    }
1671
1672    #[cfg(windows)]
1673    {
1674        use windows_sys::Win32::{
1675            Foundation::{CloseHandle, STILL_ACTIVE},
1676            System::Threading::{
1677                GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
1678            },
1679        };
1680
1681        unsafe {
1682            let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
1683            if handle == 0 {
1684                return false;
1685            }
1686            let mut exit_code = 0;
1687            let success = GetExitCodeProcess(handle, &mut exit_code);
1688            let _ = CloseHandle(handle);
1689            success != 0 && exit_code == STILL_ACTIVE as u32
1690        }
1691    }
1692
1693    #[cfg(not(any(unix, windows)))]
1694    {
1695        let _ = pid;
1696        true
1697    }
1698}
1699
1700fn cleanup_stale_lock(lock_path: &Path) {
1701    if let Err(err) = fs::remove_file(lock_path) {
1702        if err.kind() != io::ErrorKind::NotFound {
1703            warn!(
1704                "failed to remove stale lock file {}: {}",
1705                lock_path.display(),
1706                err
1707            );
1708        }
1709    } else {
1710        warn!(
1711            "removed stale event store lock file at {}",
1712            lock_path.display()
1713        );
1714    }
1715}
1716
1717fn store_in_use_message(path: &Path, pid: Option<u32>) -> String {
1718    match pid {
1719        Some(pid) => format!(
1720            "event store lock at {} is held by process {pid}; stop the running dbx instance and retry",
1721            path.display()
1722        ),
1723        None => format!(
1724            "event store at {} is currently in use; stop the running dbx instance and retry",
1725            path.display()
1726        ),
1727    }
1728}
1729
1730pub fn is_lock_error_message(message: &str) -> bool {
1731    let lower = message.to_ascii_lowercase();
1732    lower.contains("lock file")
1733        || lower.contains("resource temporarily unavailable")
1734        || lower.contains("lock is held")
1735        || lower.contains("is held by process")
1736        || lower.contains("lock hold")
1737        || lower.contains("currently in use")
1738        || lower.contains("no locks available")
1739}
1740
1741impl EventStore {
1742    pub fn open(path: PathBuf, encryptor: Option<Encryptor>, worker_id: u16) -> Result<Self> {
1743        let mut options = Options::default();
1744        options.create_if_missing(true);
1745        configure_db_options(&mut options, false);
1746        let db = open_db_with_lock_handling(&path, &options, false)?;
1747
1748        if worker_id > MAX_WORKER_ID {
1749            return Err(EventError::Config(format!(
1750                "snowflake worker id {} exceeds maximum {}",
1751                worker_id, MAX_WORKER_ID
1752            )));
1753        }
1754
1755        Ok(Self {
1756            db,
1757            write_lock: Mutex::new(()),
1758            read_only: false,
1759            encryptor,
1760            id_generator: Mutex::new(SnowflakeGenerator::new(worker_id)),
1761        })
1762    }
1763
1764    pub fn open_read_only(path: PathBuf, encryptor: Option<Encryptor>) -> Result<Self> {
1765        let mut options = Options::default();
1766        options.create_if_missing(false);
1767        configure_db_options(&mut options, true);
1768        let db = open_db_with_lock_handling(&path, &options, true)?;
1769
1770        Ok(Self {
1771            db,
1772            write_lock: Mutex::new(()),
1773            read_only: true,
1774            encryptor,
1775            id_generator: Mutex::new(SnowflakeGenerator::new(0)),
1776        })
1777    }
1778
1779    pub fn storage_usage_bytes(&self) -> Result<u64> {
1780        let sst = self.rocksdb_property_bytes("rocksdb.total-sst-files-size")?;
1781        let mem = self.rocksdb_property_bytes("rocksdb.size-all-mem-tables")?;
1782        let wal = self.rocksdb_property_bytes("rocksdb.live-wal-file-size")?;
1783        Ok(sst.saturating_add(mem).saturating_add(wal))
1784    }
1785
1786    fn rocksdb_property_bytes(&self, name: &str) -> Result<u64> {
1787        match self.db.property_int_value(name) {
1788            Ok(Some(value)) => Ok(value),
1789            Ok(None) => Ok(0),
1790            Err(err) => Err(EventError::Storage(format!(
1791                "failed to read RocksDB property '{name}': {err}"
1792            ))),
1793        }
1794    }
1795
1796    pub fn counts(&self) -> Result<StoreCounts> {
1797        let (active_aggregates, active_events) =
1798            self.count_index_entries(AggregateIndex::Active)?;
1799        let (archived_aggregates, archived_events) =
1800            self.count_index_entries(AggregateIndex::Archived)?;
1801
1802        Ok(StoreCounts {
1803            active_aggregates,
1804            archived_aggregates,
1805            active_events,
1806            archived_events,
1807        })
1808    }
1809
1810    fn count_index_entries(&self, index: AggregateIndex) -> Result<(usize, u64)> {
1811        let start = Instant::now();
1812        let metric = match index {
1813            AggregateIndex::Active => "rocksdb_iter_meta_count",
1814            AggregateIndex::Archived => "rocksdb_iter_meta_archived_count",
1815        };
1816
1817        let result = (|| {
1818            let prefix = meta_prefix_for(index);
1819            let iter = self
1820                .db
1821                .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
1822
1823            let mut aggregates = 0usize;
1824            let mut events = 0u64;
1825
1826            for item in iter {
1827                let (key, value) = item.map_err(|err| EventError::Storage(err.to_string()))?;
1828                if !key.starts_with(prefix.as_slice()) {
1829                    break;
1830                }
1831                if key.len() > prefix.len() && key[prefix.len()] != SEP {
1832                    break;
1833                }
1834
1835                let meta: AggregateMeta = serde_json::from_slice(&value).map_err(|err| {
1836                    EventError::Storage(format!("failed to deserialize aggregate metadata: {err}"))
1837                })?;
1838                aggregates += 1;
1839                events += meta.version;
1840            }
1841
1842            Ok((aggregates, events))
1843        })();
1844
1845        let duration = start.elapsed().as_secs_f64();
1846        record_store_op(metric, if result.is_ok() { "ok" } else { "err" }, duration);
1847        result
1848    }
1849
1850    fn next_event_id(&self) -> SnowflakeId {
1851        self.next_snowflake_id()
1852    }
1853
1854    fn next_snowflake_id(&self) -> SnowflakeId {
1855        let mut guard = self.id_generator.lock();
1856        guard.next_id()
1857    }
1858
1859    pub fn prepare_payload_from_patch(
1860        &self,
1861        aggregate_type: &str,
1862        aggregate_id: &str,
1863        patch_value: &Value,
1864    ) -> Result<Value> {
1865        if !patch_value.is_array() {
1866            return Err(EventError::InvalidSchema(
1867                "patch must be provided as a JSON array".into(),
1868            ));
1869        }
1870
1871        let top_level_keys =
1872            collect_top_level_keys(patch_value).map_err(EventError::InvalidSchema)?;
1873        if top_level_keys.is_empty() {
1874            return Err(EventError::InvalidSchema(
1875                "patch must modify at least one path".into(),
1876            ));
1877        }
1878
1879        let patch: Patch = serde_json::from_value(patch_value.clone())
1880            .map_err(|err| EventError::InvalidSchema(format!("failed to parse patch: {}", err)))?;
1881
1882        let state_map = self.load_state_map(aggregate_type, aggregate_id)?;
1883        let mut document = state_map_to_value(&state_map);
1884        if !document.is_object() {
1885            document = Value::Object(serde_json::Map::new());
1886        }
1887
1888        json_patch::patch(&mut document, &patch)
1889            .map_err(|err| EventError::InvalidSchema(format!("failed to apply patch: {}", err)))?;
1890
1891        let document_object = document.as_object().ok_or_else(|| {
1892            EventError::InvalidSchema("patched document must be a JSON object".into())
1893        })?;
1894
1895        let mut payload = serde_json::Map::new();
1896        for key in top_level_keys {
1897            let value = document_object.get(&key).ok_or_else(|| {
1898                EventError::InvalidSchema(format!(
1899                    "patch removed key '{}' and removals are not supported",
1900                    key
1901                ))
1902            })?;
1903            payload.insert(key, value.clone());
1904        }
1905
1906        Ok(Value::Object(payload))
1907    }
1908
1909    pub fn append(&self, input: AppendEvent) -> Result<EventRecord> {
1910        self.ensure_writable()?;
1911        let _guard = self.write_lock.lock();
1912
1913        let tenant = input.tenant.clone();
1914        let references = input.reference_targets.clone();
1915        if let Some(note) = input.note.as_ref() {
1916            if note.chars().count() > MAX_EVENT_NOTE_LENGTH {
1917                return Err(EventError::InvalidSchema(format!(
1918                    "event note cannot exceed {} characters",
1919                    MAX_EVENT_NOTE_LENGTH
1920                )));
1921            }
1922        }
1923        ensure_event_type_lengths(&input.event_type, input.event_type_raw.as_ref())?;
1924
1925        let aggregate_type = input.aggregate_type.clone();
1926        let aggregate_id = input.aggregate_id.clone();
1927        let event_id = self.next_event_id();
1928
1929        let (mut meta, mut state, previous_meta) =
1930            match self.load_meta_any(&aggregate_type, &aggregate_id)? {
1931                Some((_, AggregateIndex::Archived)) => {
1932                    return Err(EventError::AggregateArchived);
1933                }
1934                Some((meta, AggregateIndex::Active)) => {
1935                    let previous = meta.clone();
1936                    (
1937                        meta,
1938                        self.load_state_map_from(
1939                            AggregateIndex::Active,
1940                            &aggregate_type,
1941                            &aggregate_id,
1942                        )?,
1943                        Some(previous),
1944                    )
1945                }
1946                None => (
1947                    AggregateMeta::new(aggregate_type.clone(), aggregate_id.clone()),
1948                    BTreeMap::new(),
1949                    None,
1950                ),
1951            };
1952        let record = apply_event(&mut meta, &mut state, input, event_id);
1953        let stored_record = self.encode_record(&record)?;
1954        let encoded_state = self.encode_state_map(&state)?;
1955
1956        let mut batch = WriteBatch::default();
1957        batch_put(
1958            &mut batch,
1959            event_key(&record.aggregate_type, &record.aggregate_id, record.version),
1960            serde_json::to_vec(&stored_record)?,
1961        )?;
1962        batch_put(
1963            &mut batch,
1964            meta_key(&record.aggregate_type, &record.aggregate_id),
1965            serde_json::to_vec(&meta)?,
1966        )?;
1967        batch_put(
1968            &mut batch,
1969            state_key(&record.aggregate_type, &record.aggregate_id),
1970            encoded_state,
1971        )?;
1972        update_reference_index_inner(
1973            &self.db,
1974            &mut batch,
1975            &tenant,
1976            &record.aggregate_type,
1977            &record.aggregate_id,
1978            &references,
1979        )?;
1980        self.sync_timestamp_indexes(
1981            &mut batch,
1982            AggregateIndex::Active,
1983            previous_meta.as_ref(),
1984            Some(&meta),
1985        );
1986
1987        self.write_batch(batch)?;
1988
1989        Ok(record)
1990    }
1991
1992    pub fn append_imported_event(&self, record: EventRecord) -> Result<()> {
1993        self.ensure_writable()?;
1994        let _guard = self.write_lock.lock();
1995
1996        let aggregate_type = record.aggregate_type.clone();
1997        let aggregate_id = record.aggregate_id.clone();
1998
1999        let (mut meta, mut state, previous_meta) =
2000            match self.load_meta_any(&aggregate_type, &aggregate_id)? {
2001                Some((_, AggregateIndex::Archived)) => {
2002                    return Err(EventError::AggregateArchived);
2003                }
2004                Some((meta, AggregateIndex::Active)) => {
2005                    let previous = meta.clone();
2006                    (
2007                        meta,
2008                        self.load_state_map_from(
2009                            AggregateIndex::Active,
2010                            &aggregate_type,
2011                            &aggregate_id,
2012                        )?,
2013                        Some(previous),
2014                    )
2015                }
2016                None => (
2017                    AggregateMeta::new(aggregate_type.clone(), aggregate_id.clone()),
2018                    BTreeMap::new(),
2019                    None,
2020                ),
2021            };
2022
2023        if meta.version + 1 != record.version {
2024            return Err(EventError::Storage(format!(
2025                "imported event version mismatch for {}::{} (expected {}, found {})",
2026                aggregate_type,
2027                aggregate_id,
2028                meta.version + 1,
2029                record.version
2030            )));
2031        }
2032        let payload_map = payload_to_map(&record.payload);
2033
2034        let computed_hash = hash_event(
2035            &aggregate_type,
2036            &aggregate_id,
2037            record.version,
2038            &record.event_type,
2039            &payload_map,
2040        );
2041
2042        if computed_hash != record.hash {
2043            return Err(EventError::Storage(format!(
2044                "imported event hash mismatch for {}::{} (expected {}, found {})",
2045                aggregate_type, aggregate_id, computed_hash, record.hash
2046            )));
2047        }
2048
2049        for (key, value) in payload_map {
2050            state.insert(key, value);
2051        }
2052
2053        if let Some(metadata) = record.extensions.as_ref() {
2054            let incoming = metadata_to_extensions_map(metadata);
2055            for (key, value) in incoming {
2056                meta.extensions.insert(key, value);
2057            }
2058        }
2059
2060        let record_created_at = record.metadata.created_at;
2061        meta.record_write(record_created_at);
2062        meta.event_hashes.push(record.hash.clone());
2063        meta.version = record.version;
2064        meta.merkle_root = compute_merkle_root(&meta.event_hashes);
2065
2066        if meta.merkle_root != record.merkle_root {
2067            return Err(EventError::Storage(format!(
2068                "imported event merkle mismatch for {}::{} (expected {}, found {})",
2069                aggregate_type, aggregate_id, meta.merkle_root, record.merkle_root
2070            )));
2071        }
2072
2073        let stored_record = self.encode_record(&record)?;
2074
2075        let mut batch = WriteBatch::default();
2076        batch_put(
2077            &mut batch,
2078            event_key(&aggregate_type, &aggregate_id, record.version),
2079            serde_json::to_vec(&stored_record)?,
2080        )?;
2081        self.sync_timestamp_indexes(
2082            &mut batch,
2083            AggregateIndex::Active,
2084            previous_meta.as_ref(),
2085            Some(&meta),
2086        );
2087        batch_put(
2088            &mut batch,
2089            meta_key(&aggregate_type, &aggregate_id),
2090            serde_json::to_vec(&meta)?,
2091        )?;
2092        batch_put(
2093            &mut batch,
2094            state_key(&aggregate_type, &aggregate_id),
2095            self.encode_state_map(&state)?,
2096        )?;
2097
2098        self.write_batch(batch)?;
2099        Ok(())
2100    }
2101
2102    pub fn transaction(&self) -> Result<Transaction<'_>> {
2103        self.ensure_writable()?;
2104        let guard = self.write_lock.lock();
2105        Ok(Transaction {
2106            store: self,
2107            _guard: guard,
2108            pending_events: Vec::new(),
2109            pending_records: Vec::new(),
2110            pending_refs: Vec::new(),
2111            meta_cache: BTreeMap::new(),
2112            state_cache: BTreeMap::new(),
2113        })
2114    }
2115
2116    pub fn get_aggregate_state(
2117        &self,
2118        aggregate_type: &str,
2119        aggregate_id: &str,
2120    ) -> Result<AggregateState> {
2121        let (meta, index) = self
2122            .load_meta_any(aggregate_type, aggregate_id)?
2123            .ok_or(EventError::AggregateNotFound)?;
2124        let state = self.load_state_map_from(index, aggregate_type, aggregate_id)?;
2125
2126        let AggregateMeta {
2127            aggregate_type,
2128            aggregate_id,
2129            version,
2130            merkle_root,
2131            archived,
2132            created_at,
2133            updated_at,
2134            extensions,
2135            ..
2136        } = meta;
2137
2138        Ok(AggregateState {
2139            aggregate_type,
2140            aggregate_id,
2141            version,
2142            state,
2143            extensions,
2144            merkle_root,
2145            created_at,
2146            updated_at,
2147            archived,
2148        })
2149    }
2150
2151    pub fn parse_timestamp_cursor(
2152        &self,
2153        raw: &str,
2154        kind: TimestampIndexKind,
2155        descending: bool,
2156    ) -> Result<AggregateCursor> {
2157        if let Some(cursor) = self.try_shorthand_timestamp_cursor(raw, kind, descending)? {
2158            return Ok(cursor);
2159        }
2160
2161        AggregateCursor::from_str(raw)
2162    }
2163
2164    fn try_shorthand_timestamp_cursor(
2165        &self,
2166        raw: &str,
2167        kind: TimestampIndexKind,
2168        descending: bool,
2169    ) -> Result<Option<AggregateCursor>> {
2170        let parts: Vec<_> = raw.trim().split(':').collect();
2171        if parts.len() != 3 || parts[0] != "ts" {
2172            return Ok(None);
2173        }
2174        let aggregate_type = parts[1];
2175        let aggregate_id = parts[2];
2176        if aggregate_type.is_empty() {
2177            return Err(EventError::InvalidCursor(
2178                "timestamp cursor aggregate_type cannot be empty".into(),
2179            ));
2180        }
2181        if aggregate_id.is_empty() {
2182            return Err(EventError::InvalidCursor(
2183                "timestamp cursor aggregate_id cannot be empty".into(),
2184            ));
2185        }
2186
2187        let aggregate = match self.get_aggregate_state(aggregate_type, aggregate_id) {
2188            Ok(aggregate) => aggregate,
2189            Err(EventError::AggregateNotFound) => {
2190                return Err(EventError::InvalidCursor(format!(
2191                    "cursor references unknown aggregate '{}:{}'",
2192                    aggregate_type, aggregate_id
2193                )));
2194            }
2195            Err(err) => return Err(err),
2196        };
2197        let timestamp = match kind {
2198            TimestampIndexKind::CreatedAt => aggregate.created_at,
2199            TimestampIndexKind::UpdatedAt => aggregate.updated_at,
2200        }
2201        .ok_or_else(|| {
2202            EventError::InvalidCursor(format!(
2203                "aggregate {}:{} missing {} timestamp",
2204                aggregate_type,
2205                aggregate_id,
2206                match kind {
2207                    TimestampIndexKind::CreatedAt => "created_at",
2208                    TimestampIndexKind::UpdatedAt => "updated_at",
2209                }
2210            ))
2211        })?;
2212
2213        let index = if aggregate.archived {
2214            CursorIndex::Archived
2215        } else {
2216            CursorIndex::Active
2217        };
2218
2219        Ok(Some(AggregateCursor::new_timestamp(
2220            index,
2221            aggregate.aggregate_type,
2222            aggregate.aggregate_id,
2223            kind,
2224            descending,
2225            timestamp.timestamp_millis(),
2226        )))
2227    }
2228
2229    pub fn list_events(
2230        &self,
2231        aggregate_type: &str,
2232        aggregate_id: &str,
2233    ) -> Result<Vec<EventRecord>> {
2234        self.list_events_paginated(aggregate_type, aggregate_id, 0, None)
2235    }
2236
2237    pub fn merkle_root_at(
2238        &self,
2239        aggregate_type: &str,
2240        aggregate_id: &str,
2241        version: u64,
2242    ) -> Result<Option<String>> {
2243        if version == 0 {
2244            return Ok(Some(empty_root()));
2245        }
2246        let Some((meta, _)) = self.load_meta_any(aggregate_type, aggregate_id)? else {
2247            return Ok(None);
2248        };
2249        if version > meta.version {
2250            return Ok(None);
2251        }
2252        if version == meta.version {
2253            return Ok(Some(meta.merkle_root.clone()));
2254        }
2255        let end = version as usize;
2256        let hashes = &meta.event_hashes[..end];
2257        Ok(Some(compute_merkle_root(hashes)))
2258    }
2259
2260    pub fn event_hashes(
2261        &self,
2262        aggregate_type: &str,
2263        aggregate_id: &str,
2264    ) -> Result<Option<Vec<String>>> {
2265        let Some((meta, _)) = self.load_meta_any(aggregate_type, aggregate_id)? else {
2266            return Ok(None);
2267        };
2268        Ok(Some(meta.event_hashes.clone()))
2269    }
2270
2271    pub fn event_by_version(
2272        &self,
2273        aggregate_type: &str,
2274        aggregate_id: &str,
2275        version: u64,
2276    ) -> Result<Option<EventRecord>> {
2277        if version == 0 {
2278            return Ok(None);
2279        }
2280        let (meta, index) = match self.load_meta_any(aggregate_type, aggregate_id)? {
2281            Some((meta, idx)) => (meta, idx),
2282            None => return Ok(None),
2283        };
2284        if version > meta.version {
2285            return Ok(None);
2286        }
2287
2288        let key = event_key_for(index, aggregate_type, aggregate_id, version);
2289        let value = match self
2290            .db
2291            .get(key)
2292            .map_err(|err| EventError::Storage(err.to_string()))?
2293        {
2294            Some(bytes) => bytes,
2295            None => return Ok(None),
2296        };
2297        let stored: EventRecord = serde_json::from_slice(&value)?;
2298        let decoded = self.decode_record(stored)?;
2299        Ok(Some(decoded))
2300    }
2301
2302    pub fn list_events_paginated(
2303        &self,
2304        aggregate_type: &str,
2305        aggregate_id: &str,
2306        skip: usize,
2307        take: Option<usize>,
2308    ) -> Result<Vec<EventRecord>> {
2309        match self.aggregate_version(aggregate_type, aggregate_id)? {
2310            Some(_) => {}
2311            None => return Err(EventError::AggregateNotFound),
2312        }
2313
2314        if matches!(take, Some(0)) {
2315            return Ok(Vec::new());
2316        }
2317
2318        let from_version = skip as u64;
2319        self.events_after(aggregate_type, aggregate_id, from_version, take)
2320    }
2321
2322    pub fn events_after(
2323        &self,
2324        aggregate_type: &str,
2325        aggregate_id: &str,
2326        from_version: u64,
2327        limit: Option<usize>,
2328    ) -> Result<Vec<EventRecord>> {
2329        let start = Instant::now();
2330        let result = (|| {
2331            let (_, index) = self
2332                .load_meta_any(aggregate_type, aggregate_id)?
2333                .ok_or(EventError::AggregateNotFound)?;
2334            let start_key = event_key_for(index, aggregate_type, aggregate_id, from_version + 1);
2335            let prefix = event_prefix_for(index, aggregate_type, aggregate_id);
2336            let iter = self
2337                .db
2338                .iterator(IteratorMode::From(start_key.as_slice(), Direction::Forward));
2339
2340            let mut events = Vec::new();
2341            for item in iter {
2342                let (key, value) = item.map_err(|err| EventError::Storage(err.to_string()))?;
2343                if !key.starts_with(prefix.as_slice()) {
2344                    break;
2345                }
2346                let record: EventRecord = serde_json::from_slice(&value)?;
2347                let record = self.decode_record(record)?;
2348                if record.version <= from_version {
2349                    continue;
2350                }
2351                events.push(record);
2352                if let Some(limit) = limit {
2353                    if events.len() >= limit {
2354                        break;
2355                    }
2356                }
2357            }
2358
2359            Ok(events)
2360        })();
2361        let duration = start.elapsed().as_secs_f64();
2362        record_store_op(
2363            "rocksdb_iter_events",
2364            if result.is_ok() { "ok" } else { "err" },
2365            duration,
2366        );
2367        result
2368    }
2369
2370    pub fn events_paginated(
2371        &self,
2372        scope: EventQueryScope<'_>,
2373        archive_scope: EventArchiveScope,
2374        skip: usize,
2375        take: Option<usize>,
2376        sort: Option<&[EventSort]>,
2377        filter: Option<&FilterExpr>,
2378    ) -> Result<Vec<EventRecord>> {
2379        match archive_scope {
2380            EventArchiveScope::ActiveOnly => Ok(self.collect_events_paginated(
2381                AggregateIndex::Active,
2382                scope,
2383                skip,
2384                take,
2385                sort,
2386                filter,
2387            )),
2388            EventArchiveScope::ArchivedOnly => Ok(self.collect_events_paginated(
2389                AggregateIndex::Archived,
2390                scope,
2391                skip,
2392                take,
2393                sort,
2394                filter,
2395            )),
2396            EventArchiveScope::IncludeArchived => {
2397                let mut active = self.collect_events_paginated(
2398                    AggregateIndex::Active,
2399                    scope,
2400                    0,
2401                    None,
2402                    None,
2403                    filter,
2404                );
2405                let mut archived = self.collect_events_paginated(
2406                    AggregateIndex::Archived,
2407                    scope,
2408                    0,
2409                    None,
2410                    None,
2411                    filter,
2412                );
2413                active.append(&mut archived);
2414
2415                if let Some(keys) = sort {
2416                    active.sort_by(|a, b| compare_event_sort_keys(a, b, keys));
2417                }
2418
2419                let total = active.len();
2420                let start = skip.min(total);
2421                let end = if let Some(limit) = take {
2422                    start.saturating_add(limit).min(total)
2423                } else {
2424                    total
2425                };
2426                Ok(active[start..end].to_vec())
2427            }
2428        }
2429    }
2430
2431    pub fn find_event_by_id(&self, event_id: SnowflakeId) -> Result<Option<EventRecord>> {
2432        if let Some(event) = self.find_event_by_id_in_index(AggregateIndex::Active, event_id)? {
2433            return Ok(Some(event));
2434        }
2435        self.find_event_by_id_in_index(AggregateIndex::Archived, event_id)
2436    }
2437
2438    pub fn aggregate_version(
2439        &self,
2440        aggregate_type: &str,
2441        aggregate_id: &str,
2442    ) -> Result<Option<u64>> {
2443        let meta = self.load_meta_any(aggregate_type, aggregate_id)?;
2444        Ok(meta.map(|(meta, _)| meta.version))
2445    }
2446
2447    pub fn list_aggregate_ids(&self, aggregate_type: &str) -> Result<Vec<String>> {
2448        let start = Instant::now();
2449        let result = (|| {
2450            let mut prefix = key_with_segments(&[PREFIX_META, aggregate_type]);
2451            prefix.push(SEP);
2452
2453            let iter = self
2454                .db
2455                .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
2456
2457            let mut ids = Vec::new();
2458            for item in iter {
2459                let (key, _) = item.map_err(|err| EventError::Storage(err.to_string()))?;
2460                if !key.starts_with(prefix.as_slice()) {
2461                    break;
2462                }
2463                let remainder = &key[prefix.len()..];
2464                if remainder.is_empty() {
2465                    continue;
2466                }
2467                let id = std::str::from_utf8(remainder)
2468                    .map_err(|err| EventError::Storage(err.to_string()))?;
2469                ids.push(id.to_string());
2470            }
2471            Ok(ids)
2472        })();
2473        let duration = start.elapsed().as_secs_f64();
2474        record_store_op(
2475            "rocksdb_iter_meta_ids",
2476            if result.is_ok() { "ok" } else { "err" },
2477            duration,
2478        );
2479        result
2480    }
2481
2482    pub fn verify(&self, aggregate_type: &str, aggregate_id: &str) -> Result<String> {
2483        let (meta, _) = self
2484            .load_meta_any(aggregate_type, aggregate_id)?
2485            .ok_or(EventError::AggregateNotFound)?;
2486        Ok(meta.merkle_root)
2487    }
2488
2489    pub fn create_snapshot(
2490        &self,
2491        aggregate_type: &str,
2492        aggregate_id: &str,
2493        comment: Option<String>,
2494    ) -> Result<SnapshotRecord> {
2495        self.ensure_writable()?;
2496        let _guard = self.write_lock.lock();
2497        let state = self.get_aggregate_state(aggregate_type, aggregate_id)?;
2498        let created_at = Utc::now();
2499        let record = SnapshotRecord {
2500            snapshot_id: Some(self.next_snowflake_id()),
2501            aggregate_type: state.aggregate_type.clone(),
2502            aggregate_id: state.aggregate_id.clone(),
2503            version: state.version,
2504            state: state.state.clone(),
2505            merkle_root: state.merkle_root.clone(),
2506            created_at,
2507            comment,
2508        };
2509
2510        let key = snapshot_key(aggregate_type, aggregate_id, created_at);
2511        self.db
2512            .put(key.clone(), serde_json::to_vec(&record)?)
2513            .map_err(|err| EventError::Storage(err.to_string()))?;
2514        if let Some(snapshot_id) = record.snapshot_id {
2515            let index_key = snapshot_index_key(snapshot_id);
2516            self.db
2517                .put(index_key, key)
2518                .map_err(|err| EventError::Storage(err.to_string()))?;
2519        }
2520
2521        Ok(record)
2522    }
2523
2524    pub fn list_snapshots(
2525        &self,
2526        aggregate_type: Option<&str>,
2527        aggregate_id: Option<&str>,
2528        version: Option<u64>,
2529    ) -> Result<Vec<SnapshotRecord>> {
2530        let start = Instant::now();
2531        let mut segments = vec![PREFIX_SNAPSHOT];
2532        if let Some(aggregate_type) = aggregate_type {
2533            segments.push(aggregate_type);
2534            if let Some(aggregate_id) = aggregate_id {
2535                segments.push(aggregate_id);
2536            }
2537        }
2538        let prefix = key_with_segments(&segments);
2539        let result = (|| {
2540            let mut snapshots = Vec::new();
2541            let iter = self
2542                .db
2543                .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
2544
2545            for item in iter {
2546                let (key, value) = item.map_err(|err| EventError::Storage(err.to_string()))?;
2547                if !key.starts_with(prefix.as_slice()) {
2548                    break;
2549                }
2550
2551                let snapshot: SnapshotRecord = serde_json::from_slice(&value).map_err(|err| {
2552                    EventError::Storage(format!("failed to deserialize snapshot: {err}"))
2553                })?;
2554                if version.map_or(true, |v| v == snapshot.version) {
2555                    snapshots.push(snapshot);
2556                }
2557            }
2558            Ok(snapshots)
2559        })();
2560
2561        let duration = start.elapsed().as_secs_f64();
2562        record_store_op(
2563            "rocksdb_list_snapshots",
2564            if result.is_ok() { "ok" } else { "err" },
2565            duration,
2566        );
2567        result
2568    }
2569
2570    pub fn find_snapshot_by_id(&self, snapshot_id: SnowflakeId) -> Result<Option<SnapshotRecord>> {
2571        let start = Instant::now();
2572        let result = (|| {
2573            // Fast path: use secondary index to jump to the snapshot key.
2574            let index_key = snapshot_index_key(snapshot_id);
2575            if let Some(snapshot_key) = self
2576                .db
2577                .get(index_key)
2578                .map_err(|err| EventError::Storage(err.to_string()))?
2579            {
2580                let Some(value) = self
2581                    .db
2582                    .get(snapshot_key)
2583                    .map_err(|err| EventError::Storage(err.to_string()))?
2584                else {
2585                    return Ok(None);
2586                };
2587                let snapshot: SnapshotRecord = serde_json::from_slice(&value).map_err(|err| {
2588                    EventError::Storage(format!("failed to deserialize snapshot: {err}"))
2589                })?;
2590                return Ok(Some(snapshot));
2591            }
2592
2593            // Backward-compatibility: scan in case the index is missing for older snapshots.
2594            let prefix = key_with_segments(&[PREFIX_SNAPSHOT]);
2595            let iter = self
2596                .db
2597                .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
2598            for item in iter {
2599                let (key, value) = item.map_err(|err| EventError::Storage(err.to_string()))?;
2600                if !key.starts_with(prefix.as_slice()) {
2601                    break;
2602                }
2603                let snapshot: SnapshotRecord = serde_json::from_slice(&value).map_err(|err| {
2604                    EventError::Storage(format!("failed to deserialize snapshot: {err}"))
2605                })?;
2606                if snapshot.snapshot_id == Some(snapshot_id) {
2607                    return Ok(Some(snapshot));
2608                }
2609            }
2610            Ok(None)
2611        })();
2612
2613        let duration = start.elapsed().as_secs_f64();
2614        record_store_op(
2615            "rocksdb_find_snapshot",
2616            if result.is_ok() { "ok" } else { "err" },
2617            duration,
2618        );
2619        result
2620    }
2621
2622    pub fn set_archive(
2623        &self,
2624        aggregate_type: &str,
2625        aggregate_id: &str,
2626        archived: bool,
2627        comment: Option<String>,
2628    ) -> Result<AggregateState> {
2629        self.ensure_writable()?;
2630        let _guard = self.write_lock.lock();
2631        let (mut meta, current_index) = self
2632            .load_meta_any(aggregate_type, aggregate_id)?
2633            .ok_or(EventError::AggregateNotFound)?;
2634        let previous_meta = meta.clone();
2635        let state_map = self.load_state_map_from(current_index, aggregate_type, aggregate_id)?;
2636
2637        let changed_at = Utc::now();
2638        meta.archived = archived;
2639        if archived {
2640            meta.archived_at = Some(changed_at);
2641            meta.archive_comment = comment;
2642        } else {
2643            meta.archived_at = None;
2644            meta.archive_comment = None;
2645        }
2646        meta.updated_at = Some(changed_at);
2647
2648        let destination_index = if archived {
2649            AggregateIndex::Archived
2650        } else {
2651            AggregateIndex::Active
2652        };
2653
2654        let encoded_meta = serde_json::to_vec(&meta)?;
2655        let encoded_state = self.encode_state_map(&state_map)?;
2656
2657        let mut batch = WriteBatch::default();
2658        if current_index != destination_index {
2659            self.move_event_stream(
2660                aggregate_type,
2661                aggregate_id,
2662                current_index,
2663                destination_index,
2664                &mut batch,
2665            )?;
2666        }
2667        match (current_index, destination_index) {
2668            (AggregateIndex::Active, AggregateIndex::Archived) => {
2669                self.sync_timestamp_indexes(
2670                    &mut batch,
2671                    AggregateIndex::Active,
2672                    Some(&previous_meta),
2673                    None,
2674                );
2675                self.sync_timestamp_indexes(
2676                    &mut batch,
2677                    AggregateIndex::Archived,
2678                    None,
2679                    Some(&meta),
2680                );
2681                batch.delete(meta_key(aggregate_type, aggregate_id));
2682                batch.delete(state_key(aggregate_type, aggregate_id));
2683                batch_put(
2684                    &mut batch,
2685                    meta_archived_key(aggregate_type, aggregate_id),
2686                    encoded_meta.clone(),
2687                )?;
2688                batch_put(
2689                    &mut batch,
2690                    state_archived_key(aggregate_type, aggregate_id),
2691                    encoded_state.clone(),
2692                )?;
2693            }
2694            (AggregateIndex::Archived, AggregateIndex::Active) => {
2695                self.sync_timestamp_indexes(
2696                    &mut batch,
2697                    AggregateIndex::Archived,
2698                    Some(&previous_meta),
2699                    None,
2700                );
2701                self.sync_timestamp_indexes(&mut batch, AggregateIndex::Active, None, Some(&meta));
2702                batch.delete(meta_archived_key(aggregate_type, aggregate_id));
2703                batch.delete(state_archived_key(aggregate_type, aggregate_id));
2704                batch_put(
2705                    &mut batch,
2706                    meta_key(aggregate_type, aggregate_id),
2707                    encoded_meta.clone(),
2708                )?;
2709                batch_put(
2710                    &mut batch,
2711                    state_key(aggregate_type, aggregate_id),
2712                    encoded_state.clone(),
2713                )?;
2714            }
2715            (AggregateIndex::Active, AggregateIndex::Active) => {
2716                self.sync_timestamp_indexes(
2717                    &mut batch,
2718                    AggregateIndex::Active,
2719                    Some(&previous_meta),
2720                    Some(&meta),
2721                );
2722                batch_put(
2723                    &mut batch,
2724                    meta_key(aggregate_type, aggregate_id),
2725                    encoded_meta.clone(),
2726                )?;
2727                batch_put(
2728                    &mut batch,
2729                    state_key(aggregate_type, aggregate_id),
2730                    encoded_state.clone(),
2731                )?;
2732            }
2733            (AggregateIndex::Archived, AggregateIndex::Archived) => {
2734                self.sync_timestamp_indexes(
2735                    &mut batch,
2736                    AggregateIndex::Archived,
2737                    Some(&previous_meta),
2738                    Some(&meta),
2739                );
2740                batch_put(
2741                    &mut batch,
2742                    meta_archived_key(aggregate_type, aggregate_id),
2743                    encoded_meta.clone(),
2744                )?;
2745                batch_put(
2746                    &mut batch,
2747                    state_archived_key(aggregate_type, aggregate_id),
2748                    encoded_state.clone(),
2749                )?;
2750            }
2751        }
2752
2753        self.write_batch(batch)?;
2754
2755        self.get_aggregate_state(aggregate_type, aggregate_id)
2756    }
2757
2758    fn move_event_stream(
2759        &self,
2760        aggregate_type: &str,
2761        aggregate_id: &str,
2762        from: AggregateIndex,
2763        to: AggregateIndex,
2764        batch: &mut WriteBatch,
2765    ) -> Result<()> {
2766        if from == to {
2767            return Ok(());
2768        }
2769
2770        let prefix = event_prefix_for(from, aggregate_type, aggregate_id);
2771        let mut iter = self
2772            .db
2773            .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
2774        let mut records = Vec::new();
2775
2776        while let Some(item) = iter.next() {
2777            let (key, value) = item.map_err(|err| EventError::Storage(err.to_string()))?;
2778            if !key.starts_with(prefix.as_slice()) {
2779                break;
2780            }
2781            let version = parse_event_version(&key)?;
2782            records.push((key, value.to_vec(), version));
2783        }
2784
2785        for (old_key, value, version) in records {
2786            batch.delete(old_key);
2787            batch.put(
2788                event_key_for(to, aggregate_type, aggregate_id, version),
2789                value,
2790            );
2791        }
2792
2793        Ok(())
2794    }
2795
2796    fn encode_record(&self, record: &EventRecord) -> Result<EventRecord> {
2797        if let Some(enc) = &self.encryptor {
2798            if encryption::extract_encrypted_value(&record.payload).is_some() {
2799                return Ok(record.clone());
2800            }
2801            let payload_bytes = serde_json::to_vec(&record.payload)?;
2802            let ciphertext = enc.encrypt_to_string(&payload_bytes)?;
2803            let mut stored = record.clone();
2804            stored.payload = encryption::wrap_encrypted_value(ciphertext);
2805            Ok(stored)
2806        } else {
2807            Ok(record.clone())
2808        }
2809    }
2810
2811    fn decode_record(&self, mut record: EventRecord) -> Result<EventRecord> {
2812        if let Some(enc) = &self.encryptor {
2813            if let Some(ciphertext) = encryption::extract_encrypted_value(&record.payload) {
2814                let bytes = enc.decrypt_from_str(ciphertext)?;
2815                record.payload = serde_json::from_slice(&bytes)?;
2816            }
2817            Ok(record)
2818        } else {
2819            if encryption::extract_encrypted_value(&record.payload).is_some() {
2820                return Err(EventError::Config(
2821                    "data encryption key must be configured to read encrypted events".to_string(),
2822                ));
2823            }
2824            Ok(record)
2825        }
2826    }
2827
2828    fn encode_state_map(&self, state: &BTreeMap<String, String>) -> Result<Vec<u8>> {
2829        let json = serde_json::to_vec(state)?;
2830        if let Some(enc) = &self.encryptor {
2831            let ciphertext = enc.encrypt_to_string(&json)?;
2832            Ok(ciphertext.into_bytes())
2833        } else {
2834            Ok(json)
2835        }
2836    }
2837
2838    fn decode_state_map(&self, bytes: &[u8]) -> Result<BTreeMap<String, String>> {
2839        if let Some(enc) = &self.encryptor {
2840            let text = str::from_utf8(bytes).map_err(|err| EventError::Storage(err.to_string()))?;
2841            if encryption::is_encrypted_blob(text.trim()) {
2842                let decrypted = enc.decrypt_from_str(text.trim())?;
2843                Ok(serde_json::from_slice(&decrypted)?)
2844            } else if text.trim().is_empty() {
2845                Ok(BTreeMap::new())
2846            } else {
2847                Ok(serde_json::from_str(text)?)
2848            }
2849        } else {
2850            if let Ok(text) = str::from_utf8(bytes) {
2851                if encryption::is_encrypted_blob(text.trim()) {
2852                    return Err(EventError::Config(
2853                        "data encryption key must be configured to read encrypted aggregates"
2854                            .to_string(),
2855                    ));
2856                }
2857            }
2858            Ok(serde_json::from_slice(bytes)?)
2859        }
2860    }
2861
2862    fn ensure_writable(&self) -> Result<()> {
2863        if self.read_only {
2864            Err(EventError::Storage(
2865                "event store opened in read-only mode".into(),
2866            ))
2867        } else {
2868            Ok(())
2869        }
2870    }
2871
2872    fn write_batch(&self, batch: WriteBatch) -> Result<()> {
2873        let start = Instant::now();
2874        let result = self
2875            .db
2876            .write(batch)
2877            .map_err(|err| EventError::Storage(err.to_string()));
2878        let duration = start.elapsed().as_secs_f64();
2879        record_store_op(
2880            "rocksdb_write",
2881            if result.is_ok() { "ok" } else { "err" },
2882            duration,
2883        );
2884        result
2885    }
2886
2887    fn sync_timestamp_indexes(
2888        &self,
2889        batch: &mut WriteBatch,
2890        index: AggregateIndex,
2891        previous: Option<&AggregateMeta>,
2892        current: Option<&AggregateMeta>,
2893    ) {
2894        self.sync_single_timestamp_index(
2895            batch,
2896            TimestampIndexKind::CreatedAt,
2897            index,
2898            previous,
2899            current,
2900        );
2901        self.sync_single_timestamp_index(
2902            batch,
2903            TimestampIndexKind::UpdatedAt,
2904            index,
2905            previous,
2906            current,
2907        );
2908    }
2909
2910    fn sync_single_timestamp_index(
2911        &self,
2912        batch: &mut WriteBatch,
2913        kind: TimestampIndexKind,
2914        index: AggregateIndex,
2915        previous: Option<&AggregateMeta>,
2916        current: Option<&AggregateMeta>,
2917    ) {
2918        let meta_identity = current.or(previous);
2919        let Some(meta) = meta_identity else {
2920            return;
2921        };
2922        let prev_ts = previous.and_then(|meta| meta_timestamp_ms(meta, kind));
2923        let curr_ts = current.and_then(|meta| meta_timestamp_ms(meta, kind));
2924        if prev_ts == curr_ts {
2925            return;
2926        }
2927        if let Some(prev) = prev_ts {
2928            batch.delete(timestamp_index_key(
2929                kind,
2930                index,
2931                prev,
2932                &meta.aggregate_type,
2933                &meta.aggregate_id,
2934            ));
2935        }
2936        if let Some(curr) = curr_ts {
2937            batch.put(
2938                timestamp_index_key(kind, index, curr, &meta.aggregate_type, &meta.aggregate_id),
2939                Vec::new(),
2940            );
2941        }
2942    }
2943
2944    pub fn aggregates(&self) -> Vec<AggregateState> {
2945        self.aggregates_paginated(0, None)
2946    }
2947
2948    pub fn aggregates_paginated(&self, skip: usize, take: Option<usize>) -> Vec<AggregateState> {
2949        self.aggregates_paginated_with_transform_internal(
2950            skip,
2951            take,
2952            None,
2953            AggregateQueryScope::ActiveOnly,
2954            None,
2955            true,
2956            |aggregate| Some(aggregate),
2957        )
2958    }
2959
2960    pub fn aggregates_paginated_with_transform<F>(
2961        &self,
2962        skip: usize,
2963        take: Option<usize>,
2964        sort: Option<&[AggregateSort]>,
2965        scope: AggregateQueryScope,
2966        cursor: Option<&AggregateCursor>,
2967        transform: F,
2968    ) -> Vec<AggregateState>
2969    where
2970        F: FnMut(AggregateState) -> Option<AggregateState>,
2971    {
2972        self.aggregates_paginated_with_transform_internal(
2973            skip, take, sort, scope, cursor, true, transform,
2974        )
2975    }
2976
2977    pub fn aggregates_paginated_without_state<F>(
2978        &self,
2979        skip: usize,
2980        take: Option<usize>,
2981        sort: Option<&[AggregateSort]>,
2982        scope: AggregateQueryScope,
2983        cursor: Option<&AggregateCursor>,
2984        transform: F,
2985    ) -> Vec<AggregateState>
2986    where
2987        F: FnMut(AggregateState) -> Option<AggregateState>,
2988    {
2989        self.aggregates_paginated_with_transform_internal(
2990            skip, take, sort, scope, cursor, false, transform,
2991        )
2992    }
2993
2994    fn aggregates_paginated_with_transform_internal<F>(
2995        &self,
2996        skip: usize,
2997        take: Option<usize>,
2998        sort: Option<&[AggregateSort]>,
2999        scope: AggregateQueryScope,
3000        cursor: Option<&AggregateCursor>,
3001        include_state: bool,
3002        mut transform: F,
3003    ) -> Vec<AggregateState>
3004    where
3005        F: FnMut(AggregateState) -> Option<AggregateState>,
3006    {
3007        if let Some(keys) = sort {
3008            if let Some((kind, descending)) = timestamp_sort_hint(keys) {
3009                return self.collect_timestamp_sorted(
3010                    kind,
3011                    scope,
3012                    skip,
3013                    take,
3014                    descending,
3015                    cursor,
3016                    include_state,
3017                    &mut transform,
3018                );
3019            }
3020        }
3021        match scope {
3022            AggregateQueryScope::ActiveOnly => self.collect_index_paginated(
3023                AggregateIndex::Active,
3024                skip,
3025                take,
3026                sort,
3027                include_state,
3028                &mut transform,
3029            ),
3030            AggregateQueryScope::ArchivedOnly => self.collect_index_paginated(
3031                AggregateIndex::Archived,
3032                skip,
3033                take,
3034                sort,
3035                include_state,
3036                &mut transform,
3037            ),
3038            AggregateQueryScope::IncludeArchived => {
3039                let mut items = self.collect_index_paginated(
3040                    AggregateIndex::Active,
3041                    0,
3042                    None,
3043                    None,
3044                    include_state,
3045                    &mut transform,
3046                );
3047                let mut archived_items = self.collect_index_paginated(
3048                    AggregateIndex::Archived,
3049                    0,
3050                    None,
3051                    None,
3052                    include_state,
3053                    &mut transform,
3054                );
3055                items.append(&mut archived_items);
3056
3057                if let Some(keys) = sort {
3058                    items.sort_by(|a, b| compare_aggregate_sort_keys(a, b, keys));
3059                }
3060
3061                let total = items.len();
3062                let start = skip.min(total);
3063                let end = if let Some(limit) = take {
3064                    start.saturating_add(limit).min(total)
3065                } else {
3066                    total
3067                };
3068                items[start..end].to_vec()
3069            }
3070        }
3071    }
3072
3073    fn collect_timestamp_sorted<F>(
3074        &self,
3075        kind: TimestampIndexKind,
3076        scope: AggregateQueryScope,
3077        skip: usize,
3078        take: Option<usize>,
3079        descending: bool,
3080        cursor: Option<&AggregateCursor>,
3081        include_state: bool,
3082        transform: &mut F,
3083    ) -> Vec<AggregateState>
3084    where
3085        F: FnMut(AggregateState) -> Option<AggregateState>,
3086    {
3087        match scope {
3088            AggregateQueryScope::ActiveOnly => self.collect_timestamp_index_paginated(
3089                kind,
3090                AggregateIndex::Active,
3091                skip,
3092                take,
3093                descending,
3094                cursor,
3095                include_state,
3096                transform,
3097            ),
3098            AggregateQueryScope::ArchivedOnly => self.collect_timestamp_index_paginated(
3099                kind,
3100                AggregateIndex::Archived,
3101                skip,
3102                take,
3103                descending,
3104                cursor,
3105                include_state,
3106                transform,
3107            ),
3108            AggregateQueryScope::IncludeArchived => self.collect_timestamp_indexes_merged(
3109                kind,
3110                skip,
3111                take,
3112                descending,
3113                cursor,
3114                include_state,
3115                transform,
3116            ),
3117        }
3118    }
3119
3120    fn collect_timestamp_index_paginated<F>(
3121        &self,
3122        kind: TimestampIndexKind,
3123        index: AggregateIndex,
3124        skip: usize,
3125        take: Option<usize>,
3126        descending: bool,
3127        cursor: Option<&AggregateCursor>,
3128        include_state: bool,
3129        transform: &mut F,
3130    ) -> Vec<AggregateState>
3131    where
3132        F: FnMut(AggregateState) -> Option<AggregateState>,
3133    {
3134        let mut stream = TimestampIndexIterator::new(self, kind, index, descending);
3135        let mut items = Vec::new();
3136        let mut skipped = 0usize;
3137        let cursor_key = cursor.and_then(|token| {
3138            token.as_timestamp_key().and_then(
3139                |(cursor_kind, cursor_desc, cursor_ts, cursor_index, agg_type, agg_id)| {
3140                    if cursor_kind == kind
3141                        && cursor_desc == descending
3142                        && cursor_index == CursorIndex::from(index)
3143                    {
3144                        Some((cursor_ts, agg_type.to_owned(), agg_id.to_owned()))
3145                    } else {
3146                        None
3147                    }
3148                },
3149            )
3150        });
3151        let mut resume = cursor_key;
3152
3153        while let Some(entry) = stream.next_entry() {
3154            let aggregate = match self.load_aggregate_from_index(
3155                index,
3156                &entry.aggregate_type,
3157                &entry.aggregate_id,
3158                include_state,
3159            ) {
3160                Ok(Some(aggregate)) => aggregate,
3161                Ok(None) => continue,
3162                Err(_) => {
3163                    stream.mark_error();
3164                    continue;
3165                }
3166            };
3167
3168            let mut consume_cursor = false;
3169            if let Some((cursor_ts, cursor_type, cursor_id)) = resume.as_ref() {
3170                let ordering = compare_timestamp_values(
3171                    entry.timestamp_ms,
3172                    &entry.aggregate_type,
3173                    &entry.aggregate_id,
3174                    *cursor_ts,
3175                    cursor_type,
3176                    cursor_id,
3177                );
3178                let skip_entry = if descending {
3179                    ordering != StdOrdering::Less
3180                } else {
3181                    ordering != StdOrdering::Greater
3182                };
3183                if skip_entry {
3184                    continue;
3185                }
3186                consume_cursor = true;
3187            }
3188            if consume_cursor {
3189                resume = None;
3190            }
3191
3192            let Some(aggregate) = transform(aggregate) else {
3193                continue;
3194            };
3195
3196            if skipped < skip {
3197                skipped += 1;
3198                continue;
3199            }
3200            items.push(aggregate);
3201            if let Some(limit) = take {
3202                if items.len() >= limit {
3203                    break;
3204                }
3205            }
3206        }
3207
3208        items
3209    }
3210
3211    fn collect_timestamp_indexes_merged<F>(
3212        &self,
3213        kind: TimestampIndexKind,
3214        skip: usize,
3215        take: Option<usize>,
3216        descending: bool,
3217        cursor: Option<&AggregateCursor>,
3218        include_state: bool,
3219        transform: &mut F,
3220    ) -> Vec<AggregateState>
3221    where
3222        F: FnMut(AggregateState) -> Option<AggregateState>,
3223    {
3224        let mut active_stream =
3225            TimestampIndexIterator::new(self, kind, AggregateIndex::Active, descending);
3226        let mut archived_stream =
3227            TimestampIndexIterator::new(self, kind, AggregateIndex::Archived, descending);
3228        let mut next_active = active_stream.next_entry();
3229        let mut next_archived = archived_stream.next_entry();
3230        let mut skipped = 0usize;
3231        let mut items = Vec::new();
3232        let cursor_key = cursor.and_then(|token| {
3233            token.as_timestamp_key().and_then(
3234                |(cursor_kind, cursor_desc, cursor_ts, _cursor_index, agg_type, agg_id)| {
3235                    if cursor_kind == kind && cursor_desc == descending {
3236                        Some((cursor_ts, agg_type.to_owned(), agg_id.to_owned()))
3237                    } else {
3238                        None
3239                    }
3240                },
3241            )
3242        });
3243        let mut resume = cursor_key;
3244
3245        loop {
3246            let (source_index, entry) = match (&next_active, &next_archived) {
3247                (None, None) => break,
3248                (Some(_), None) => (AggregateIndex::Active, next_active.take()),
3249                (None, Some(_)) => (AggregateIndex::Archived, next_archived.take()),
3250                (Some(active), Some(archived)) => {
3251                    if compare_timestamp_entries(active, archived, descending)
3252                        != StdOrdering::Greater
3253                    {
3254                        (AggregateIndex::Active, next_active.take())
3255                    } else {
3256                        (AggregateIndex::Archived, next_archived.take())
3257                    }
3258                }
3259            };
3260
3261            let Some(entry) = entry else {
3262                break;
3263            };
3264
3265            let aggregate = match self.load_aggregate_from_index(
3266                source_index,
3267                &entry.aggregate_type,
3268                &entry.aggregate_id,
3269                include_state,
3270            ) {
3271                Ok(Some(aggregate)) => aggregate,
3272                Ok(None) => {
3273                    match source_index {
3274                        AggregateIndex::Active => next_active = active_stream.next_entry(),
3275                        AggregateIndex::Archived => next_archived = archived_stream.next_entry(),
3276                    }
3277                    continue;
3278                }
3279                Err(_) => {
3280                    match source_index {
3281                        AggregateIndex::Active => active_stream.mark_error(),
3282                        AggregateIndex::Archived => archived_stream.mark_error(),
3283                    }
3284                    match source_index {
3285                        AggregateIndex::Active => next_active = active_stream.next_entry(),
3286                        AggregateIndex::Archived => next_archived = archived_stream.next_entry(),
3287                    }
3288                    continue;
3289                }
3290            };
3291
3292            let mut consume_cursor = false;
3293            if let Some((cursor_ts, cursor_type, cursor_id)) = resume.as_ref() {
3294                let ordering = compare_timestamp_values(
3295                    entry.timestamp_ms,
3296                    &entry.aggregate_type,
3297                    &entry.aggregate_id,
3298                    *cursor_ts,
3299                    cursor_type,
3300                    cursor_id,
3301                );
3302                let skip_entry = if descending {
3303                    ordering != StdOrdering::Less
3304                } else {
3305                    ordering != StdOrdering::Greater
3306                };
3307                if skip_entry {
3308                    match source_index {
3309                        AggregateIndex::Active => next_active = active_stream.next_entry(),
3310                        AggregateIndex::Archived => next_archived = archived_stream.next_entry(),
3311                    }
3312                    continue;
3313                }
3314                consume_cursor = true;
3315            }
3316            if consume_cursor {
3317                resume = None;
3318            }
3319
3320            let Some(aggregate) = transform(aggregate) else {
3321                match source_index {
3322                    AggregateIndex::Active => next_active = active_stream.next_entry(),
3323                    AggregateIndex::Archived => next_archived = archived_stream.next_entry(),
3324                }
3325                continue;
3326            };
3327
3328            if skipped < skip {
3329                skipped += 1;
3330            } else {
3331                items.push(aggregate);
3332            }
3333
3334            if let Some(limit) = take {
3335                if items.len() >= limit {
3336                    break;
3337                }
3338            }
3339
3340            match source_index {
3341                AggregateIndex::Active => next_active = active_stream.next_entry(),
3342                AggregateIndex::Archived => next_archived = archived_stream.next_entry(),
3343            }
3344        }
3345
3346        items
3347    }
3348
3349    fn load_aggregate_from_index(
3350        &self,
3351        index: AggregateIndex,
3352        aggregate_type: &str,
3353        aggregate_id: &str,
3354        include_state: bool,
3355    ) -> Result<Option<AggregateState>> {
3356        let meta = match self.load_meta_from(index, aggregate_type, aggregate_id)? {
3357            Some(meta) => meta,
3358            None => return Ok(None),
3359        };
3360        let AggregateMeta {
3361            aggregate_type,
3362            aggregate_id,
3363            version,
3364            merkle_root,
3365            archived,
3366            created_at,
3367            updated_at,
3368            extensions,
3369            ..
3370        } = meta;
3371        let state =
3372            self.maybe_load_state_map(include_state, index, &aggregate_type, &aggregate_id)?;
3373        Ok(Some(AggregateState {
3374            aggregate_type,
3375            aggregate_id,
3376            version,
3377            state,
3378            extensions,
3379            merkle_root,
3380            created_at,
3381            updated_at,
3382            archived,
3383        }))
3384    }
3385
3386    fn collect_index_paginated<F>(
3387        &self,
3388        index: AggregateIndex,
3389        skip: usize,
3390        take: Option<usize>,
3391        sort: Option<&[AggregateSort]>,
3392        include_state: bool,
3393        transform: &mut F,
3394    ) -> Vec<AggregateState>
3395    where
3396        F: FnMut(AggregateState) -> Option<AggregateState>,
3397    {
3398        let start = Instant::now();
3399        let prefix = meta_prefix_for(index);
3400        let iter = self
3401            .db
3402            .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
3403        let mut items = Vec::new();
3404        let mut skipped = 0usize;
3405        let should_sort = sort.map_or(false, |keys| !aggregate_sort_matches_key_order(keys));
3406        let mut status = "ok";
3407        let mut pending = if should_sort { Some(Vec::new()) } else { None };
3408
3409        for item in iter {
3410            let Ok((key, value)) = item else {
3411                continue;
3412            };
3413            if !key.starts_with(prefix.as_slice()) {
3414                break;
3415            }
3416            if key.len() > prefix.len() && key[prefix.len()] != SEP {
3417                break;
3418            }
3419
3420            let meta: AggregateMeta = match serde_json::from_slice(&value) {
3421                Ok(meta) => meta,
3422                Err(_) => continue,
3423            };
3424
3425            let AggregateMeta {
3426                aggregate_type,
3427                aggregate_id,
3428                version,
3429                merkle_root,
3430                archived,
3431                created_at,
3432                updated_at,
3433                extensions,
3434                ..
3435            } = meta;
3436            if should_sort {
3437                if let Some(list) = pending.as_mut() {
3438                    list.push(AggregateSummary {
3439                        aggregate_type,
3440                        aggregate_id,
3441                        version,
3442                        merkle_root,
3443                        archived,
3444                        created_at,
3445                        updated_at,
3446                        extensions,
3447                    });
3448                }
3449                continue;
3450            }
3451            let state = match self.maybe_load_state_map(
3452                include_state,
3453                index,
3454                &aggregate_type,
3455                &aggregate_id,
3456            ) {
3457                Ok(state) => state,
3458                Err(_) => {
3459                    status = "err";
3460                    continue;
3461                }
3462            };
3463
3464            let aggregate = AggregateState {
3465                aggregate_type,
3466                aggregate_id,
3467                version,
3468                state,
3469                extensions,
3470                merkle_root,
3471                created_at,
3472                updated_at,
3473                archived,
3474            };
3475
3476            let Some(aggregate) = transform(aggregate) else {
3477                continue;
3478            };
3479
3480            if skipped < skip && !should_sort {
3481                skipped += 1;
3482                continue;
3483            }
3484
3485            if should_sort {
3486                items.push(aggregate);
3487                continue;
3488            }
3489
3490            items.push(aggregate);
3491
3492            if let Some(limit) = take {
3493                if items.len() >= limit {
3494                    break;
3495                }
3496            }
3497        }
3498
3499        if should_sort {
3500            let summaries = pending.unwrap_or_default();
3501            let (mut aggregates, had_errors) =
3502                self.load_states_parallel(index, summaries, include_state);
3503            if had_errors {
3504                status = "err";
3505            }
3506            for aggregate in aggregates.drain(..) {
3507                let Some(aggregate) = transform(aggregate) else {
3508                    continue;
3509                };
3510                items.push(aggregate);
3511            }
3512            if let Some(keys) = sort {
3513                items.sort_by(|a, b| compare_aggregate_sort_keys(a, b, keys));
3514            }
3515
3516            let total = items.len();
3517            let start = skip.min(total);
3518            let end = if let Some(limit) = take {
3519                start.saturating_add(limit).min(total)
3520            } else {
3521                total
3522            };
3523            items = items[start..end].to_vec();
3524        }
3525
3526        let duration = start.elapsed().as_secs_f64();
3527        let metric = match index {
3528            AggregateIndex::Active => "rocksdb_iter_meta",
3529            AggregateIndex::Archived => "rocksdb_iter_meta_archived",
3530        };
3531        record_store_op(metric, status, duration);
3532        items
3533    }
3534
3535    pub fn aggregates_page_with_transform<F>(
3536        &self,
3537        cursor: Option<&AggregateCursor>,
3538        take: usize,
3539        scope: AggregateQueryScope,
3540        include_state: bool,
3541        transform: F,
3542    ) -> Result<(Vec<AggregateState>, Option<AggregateCursor>)>
3543    where
3544        F: FnMut(AggregateState) -> Option<AggregateState>,
3545    {
3546        self.aggregates_page_with_transform_internal(cursor, take, scope, include_state, transform)
3547    }
3548
3549    pub fn aggregates_page_without_state<F>(
3550        &self,
3551        cursor: Option<&AggregateCursor>,
3552        take: usize,
3553        scope: AggregateQueryScope,
3554        transform: F,
3555    ) -> Result<(Vec<AggregateState>, Option<AggregateCursor>)>
3556    where
3557        F: FnMut(AggregateState) -> Option<AggregateState>,
3558    {
3559        self.aggregates_page_with_transform_internal(cursor, take, scope, false, transform)
3560    }
3561
3562    fn aggregates_page_with_transform_internal<F>(
3563        &self,
3564        cursor: Option<&AggregateCursor>,
3565        take: usize,
3566        scope: AggregateQueryScope,
3567        include_state: bool,
3568        mut transform: F,
3569    ) -> Result<(Vec<AggregateState>, Option<AggregateCursor>)>
3570    where
3571        F: FnMut(AggregateState) -> Option<AggregateState>,
3572    {
3573        if take == 0 {
3574            return Ok((Vec::new(), None));
3575        }
3576
3577        match scope {
3578            AggregateQueryScope::ActiveOnly => self.collect_index_page(
3579                AggregateIndex::Active,
3580                cursor,
3581                take,
3582                include_state,
3583                &mut transform,
3584            ),
3585            AggregateQueryScope::ArchivedOnly => {
3586                if let Some(cursor) = cursor {
3587                    if cursor.index() != CursorIndex::Archived {
3588                        return Err(EventError::InvalidCursor(
3589                            "archived listings require archived cursor values".into(),
3590                        ));
3591                    }
3592                }
3593                self.collect_index_page(
3594                    AggregateIndex::Archived,
3595                    cursor,
3596                    take,
3597                    include_state,
3598                    &mut transform,
3599                )
3600            }
3601            AggregateQueryScope::IncludeArchived => {
3602                let mut remaining = take;
3603                let mut items = Vec::new();
3604
3605                if let Some(cursor) = cursor {
3606                    if cursor.index() == CursorIndex::Archived {
3607                        let (mut archived, next) = self.collect_index_page(
3608                            AggregateIndex::Archived,
3609                            Some(cursor),
3610                            remaining,
3611                            include_state,
3612                            &mut transform,
3613                        )?;
3614                        items.append(&mut archived);
3615                        return Ok((items, next));
3616                    }
3617                }
3618
3619                let (mut active_items, active_cursor) = self.collect_index_page(
3620                    AggregateIndex::Active,
3621                    cursor,
3622                    remaining,
3623                    include_state,
3624                    &mut transform,
3625                )?;
3626                remaining = remaining.saturating_sub(active_items.len());
3627                items.append(&mut active_items);
3628                if let Some(next) = active_cursor {
3629                    return Ok((items, Some(next)));
3630                }
3631                if remaining == 0 {
3632                    return Ok((items, None));
3633                }
3634                let (mut archived_items, archived_cursor) = self.collect_index_page(
3635                    AggregateIndex::Archived,
3636                    None,
3637                    remaining,
3638                    include_state,
3639                    &mut transform,
3640                )?;
3641                items.append(&mut archived_items);
3642                Ok((items, archived_cursor))
3643            }
3644        }
3645    }
3646
3647    pub fn aggregates_page(
3648        &self,
3649        cursor: Option<&AggregateCursor>,
3650        take: usize,
3651        scope: AggregateQueryScope,
3652    ) -> Result<(Vec<AggregateState>, Option<AggregateCursor>)> {
3653        self.aggregates_page_with_transform(cursor, take, scope, true, |aggregate| Some(aggregate))
3654    }
3655
3656    fn collect_index_page<F>(
3657        &self,
3658        index: AggregateIndex,
3659        cursor: Option<&AggregateCursor>,
3660        take: usize,
3661        include_state: bool,
3662        transform: &mut F,
3663    ) -> Result<(Vec<AggregateState>, Option<AggregateCursor>)>
3664    where
3665        F: FnMut(AggregateState) -> Option<AggregateState>,
3666    {
3667        let start = Instant::now();
3668        let prefix = meta_prefix_for(index);
3669        let mut start_key = prefix.clone();
3670        let mut skip_current = false;
3671        if let Some(position) = cursor {
3672            if position.index() != CursorIndex::from(index) {
3673                return Err(EventError::InvalidCursor(format!(
3674                    "cursor points to {} aggregates but scope uses {} records",
3675                    position.index(),
3676                    CursorIndex::from(index)
3677                )));
3678            }
3679            start_key = meta_key_for(index, position.aggregate_type(), position.aggregate_id());
3680            skip_current = true;
3681        }
3682        let iter = self
3683            .db
3684            .iterator(IteratorMode::From(start_key.as_slice(), Direction::Forward));
3685        let mut items = Vec::new();
3686        let mut status = "ok";
3687        let mut last_cursor = None;
3688
3689        for item in iter {
3690            let Ok((key, value)) = item else {
3691                status = "err";
3692                continue;
3693            };
3694            if !key.starts_with(prefix.as_slice()) {
3695                break;
3696            }
3697            if key.len() > prefix.len() && key[prefix.len()] != SEP {
3698                break;
3699            }
3700            if skip_current && key.as_ref() == start_key.as_slice() {
3701                skip_current = false;
3702                continue;
3703            } else if skip_current {
3704                skip_current = false;
3705            }
3706
3707            let meta: AggregateMeta = match serde_json::from_slice(&value) {
3708                Ok(meta) => meta,
3709                Err(_) => {
3710                    status = "err";
3711                    continue;
3712                }
3713            };
3714
3715            let AggregateMeta {
3716                aggregate_type,
3717                aggregate_id,
3718                version,
3719                merkle_root,
3720                archived,
3721                created_at,
3722                updated_at,
3723                extensions,
3724                ..
3725            } = meta;
3726            let state = match self.maybe_load_state_map(
3727                include_state,
3728                index,
3729                &aggregate_type,
3730                &aggregate_id,
3731            ) {
3732                Ok(state) => state,
3733                Err(_) => {
3734                    status = "err";
3735                    continue;
3736                }
3737            };
3738
3739            let aggregate = AggregateState {
3740                aggregate_type,
3741                aggregate_id,
3742                version,
3743                state,
3744                extensions,
3745                merkle_root,
3746                created_at,
3747                updated_at,
3748                archived,
3749            };
3750
3751            let Some(aggregate) = transform(aggregate) else {
3752                continue;
3753            };
3754
3755            last_cursor = Some(AggregateCursor::new(
3756                CursorIndex::from(index),
3757                aggregate.aggregate_type.clone(),
3758                aggregate.aggregate_id.clone(),
3759            ));
3760            items.push(aggregate);
3761
3762            if items.len() >= take {
3763                break;
3764            }
3765        }
3766
3767        let duration = start.elapsed().as_secs_f64();
3768        let metric = match index {
3769            AggregateIndex::Active => "rocksdb_iter_meta",
3770            AggregateIndex::Archived => "rocksdb_iter_meta_archived",
3771        };
3772        record_store_op(metric, status, duration);
3773
3774        let next_cursor = if items.len() >= take {
3775            last_cursor
3776        } else {
3777            None
3778        };
3779
3780        Ok((items, next_cursor))
3781    }
3782
3783    fn load_states_parallel(
3784        &self,
3785        index: AggregateIndex,
3786        summaries: Vec<AggregateSummary>,
3787        include_state: bool,
3788    ) -> (Vec<AggregateState>, bool) {
3789        use rayon::prelude::*;
3790
3791        let results: Vec<std::result::Result<AggregateState, EventError>> = summaries
3792            .into_par_iter()
3793            .map(|summary| {
3794                let AggregateSummary {
3795                    aggregate_type,
3796                    aggregate_id,
3797                    version,
3798                    merkle_root,
3799                    archived,
3800                    created_at,
3801                    updated_at,
3802                    extensions,
3803                } = summary;
3804                let state = self.maybe_load_state_map(
3805                    include_state,
3806                    index,
3807                    &aggregate_type,
3808                    &aggregate_id,
3809                )?;
3810                Ok(AggregateState {
3811                    aggregate_type,
3812                    aggregate_id,
3813                    version,
3814                    state,
3815                    extensions,
3816                    merkle_root,
3817                    created_at,
3818                    updated_at,
3819                    archived,
3820                })
3821            })
3822            .collect();
3823
3824        let mut had_error = false;
3825        let aggregates = results
3826            .into_iter()
3827            .filter_map(|result| match result {
3828                Ok(aggregate) => Some(aggregate),
3829                Err(_) => {
3830                    had_error = true;
3831                    None
3832                }
3833            })
3834            .collect();
3835
3836        (aggregates, had_error)
3837    }
3838
3839    fn collect_events_paginated(
3840        &self,
3841        index: AggregateIndex,
3842        scope: EventQueryScope<'_>,
3843        skip: usize,
3844        take: Option<usize>,
3845        sort: Option<&[EventSort]>,
3846        filter: Option<&FilterExpr>,
3847    ) -> Vec<EventRecord> {
3848        let start = Instant::now();
3849        let prefix = event_prefix_for_scope(index, scope);
3850        let iter = self
3851            .db
3852            .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
3853        let mut items = Vec::new();
3854        let mut skipped = 0usize;
3855        let should_sort = sort.map_or(false, |keys| !event_sort_matches_key_order(keys));
3856        let mut status = "ok";
3857
3858        for item in iter {
3859            let Ok((key, value)) = item else {
3860                status = "err";
3861                continue;
3862            };
3863            if !key.starts_with(prefix.as_slice()) {
3864                break;
3865            }
3866            if key.len() > prefix.len() && key[prefix.len()] != SEP {
3867                break;
3868            }
3869
3870            let raw: EventRecord = match serde_json::from_slice(&value) {
3871                Ok(record) => record,
3872                Err(_) => {
3873                    status = "err";
3874                    continue;
3875                }
3876            };
3877            let record = match self.decode_record(raw) {
3878                Ok(record) => record,
3879                Err(_) => {
3880                    status = "err";
3881                    continue;
3882                }
3883            };
3884
3885            if let Some(expr) = filter {
3886                if !expr.matches_event(&record) {
3887                    continue;
3888                }
3889            }
3890
3891            if skipped < skip && !should_sort {
3892                skipped += 1;
3893                continue;
3894            }
3895
3896            if should_sort {
3897                items.push(record);
3898                continue;
3899            }
3900
3901            items.push(record);
3902
3903            if let Some(limit) = take {
3904                if items.len() >= limit {
3905                    break;
3906                }
3907            }
3908        }
3909
3910        if should_sort {
3911            if let Some(keys) = sort {
3912                items.sort_by(|a, b| compare_event_sort_keys(a, b, keys));
3913            }
3914
3915            let total = items.len();
3916            let start = skip.min(total);
3917            let end = if let Some(limit) = take {
3918                start.saturating_add(limit).min(total)
3919            } else {
3920                total
3921            };
3922            items = items[start..end].to_vec();
3923        }
3924
3925        let duration = start.elapsed().as_secs_f64();
3926        let metric = match index {
3927            AggregateIndex::Active => "rocksdb_iter_events",
3928            AggregateIndex::Archived => "rocksdb_iter_events_archived",
3929        };
3930        record_store_op(metric, status, duration);
3931        items
3932    }
3933
3934    pub fn events_page(
3935        &self,
3936        scope: EventQueryScope<'_>,
3937        archive_scope: EventArchiveScope,
3938        cursor: Option<&EventCursor>,
3939        take: usize,
3940        filter: Option<&FilterExpr>,
3941    ) -> Result<(Vec<EventRecord>, Option<EventCursor>)> {
3942        if take == 0 {
3943            return Ok((Vec::new(), None));
3944        }
3945
3946        match archive_scope {
3947            EventArchiveScope::ActiveOnly => {
3948                self.collect_events_page(AggregateIndex::Active, scope, cursor, take, filter)
3949            }
3950            EventArchiveScope::ArchivedOnly => {
3951                if let Some(cursor) = cursor {
3952                    if cursor.index() != CursorIndex::Archived {
3953                        return Err(EventError::InvalidCursor(
3954                            "archived listings require archived cursor values".into(),
3955                        ));
3956                    }
3957                }
3958                self.collect_events_page(AggregateIndex::Archived, scope, cursor, take, filter)
3959            }
3960            EventArchiveScope::IncludeArchived => {
3961                if let Some(cursor) = cursor {
3962                    if cursor.index() == CursorIndex::Archived {
3963                        return self.collect_events_page(
3964                            AggregateIndex::Archived,
3965                            scope,
3966                            Some(cursor),
3967                            take,
3968                            filter,
3969                        );
3970                    }
3971                }
3972
3973                let (mut active, next_active) =
3974                    self.collect_events_page(AggregateIndex::Active, scope, cursor, take, filter)?;
3975                if let Some(cursor) = next_active {
3976                    return Ok((active, Some(cursor)));
3977                }
3978                if active.len() >= take {
3979                    return Ok((active, None));
3980                }
3981                let remaining = take.saturating_sub(active.len());
3982                let (mut archived, next_archived) = self.collect_events_page(
3983                    AggregateIndex::Archived,
3984                    scope,
3985                    None,
3986                    remaining,
3987                    filter,
3988                )?;
3989                active.append(&mut archived);
3990                Ok((active, next_archived))
3991            }
3992        }
3993    }
3994
3995    fn collect_events_page(
3996        &self,
3997        index: AggregateIndex,
3998        scope: EventQueryScope<'_>,
3999        cursor: Option<&EventCursor>,
4000        take: usize,
4001        filter: Option<&FilterExpr>,
4002    ) -> Result<(Vec<EventRecord>, Option<EventCursor>)> {
4003        let start = Instant::now();
4004        let prefix = event_prefix_for_scope(index, scope);
4005        let mut start_key = prefix.clone();
4006        let mut skip_current = false;
4007        if let Some(token) = cursor {
4008            if token.index() != CursorIndex::from(index) {
4009                return Err(EventError::InvalidCursor(format!(
4010                    "cursor points to {} events but scope uses {} records",
4011                    token.index(),
4012                    CursorIndex::from(index)
4013                )));
4014            }
4015            if !event_cursor_matches_scope(token, scope) {
4016                return Err(EventError::InvalidCursor(
4017                    "cursor does not match the requested event scope".into(),
4018                ));
4019            }
4020            start_key = event_key_for(
4021                index,
4022                token.aggregate_type(),
4023                token.aggregate_id(),
4024                token.version(),
4025            );
4026            skip_current = true;
4027        }
4028        let iter = self
4029            .db
4030            .iterator(IteratorMode::From(start_key.as_slice(), Direction::Forward));
4031        let mut items = Vec::new();
4032        let mut status = "ok";
4033        let mut last_cursor = None;
4034
4035        for item in iter {
4036            let Ok((key, value)) = item else {
4037                status = "err";
4038                continue;
4039            };
4040            if !key.starts_with(prefix.as_slice()) {
4041                break;
4042            }
4043            if skip_current && key.as_ref() == start_key.as_slice() {
4044                skip_current = false;
4045                continue;
4046            } else if skip_current {
4047                skip_current = false;
4048            }
4049
4050            let raw: EventRecord = match serde_json::from_slice(&value) {
4051                Ok(record) => record,
4052                Err(_) => {
4053                    status = "err";
4054                    continue;
4055                }
4056            };
4057            let record = match self.decode_record(raw) {
4058                Ok(record) => record,
4059                Err(_) => {
4060                    status = "err";
4061                    continue;
4062                }
4063            };
4064
4065            if let Some(expr) = filter {
4066                if !expr.matches_event(&record) {
4067                    continue;
4068                }
4069            }
4070
4071            last_cursor = Some(EventCursor::new(
4072                CursorIndex::from(index),
4073                record.aggregate_type.clone(),
4074                record.aggregate_id.clone(),
4075                record.version,
4076            ));
4077            items.push(record);
4078
4079            if items.len() >= take {
4080                break;
4081            }
4082        }
4083
4084        let duration = start.elapsed().as_secs_f64();
4085        let metric = match index {
4086            AggregateIndex::Active => "rocksdb_iter_events",
4087            AggregateIndex::Archived => "rocksdb_iter_events_archived",
4088        };
4089        record_store_op(metric, status, duration);
4090
4091        let next_cursor = if items.len() >= take {
4092            last_cursor
4093        } else {
4094            None
4095        };
4096
4097        Ok((items, next_cursor))
4098    }
4099
4100    fn find_event_by_id_in_index(
4101        &self,
4102        index: AggregateIndex,
4103        event_id: SnowflakeId,
4104    ) -> Result<Option<EventRecord>> {
4105        let start = Instant::now();
4106        let prefix = key_with_segments(&[index.event_prefix()]);
4107        let iter = self
4108            .db
4109            .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
4110        let mut status = "ok";
4111
4112        for item in iter {
4113            let Ok((key, value)) = item else {
4114                status = "err";
4115                continue;
4116            };
4117            if !key.starts_with(prefix.as_slice()) {
4118                break;
4119            }
4120            if key.len() > prefix.len() && key[prefix.len()] != SEP {
4121                break;
4122            }
4123
4124            let raw: EventRecord = match serde_json::from_slice(&value) {
4125                Ok(record) => record,
4126                Err(_) => {
4127                    status = "err";
4128                    continue;
4129                }
4130            };
4131            let record = match self.decode_record(raw) {
4132                Ok(record) => record,
4133                Err(_) => {
4134                    status = "err";
4135                    continue;
4136                }
4137            };
4138
4139            if record.metadata.event_id == event_id {
4140                let duration = start.elapsed().as_secs_f64();
4141                let metric = match index {
4142                    AggregateIndex::Active => "rocksdb_scan_event_id",
4143                    AggregateIndex::Archived => "rocksdb_scan_event_id_archived",
4144                };
4145                record_store_op(metric, status, duration);
4146                return Ok(Some(record));
4147            }
4148        }
4149
4150        let duration = start.elapsed().as_secs_f64();
4151        let metric = match index {
4152            AggregateIndex::Active => "rocksdb_scan_event_id",
4153            AggregateIndex::Archived => "rocksdb_scan_event_id_archived",
4154        };
4155        record_store_op(metric, status, duration);
4156        Ok(None)
4157    }
4158
4159    pub fn aggregate_positions(&self) -> Result<Vec<AggregatePositionEntry>> {
4160        let start = Instant::now();
4161        let result = (|| {
4162            let prefix = meta_prefix();
4163            let iter = self
4164                .db
4165                .iterator(IteratorMode::From(prefix.as_slice(), Direction::Forward));
4166            let mut items = Vec::new();
4167
4168            for item in iter {
4169                let (key, value) = item.map_err(|err| EventError::Storage(err.to_string()))?;
4170                if !key.starts_with(prefix.as_slice()) {
4171                    break;
4172                }
4173                if key.len() > prefix.len() && key[prefix.len()] != SEP {
4174                    break;
4175                }
4176
4177                let meta: AggregateMeta = serde_json::from_slice(&value)?;
4178                items.push(AggregatePositionEntry {
4179                    aggregate_type: meta.aggregate_type,
4180                    aggregate_id: meta.aggregate_id,
4181                    version: meta.version,
4182                });
4183            }
4184
4185            Ok(items)
4186        })();
4187        let duration = start.elapsed().as_secs_f64();
4188        record_store_op(
4189            "rocksdb_iter_meta_positions",
4190            if result.is_ok() { "ok" } else { "err" },
4191            duration,
4192        );
4193        result
4194    }
4195
4196    fn load_meta_from(
4197        &self,
4198        index: AggregateIndex,
4199        aggregate_type: &str,
4200        aggregate_id: &str,
4201    ) -> Result<Option<AggregateMeta>> {
4202        let start = Instant::now();
4203        let result = (|| {
4204            let key = meta_key_for(index, aggregate_type, aggregate_id);
4205            let value = self
4206                .db
4207                .get(key)
4208                .map_err(|err| EventError::Storage(err.to_string()))?;
4209            if let Some(value) = value {
4210                Ok(Some(serde_json::from_slice(&value)?))
4211            } else {
4212                Ok(None)
4213            }
4214        })();
4215        let duration = start.elapsed().as_secs_f64();
4216        record_store_op(
4217            "rocksdb_get_meta",
4218            if result.is_ok() { "ok" } else { "err" },
4219            duration,
4220        );
4221        result
4222    }
4223
4224    fn load_meta_any(
4225        &self,
4226        aggregate_type: &str,
4227        aggregate_id: &str,
4228    ) -> Result<Option<(AggregateMeta, AggregateIndex)>> {
4229        if let Some(meta) =
4230            self.load_meta_from(AggregateIndex::Active, aggregate_type, aggregate_id)?
4231        {
4232            return Ok(Some((meta, AggregateIndex::Active)));
4233        }
4234        if let Some(meta) =
4235            self.load_meta_from(AggregateIndex::Archived, aggregate_type, aggregate_id)?
4236        {
4237            return Ok(Some((meta, AggregateIndex::Archived)));
4238        }
4239        Ok(None)
4240    }
4241
4242    fn load_meta(&self, aggregate_type: &str, aggregate_id: &str) -> Result<Option<AggregateMeta>> {
4243        self.load_meta_from(AggregateIndex::Active, aggregate_type, aggregate_id)
4244    }
4245
4246    pub fn create_aggregate(&self, aggregate_type: &str, aggregate_id: &str) -> Result<()> {
4247        self.ensure_writable()?;
4248        let _guard = self.write_lock.lock();
4249
4250        if self.load_meta_any(aggregate_type, aggregate_id)?.is_some() {
4251            return Err(EventError::Storage(format!(
4252                "aggregate {}:{} already exists",
4253                aggregate_type, aggregate_id
4254            )));
4255        }
4256
4257        let meta = AggregateMeta::new(aggregate_type.to_string(), aggregate_id.to_string());
4258        let state: BTreeMap<String, String> = BTreeMap::new();
4259
4260        let start_meta = Instant::now();
4261        let meta_result = self
4262            .db
4263            .put(
4264                meta_key(aggregate_type, aggregate_id),
4265                serde_json::to_vec(&meta)?,
4266            )
4267            .map_err(|err| EventError::Storage(err.to_string()));
4268        let duration_meta = start_meta.elapsed().as_secs_f64();
4269        record_store_op(
4270            "rocksdb_put_meta",
4271            if meta_result.is_ok() { "ok" } else { "err" },
4272            duration_meta,
4273        );
4274        meta_result?;
4275
4276        let start_state = Instant::now();
4277        let state_result = self
4278            .db
4279            .put(
4280                state_key(aggregate_type, aggregate_id),
4281                serde_json::to_vec(&state)?,
4282            )
4283            .map_err(|err| EventError::Storage(err.to_string()));
4284        let duration_state = start_state.elapsed().as_secs_f64();
4285        record_store_op(
4286            "rocksdb_put_state",
4287            if state_result.is_ok() { "ok" } else { "err" },
4288            duration_state,
4289        );
4290        state_result?;
4291
4292        Ok(())
4293    }
4294
4295    pub fn remove_aggregate(&self, aggregate_type: &str, aggregate_id: &str) -> Result<()> {
4296        self.ensure_writable()?;
4297        let _guard = self.write_lock.lock();
4298
4299        let Some(meta) = self.load_meta(aggregate_type, aggregate_id)? else {
4300            return Err(EventError::AggregateNotFound);
4301        };
4302
4303        if meta.version > 0 {
4304            return Err(EventError::Storage(format!(
4305                "aggregate {}:{} cannot be removed while events exist",
4306                aggregate_type, aggregate_id
4307            )));
4308        }
4309
4310        let mut batch = WriteBatch::default();
4311        batch.delete(meta_key(aggregate_type, aggregate_id));
4312        batch.delete(state_key(aggregate_type, aggregate_id));
4313        self.sync_timestamp_indexes(&mut batch, AggregateIndex::Active, Some(&meta), None);
4314        // best-effort cleanup of reference index entries (tenant/domain handled by caller)
4315        self.write_batch(batch)
4316    }
4317
4318    pub fn update_reference_index(
4319        &self,
4320        tenant: &str,
4321        aggregate_type: &str,
4322        aggregate_id: &str,
4323        references: &[(String, String)],
4324    ) -> Result<()> {
4325        self.ensure_writable()?;
4326        let _guard = self.write_lock.lock();
4327
4328        let source_key = refsrc_key(tenant, aggregate_type, aggregate_id);
4329        let prev: Vec<StoredReferencePath> = self
4330            .db
4331            .get(source_key.clone())
4332            .map_err(|err| EventError::Storage(err.to_string()))?
4333            .map(|bytes| serde_json::from_slice(&bytes))
4334            .transpose()
4335            .map_err(|err| EventError::Serialization(err.to_string()))?
4336            .unwrap_or_default();
4337
4338        let mut batch = WriteBatch::default();
4339
4340        // Remove old target mappings
4341        for entry in prev {
4342            let target_key = reftgt_key(tenant, &entry.target);
4343            let existing: Vec<StoredReferrer> = self
4344                .db
4345                .get(&target_key)
4346                .map_err(|err| EventError::Storage(err.to_string()))?
4347                .map(|bytes| serde_json::from_slice(&bytes))
4348                .transpose()
4349                .map_err(|err| EventError::Serialization(err.to_string()))?
4350                .unwrap_or_default();
4351            let filtered: Vec<StoredReferrer> = existing
4352                .into_iter()
4353                .filter(|r| !(r.aggregate_type == aggregate_type && r.aggregate_id == aggregate_id))
4354                .collect();
4355            if filtered.is_empty() {
4356                batch.delete(target_key);
4357            } else {
4358                batch.put(target_key, serde_json::to_vec(&filtered)?);
4359            }
4360        }
4361
4362        // Add new mappings
4363        let mut new_paths = Vec::new();
4364        for (target, path) in references {
4365            let path_entry = StoredReferencePath {
4366                target: target.clone(),
4367                path: path.clone(),
4368            };
4369            new_paths.push(path_entry.clone());
4370
4371            let target_key = reftgt_key(tenant, target);
4372            let mut existing: Vec<StoredReferrer> = self
4373                .db
4374                .get(&target_key)
4375                .map_err(|err| EventError::Storage(err.to_string()))?
4376                .map(|bytes| serde_json::from_slice(&bytes))
4377                .transpose()
4378                .map_err(|err| EventError::Serialization(err.to_string()))?
4379                .unwrap_or_default();
4380            if !existing.iter().any(|r| {
4381                r.aggregate_type == aggregate_type
4382                    && r.aggregate_id == aggregate_id
4383                    && r.path == *path
4384            }) {
4385                existing.push(StoredReferrer {
4386                    aggregate_type: aggregate_type.to_string(),
4387                    aggregate_id: aggregate_id.to_string(),
4388                    path: path.clone(),
4389                });
4390                batch.put(target_key, serde_json::to_vec(&existing)?);
4391            }
4392        }
4393
4394        batch.put(source_key, serde_json::to_vec(&new_paths)?);
4395        self.write_batch(batch)
4396    }
4397
4398    pub fn clear_reference_index(
4399        &self,
4400        tenant: &str,
4401        aggregate_type: &str,
4402        aggregate_id: &str,
4403    ) -> Result<()> {
4404        self.update_reference_index(tenant, aggregate_type, aggregate_id, &[])
4405    }
4406
4407    pub fn referrers_for(
4408        &self,
4409        tenant: &str,
4410        target: &str,
4411    ) -> Result<Vec<(String, String, String)>> {
4412        let target_key = reftgt_key(tenant, target);
4413        let existing: Vec<StoredReferrer> = self
4414            .db
4415            .get(&target_key)
4416            .map_err(|err| EventError::Storage(err.to_string()))?
4417            .map(|bytes| serde_json::from_slice(&bytes))
4418            .transpose()
4419            .map_err(|err| EventError::Serialization(err.to_string()))?
4420            .unwrap_or_default();
4421        Ok(existing
4422            .into_iter()
4423            .map(|r| (r.aggregate_type, r.aggregate_id, r.path))
4424            .collect())
4425    }
4426
4427    pub fn referrers_for_any_tenant(
4428        &self,
4429        target: &str,
4430    ) -> Result<Vec<(String, String, String, String)>> {
4431        let mut matches = Vec::new();
4432        let prefix = PREFIX_REF_TARGET.as_bytes();
4433        for entry in self.db.prefix_iterator(prefix) {
4434            let (key, value) = entry.map_err(|err| EventError::Storage(err.to_string()))?;
4435            let segments: Vec<&[u8]> = key.split(|byte| *byte == SEP).collect();
4436            if segments.len() != 3 {
4437                continue;
4438            }
4439            let key_target = match str::from_utf8(segments[2]) {
4440                Ok(value) => value,
4441                Err(_) => continue,
4442            };
4443            if key_target != target {
4444                continue;
4445            }
4446            let tenant = match str::from_utf8(segments[1]) {
4447                Ok(value) => value.to_string(),
4448                Err(_) => continue,
4449            };
4450            let referrers: Vec<StoredReferrer> = serde_json::from_slice(&value).map_err(|err| {
4451                EventError::Serialization(format!("failed to decode referrer index: {err}"))
4452            })?;
4453            for r in referrers {
4454                matches.push((tenant.clone(), r.aggregate_type, r.aggregate_id, r.path));
4455            }
4456        }
4457        Ok(matches)
4458    }
4459
4460    fn load_state_map_from(
4461        &self,
4462        index: AggregateIndex,
4463        aggregate_type: &str,
4464        aggregate_id: &str,
4465    ) -> Result<BTreeMap<String, String>> {
4466        let start = Instant::now();
4467        let result = (|| {
4468            let key = state_key_for(index, aggregate_type, aggregate_id);
4469            let value = self
4470                .db
4471                .get(key)
4472                .map_err(|err| EventError::Storage(err.to_string()))?;
4473            if let Some(value) = value {
4474                self.decode_state_map(&value)
4475            } else {
4476                Ok(BTreeMap::new())
4477            }
4478        })();
4479        let duration = start.elapsed().as_secs_f64();
4480        record_store_op(
4481            "rocksdb_get_state",
4482            if result.is_ok() { "ok" } else { "err" },
4483            duration,
4484        );
4485        result
4486    }
4487
4488    fn maybe_load_state_map(
4489        &self,
4490        include_state: bool,
4491        index: AggregateIndex,
4492        aggregate_type: &str,
4493        aggregate_id: &str,
4494    ) -> Result<BTreeMap<String, String>> {
4495        if include_state {
4496            self.load_state_map_from(index, aggregate_type, aggregate_id)
4497        } else {
4498            Ok(BTreeMap::new())
4499        }
4500    }
4501
4502    fn load_state_map(
4503        &self,
4504        aggregate_type: &str,
4505        aggregate_id: &str,
4506    ) -> Result<BTreeMap<String, String>> {
4507        self.load_state_map_from(AggregateIndex::Active, aggregate_type, aggregate_id)
4508    }
4509}
4510
4511fn configure_db_options(options: &mut Options, read_only: bool) {
4512    if !read_only {
4513        if let Ok(parallelism) = std::thread::available_parallelism() {
4514            let workers = parallelism.get().max(2) as i32;
4515            options.increase_parallelism(workers);
4516            options.set_max_background_jobs(workers);
4517        }
4518        options.set_allow_concurrent_memtable_write(true);
4519        options.set_enable_write_thread_adaptive_yield(true);
4520        options.set_write_buffer_size(64 * 1024 * 1024);
4521        options.set_max_write_buffer_number(4);
4522        options.set_min_write_buffer_number_to_merge(2);
4523        options.set_level_zero_file_num_compaction_trigger(8);
4524        options.set_level_zero_slowdown_writes_trigger(17);
4525        options.set_level_zero_stop_writes_trigger(24);
4526        options.set_target_file_size_base(128 * 1024 * 1024);
4527        options.set_max_total_wal_size(256 * 1024 * 1024);
4528    }
4529    options.set_bytes_per_sync(2 * 1024 * 1024);
4530    options.set_optimize_filters_for_hits(true);
4531}
4532
4533fn meta_prefix_for(index: AggregateIndex) -> Vec<u8> {
4534    key_with_segments(&[index.meta_prefix()])
4535}
4536
4537fn meta_prefix() -> Vec<u8> {
4538    meta_prefix_for(AggregateIndex::Active)
4539}
4540
4541fn meta_key(aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4542    meta_key_for(AggregateIndex::Active, aggregate_type, aggregate_id)
4543}
4544
4545fn meta_archived_key(aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4546    meta_key_for(AggregateIndex::Archived, aggregate_type, aggregate_id)
4547}
4548
4549fn state_key(aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4550    state_key_for(AggregateIndex::Active, aggregate_type, aggregate_id)
4551}
4552
4553fn state_archived_key(aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4554    state_key_for(AggregateIndex::Archived, aggregate_type, aggregate_id)
4555}
4556
4557fn meta_key_for(index: AggregateIndex, aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4558    key_with_segments(&[index.meta_prefix(), aggregate_type, aggregate_id])
4559}
4560
4561fn state_key_for(index: AggregateIndex, aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4562    key_with_segments(&[index.state_prefix(), aggregate_type, aggregate_id])
4563}
4564
4565fn event_prefix_for(index: AggregateIndex, aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4566    let prefix = match index {
4567        AggregateIndex::Active => PREFIX_EVENT,
4568        AggregateIndex::Archived => PREFIX_EVENT_ARCHIVED,
4569    };
4570    let mut key = key_with_segments(&[prefix, aggregate_type, aggregate_id]);
4571    key.push(SEP);
4572    key
4573}
4574
4575fn event_key_for(
4576    index: AggregateIndex,
4577    aggregate_type: &str,
4578    aggregate_id: &str,
4579    version: u64,
4580) -> Vec<u8> {
4581    let mut key = event_prefix_for(index, aggregate_type, aggregate_id);
4582    key.extend_from_slice(&version.to_be_bytes());
4583    key
4584}
4585
4586fn event_key(aggregate_type: &str, aggregate_id: &str, version: u64) -> Vec<u8> {
4587    event_key_for(
4588        AggregateIndex::Active,
4589        aggregate_type,
4590        aggregate_id,
4591        version,
4592    )
4593}
4594
4595fn timestamp_index_prefix(kind: TimestampIndexKind, index: AggregateIndex) -> Vec<u8> {
4596    key_with_segments(&[kind.prefix(index)])
4597}
4598
4599fn timestamp_index_key(
4600    kind: TimestampIndexKind,
4601    index: AggregateIndex,
4602    timestamp_ms: i64,
4603    aggregate_type: &str,
4604    aggregate_id: &str,
4605) -> Vec<u8> {
4606    let mut key = timestamp_index_prefix(kind, index);
4607    key.push(SEP);
4608    key.extend_from_slice(&timestamp_ms.to_be_bytes());
4609    key.push(SEP);
4610    key.extend_from_slice(aggregate_type.as_bytes());
4611    key.push(SEP);
4612    key.extend_from_slice(aggregate_id.as_bytes());
4613    key
4614}
4615
4616fn parse_timestamp_index_entry(prefix: &[u8], key: &[u8]) -> Option<TimestampIndexEntry> {
4617    if !key.starts_with(prefix) {
4618        return None;
4619    }
4620    let mut cursor = prefix.len();
4621    if key.get(cursor)? != &SEP {
4622        return None;
4623    }
4624    cursor += 1;
4625    let end_ts = cursor.checked_add(8)?;
4626    let timestamp_bytes: [u8; 8] = key.get(cursor..end_ts)?.try_into().ok()?;
4627    let timestamp_ms = i64::from_be_bytes(timestamp_bytes);
4628    cursor = end_ts;
4629    if key.get(cursor)? != &SEP {
4630        return None;
4631    }
4632    cursor += 1;
4633    let next_sep = key[cursor..]
4634        .iter()
4635        .position(|byte| *byte == SEP)
4636        .map(|offset| cursor + offset)?;
4637    let aggregate_type = std::str::from_utf8(&key[cursor..next_sep])
4638        .ok()?
4639        .to_string();
4640    cursor = next_sep + 1;
4641    if cursor > key.len() {
4642        return None;
4643    }
4644    let aggregate_id = std::str::from_utf8(&key[cursor..]).ok()?.to_string();
4645    Some(TimestampIndexEntry {
4646        aggregate_type,
4647        aggregate_id,
4648        timestamp_ms,
4649    })
4650}
4651
4652fn meta_timestamp_ms(meta: &AggregateMeta, kind: TimestampIndexKind) -> Option<i64> {
4653    match kind {
4654        TimestampIndexKind::CreatedAt => meta.created_at.as_ref().map(|dt| dt.timestamp_millis()),
4655        TimestampIndexKind::UpdatedAt => meta.updated_at.as_ref().map(|dt| dt.timestamp_millis()),
4656    }
4657}
4658
4659fn timestamp_index_metric(kind: TimestampIndexKind, index: AggregateIndex) -> &'static str {
4660    match (kind, index) {
4661        (TimestampIndexKind::CreatedAt, AggregateIndex::Active) => {
4662            "rocksdb_iter_meta_created_index"
4663        }
4664        (TimestampIndexKind::CreatedAt, AggregateIndex::Archived) => {
4665            "rocksdb_iter_meta_created_index_archived"
4666        }
4667        (TimestampIndexKind::UpdatedAt, AggregateIndex::Active) => {
4668            "rocksdb_iter_meta_updated_index"
4669        }
4670        (TimestampIndexKind::UpdatedAt, AggregateIndex::Archived) => {
4671            "rocksdb_iter_meta_updated_index_archived"
4672        }
4673    }
4674}
4675
4676fn compare_timestamp_entries(
4677    lhs: &TimestampIndexEntry,
4678    rhs: &TimestampIndexEntry,
4679    descending: bool,
4680) -> StdOrdering {
4681    let mut ordering = lhs.timestamp_ms.cmp(&rhs.timestamp_ms);
4682    if ordering == StdOrdering::Equal {
4683        ordering = lhs.aggregate_type.cmp(&rhs.aggregate_type);
4684    }
4685    if ordering == StdOrdering::Equal {
4686        ordering = lhs.aggregate_id.cmp(&rhs.aggregate_id);
4687    }
4688    if descending {
4689        ordering.reverse()
4690    } else {
4691        ordering
4692    }
4693}
4694
4695fn compare_timestamp_values(
4696    lhs_ts: i64,
4697    lhs_type: &str,
4698    lhs_id: &str,
4699    rhs_ts: i64,
4700    rhs_type: &str,
4701    rhs_id: &str,
4702) -> StdOrdering {
4703    let mut ordering = lhs_ts.cmp(&rhs_ts);
4704    if ordering == StdOrdering::Equal {
4705        ordering = lhs_type.cmp(rhs_type);
4706    }
4707    if ordering == StdOrdering::Equal {
4708        ordering = lhs_id.cmp(rhs_id);
4709    }
4710    ordering
4711}
4712
4713fn event_prefix_for_scope(index: AggregateIndex, scope: EventQueryScope<'_>) -> Vec<u8> {
4714    match scope {
4715        EventQueryScope::All => key_with_segments(&[index.event_prefix()]),
4716        EventQueryScope::AggregateType(aggregate_type) => {
4717            key_with_segments(&[index.event_prefix(), aggregate_type])
4718        }
4719        EventQueryScope::Aggregate {
4720            aggregate_type,
4721            aggregate_id,
4722        } => key_with_segments(&[index.event_prefix(), aggregate_type, aggregate_id]),
4723    }
4724}
4725
4726fn event_cursor_matches_scope(cursor: &EventCursor, scope: EventQueryScope<'_>) -> bool {
4727    match scope {
4728        EventQueryScope::All => true,
4729        EventQueryScope::AggregateType(aggregate_type) => cursor.aggregate_type() == aggregate_type,
4730        EventQueryScope::Aggregate {
4731            aggregate_type,
4732            aggregate_id,
4733        } => cursor.aggregate_type() == aggregate_type && cursor.aggregate_id() == aggregate_id,
4734    }
4735}
4736
4737fn snapshot_key(aggregate_type: &str, aggregate_id: &str, created_at: DateTime<Utc>) -> Vec<u8> {
4738    let mut key = snapshot_prefix(aggregate_type, aggregate_id);
4739    key.push(SEP);
4740    key.extend_from_slice(&created_at.timestamp_millis().to_be_bytes());
4741    key
4742}
4743
4744fn snapshot_prefix(aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4745    key_with_segments(&[PREFIX_SNAPSHOT, aggregate_type, aggregate_id])
4746}
4747
4748fn snapshot_index_key(snapshot_id: SnowflakeId) -> Vec<u8> {
4749    key_with_segments(&[PREFIX_SNAPSHOT_ID, &snapshot_id.to_string()])
4750}
4751
4752fn parse_event_version(key: &[u8]) -> Result<u64> {
4753    if key.len() < 8 {
4754        return Err(EventError::Storage("event key too short".into()));
4755    }
4756    let mut buf = [0u8; 8];
4757    buf.copy_from_slice(&key[key.len() - 8..]);
4758    Ok(u64::from_be_bytes(buf))
4759}
4760
4761fn key_with_segments(parts: &[&str]) -> Vec<u8> {
4762    let mut key = Vec::new();
4763    let mut iter = parts.iter();
4764    if let Some(first) = iter.next() {
4765        key.extend_from_slice(first.as_bytes());
4766    }
4767    for part in iter {
4768        key.push(SEP);
4769        key.extend_from_slice(part.as_bytes());
4770    }
4771    key
4772}
4773
4774fn refsrc_key(tenant: &str, aggregate_type: &str, aggregate_id: &str) -> Vec<u8> {
4775    key_with_segments(&[PREFIX_REF_SOURCE, tenant, aggregate_type, aggregate_id])
4776}
4777
4778fn reftgt_key(tenant: &str, target: &str) -> Vec<u8> {
4779    key_with_segments(&[PREFIX_REF_TARGET, tenant, target])
4780}
4781
4782fn update_reference_index_inner(
4783    db: &DBWithThreadMode<MultiThreaded>,
4784    batch: &mut WriteBatch,
4785    tenant: &str,
4786    aggregate_type: &str,
4787    aggregate_id: &str,
4788    references: &[(String, String)],
4789) -> Result<()> {
4790    let source_key = refsrc_key(tenant, aggregate_type, aggregate_id);
4791    let prev: Vec<StoredReferencePath> = db
4792        .get(source_key.clone())
4793        .map_err(|err| EventError::Storage(err.to_string()))?
4794        .map(|bytes| serde_json::from_slice(&bytes))
4795        .transpose()
4796        .map_err(|err| EventError::Serialization(err.to_string()))?
4797        .unwrap_or_default();
4798
4799    fn load_target_referrers<'a>(
4800        db: &DBWithThreadMode<MultiThreaded>,
4801        tenant: &str,
4802        target: &str,
4803        cache: &'a mut HashMap<String, Vec<StoredReferrer>>,
4804    ) -> Result<&'a mut Vec<StoredReferrer>> {
4805        match cache.entry(target.to_string()) {
4806            Entry::Occupied(entry) => Ok(entry.into_mut()),
4807            Entry::Vacant(entry) => {
4808                let target_key = reftgt_key(tenant, target);
4809                let existing: Vec<StoredReferrer> = db
4810                    .get(&target_key)
4811                    .map_err(|err| EventError::Storage(err.to_string()))?
4812                    .map(|bytes| serde_json::from_slice(&bytes))
4813                    .transpose()
4814                    .map_err(|err| EventError::Serialization(err.to_string()))?
4815                    .unwrap_or_default();
4816                Ok(entry.insert(existing))
4817            }
4818        }
4819    }
4820
4821    let mut target_referrers: HashMap<String, Vec<StoredReferrer>> = HashMap::new();
4822    let mut updated_targets = BTreeSet::new();
4823
4824    // Remove old target mappings
4825    for entry in prev {
4826        let referrers = load_target_referrers(db, tenant, &entry.target, &mut target_referrers)?;
4827        let len_before = referrers.len();
4828        referrers
4829            .retain(|r| !(r.aggregate_type == aggregate_type && r.aggregate_id == aggregate_id));
4830        if referrers.len() != len_before {
4831            updated_targets.insert(entry.target.clone());
4832        }
4833    }
4834
4835    // Add new mappings
4836    let mut new_paths = Vec::new();
4837    for (target, path) in references {
4838        let path_entry = StoredReferencePath {
4839            target: target.clone(),
4840            path: path.clone(),
4841        };
4842        new_paths.push(path_entry);
4843
4844        let referrers = load_target_referrers(db, tenant, target, &mut target_referrers)?;
4845        if !referrers.iter().any(|r| {
4846            r.aggregate_type == aggregate_type && r.aggregate_id == aggregate_id && r.path == *path
4847        }) {
4848            referrers.push(StoredReferrer {
4849                aggregate_type: aggregate_type.to_string(),
4850                aggregate_id: aggregate_id.to_string(),
4851                path: path.clone(),
4852            });
4853            updated_targets.insert(target.clone());
4854        }
4855    }
4856
4857    for target in updated_targets {
4858        let target_key = reftgt_key(tenant, &target);
4859        if let Some(referrers) = target_referrers.get(&target) {
4860            if referrers.is_empty() {
4861                batch.delete(target_key);
4862            } else {
4863                batch.put(target_key, serde_json::to_vec(referrers)?);
4864            }
4865        }
4866    }
4867
4868    batch.put(source_key, serde_json::to_vec(&new_paths)?);
4869    Ok(())
4870}
4871
4872fn record_store_op(operation: &'static str, status: &'static str, duration: f64) {
4873    let labels = [("operation", operation), ("status", status)];
4874    counter!("eventdbx_store_operations_total", &labels).increment(1);
4875    histogram!("eventdbx_store_operation_duration_seconds", &labels).record(duration);
4876}
4877
4878fn hash_event(
4879    aggregate_type: &str,
4880    aggregate_id: &str,
4881    version: u64,
4882    event_type: &str,
4883    payload: &BTreeMap<String, String>,
4884) -> String {
4885    use sha2::{Digest, Sha256};
4886
4887    let mut hasher = Sha256::new();
4888    hasher.update(aggregate_type.as_bytes());
4889    hasher.update(aggregate_id.as_bytes());
4890    hasher.update(version.to_be_bytes());
4891    hasher.update(event_type.as_bytes());
4892
4893    for (key, value) in payload {
4894        hasher.update(key.as_bytes());
4895        hasher.update(value.as_bytes());
4896    }
4897
4898    hex::encode(hasher.finalize())
4899}
4900
4901pub fn payload_to_map(value: &Value) -> BTreeMap<String, String> {
4902    fn normalize(value: &Value) -> String {
4903        match value {
4904            Value::String(s) => s.clone(),
4905            Value::Null => String::new(),
4906            _ => value.to_string(),
4907        }
4908    }
4909
4910    match value {
4911        Value::Object(map) => map
4912            .iter()
4913            .map(|(k, v)| (k.clone(), normalize(v)))
4914            .collect::<BTreeMap<_, _>>(),
4915        other => {
4916            let mut map = BTreeMap::new();
4917            if !other.is_null() {
4918                map.insert("_value".into(), normalize(other));
4919            }
4920            map
4921        }
4922    }
4923}
4924
4925#[cfg(test)]
4926mod tests {
4927    use super::*;
4928
4929    #[test]
4930    fn computes_consistent_merkle_root() {
4931        let root = compute_merkle_root(&[]);
4932        assert_eq!(root, empty_root());
4933
4934        let root = compute_merkle_root(&["abc".into()]);
4935        let root2 = compute_merkle_root(&["abc".into()]);
4936        assert_eq!(root, root2);
4937    }
4938
4939    #[test]
4940    fn append_and_retrieve_events() {
4941        let dir = tempfile::tempdir().unwrap();
4942        let path = dir.path().join("event_store");
4943
4944        {
4945            let store = EventStore::open(path.clone(), None, 0).unwrap();
4946
4947            let payload = serde_json::json!({ "name": "Alice" });
4948
4949            let record = store
4950                .append(AppendEvent {
4951                    aggregate_type: "patient".into(),
4952                    aggregate_id: "patient-1".into(),
4953                    event_type: "patient-created".into(),
4954                    event_type_raw: None,
4955                    payload: payload.clone(),
4956                    metadata: None,
4957                    issued_by: None,
4958                    note: None,
4959                    tenant: "default".into(),
4960                    reference_targets: Vec::new(),
4961                })
4962                .unwrap();
4963
4964            assert_eq!(record.version, 1);
4965            assert_eq!(record.payload["name"], "Alice");
4966
4967            let state = store.get_aggregate_state("patient", "patient-1").unwrap();
4968            assert_eq!(state.version, 1);
4969            assert_eq!(state.state["name"], "Alice");
4970            assert!(!state.archived);
4971
4972            let events = store.list_events("patient", "patient-1").unwrap();
4973            assert_eq!(events.len(), 1);
4974        }
4975    }
4976
4977    #[test]
4978    fn transaction_appends_multiple_events() {
4979        let dir = tempfile::tempdir().unwrap();
4980        let path = dir.path().join("event_store");
4981        let store = EventStore::open(path, None, 0).unwrap();
4982
4983        let mut tx = store.transaction().unwrap();
4984        let first = tx
4985            .append(AppendEvent {
4986                aggregate_type: "order".into(),
4987                aggregate_id: "order-42".into(),
4988                event_type: "order-created".into(),
4989                event_type_raw: None,
4990                payload: serde_json::json!({ "status": "processing" }),
4991                metadata: None,
4992                issued_by: None,
4993                note: None,
4994                tenant: "default".into(),
4995                reference_targets: Vec::new(),
4996            })
4997            .unwrap();
4998        assert_eq!(first.version, 1);
4999
5000        let second = tx
5001            .append(AppendEvent {
5002                aggregate_type: "order".into(),
5003                aggregate_id: "order-42".into(),
5004                event_type: "order-updated".into(),
5005                event_type_raw: None,
5006                payload: serde_json::json!({
5007                    "status": "shipped",
5008                    "tracking": "abc123"
5009                }),
5010                metadata: None,
5011                issued_by: None,
5012                note: None,
5013                tenant: "default".into(),
5014                reference_targets: Vec::new(),
5015            })
5016            .unwrap();
5017        assert_eq!(second.version, 2);
5018
5019        let committed = tx.commit().unwrap();
5020        assert_eq!(committed.len(), 2);
5021        assert_eq!(committed[0].version, 1);
5022        assert_eq!(committed[1].version, 2);
5023
5024        let state = store.get_aggregate_state("order", "order-42").unwrap();
5025        assert_eq!(state.version, 2);
5026        assert_eq!(state.state["status"], "shipped");
5027        assert_eq!(state.state["tracking"], "abc123");
5028
5029        let events = store.list_events("order", "order-42").unwrap();
5030        assert_eq!(events.len(), 2);
5031    }
5032
5033    #[test]
5034    fn aggregate_sort_key_order_detection() {
5035        assert!(aggregate_sort_matches_key_order(&[]));
5036
5037        let sorts = vec![AggregateSort {
5038            field: AggregateSortField::AggregateType,
5039            descending: false,
5040        }];
5041        assert!(aggregate_sort_matches_key_order(&sorts));
5042
5043        let sorts = vec![
5044            AggregateSort {
5045                field: AggregateSortField::AggregateType,
5046                descending: false,
5047            },
5048            AggregateSort {
5049                field: AggregateSortField::AggregateId,
5050                descending: false,
5051            },
5052        ];
5053        assert!(aggregate_sort_matches_key_order(&sorts));
5054
5055        let invalid = vec![AggregateSort {
5056            field: AggregateSortField::AggregateId,
5057            descending: false,
5058        }];
5059        assert!(!aggregate_sort_matches_key_order(&invalid));
5060
5061        let descending = vec![AggregateSort {
5062            field: AggregateSortField::AggregateType,
5063            descending: true,
5064        }];
5065        assert!(!aggregate_sort_matches_key_order(&descending));
5066    }
5067
5068    #[test]
5069    fn event_sort_key_order_detection() {
5070        assert!(event_sort_matches_key_order(&[]));
5071
5072        let sorts = vec![EventSort {
5073            field: EventSortField::AggregateType,
5074            descending: false,
5075        }];
5076        assert!(event_sort_matches_key_order(&sorts));
5077
5078        let sorts = vec![
5079            EventSort {
5080                field: EventSortField::AggregateType,
5081                descending: false,
5082            },
5083            EventSort {
5084                field: EventSortField::AggregateId,
5085                descending: false,
5086            },
5087            EventSort {
5088                field: EventSortField::Version,
5089                descending: false,
5090            },
5091        ];
5092        assert!(event_sort_matches_key_order(&sorts));
5093
5094        let invalid_field = vec![EventSort {
5095            field: EventSortField::EventType,
5096            descending: false,
5097        }];
5098        assert!(!event_sort_matches_key_order(&invalid_field));
5099
5100        let descending = vec![EventSort {
5101            field: EventSortField::AggregateType,
5102            descending: true,
5103        }];
5104        assert!(!event_sort_matches_key_order(&descending));
5105    }
5106
5107    #[test]
5108    fn aggregates_sort_by_created_at_desc() {
5109        let dir = tempfile::tempdir().unwrap();
5110        let path = dir.path().join("event_store");
5111        let store = EventStore::open(path, None, 0).unwrap();
5112        let base = Utc::now();
5113        let cases = vec![
5114            ("agg-1", base + chrono::Duration::milliseconds(10)),
5115            ("agg-2", base + chrono::Duration::milliseconds(20)),
5116            ("agg-3", base + chrono::Duration::milliseconds(30)),
5117        ];
5118
5119        for (idx, (aggregate_id, created_at)) in cases.iter().enumerate() {
5120            import_history(
5121                &store,
5122                aggregate_id,
5123                &[created_at.to_owned()],
5124                (idx as u64) * 10,
5125            );
5126        }
5127
5128        let sorts = vec![AggregateSort {
5129            field: AggregateSortField::CreatedAt,
5130            descending: true,
5131        }];
5132        let aggregates = store.aggregates_paginated_with_transform(
5133            0,
5134            Some(3),
5135            Some(&sorts),
5136            AggregateQueryScope::ActiveOnly,
5137            None,
5138            |aggregate| Some(aggregate),
5139        );
5140        let ids: Vec<_> = aggregates.into_iter().map(|agg| agg.aggregate_id).collect();
5141        assert_eq!(ids, vec!["agg-3", "agg-2", "agg-1"]);
5142    }
5143
5144    #[test]
5145    fn aggregates_sort_by_updated_at_desc() {
5146        let dir = tempfile::tempdir().unwrap();
5147        let path = dir.path().join("event_store");
5148        let store = EventStore::open(path, None, 0).unwrap();
5149        let base = Utc::now();
5150        import_history(
5151            &store,
5152            "agg-1",
5153            &[
5154                base + chrono::Duration::milliseconds(5),
5155                base + chrono::Duration::milliseconds(50),
5156            ],
5157            100,
5158        );
5159        import_history(
5160            &store,
5161            "agg-2",
5162            &[
5163                base + chrono::Duration::milliseconds(10),
5164                base + chrono::Duration::milliseconds(30),
5165            ],
5166            200,
5167        );
5168
5169        let sorts = vec![AggregateSort {
5170            field: AggregateSortField::UpdatedAt,
5171            descending: true,
5172        }];
5173        let aggregates = store.aggregates_paginated_with_transform(
5174            0,
5175            Some(2),
5176            Some(&sorts),
5177            AggregateQueryScope::ActiveOnly,
5178            None,
5179            |aggregate| Some(aggregate),
5180        );
5181        let ids: Vec<_> = aggregates.into_iter().map(|agg| agg.aggregate_id).collect();
5182        assert_eq!(ids, vec!["agg-1", "agg-2"]);
5183    }
5184
5185    fn import_history(
5186        store: &EventStore,
5187        aggregate_id: &str,
5188        timestamps: &[DateTime<Utc>],
5189        base_event_id: u64,
5190    ) {
5191        let aggregate_type = "chronicle";
5192        let event_type = "created";
5193        let mut hashes = Vec::new();
5194        for (idx, timestamp) in timestamps.iter().enumerate() {
5195            let version = (idx as u64) + 1;
5196            let payload = serde_json::json!({ "seq": version });
5197            let payload_map = payload_to_map(&payload);
5198            let hash = hash_event(
5199                aggregate_type,
5200                aggregate_id,
5201                version,
5202                event_type,
5203                &payload_map,
5204            );
5205            hashes.push(hash.clone());
5206            let merkle_root = compute_merkle_root(&hashes);
5207            let record = EventRecord {
5208                aggregate_type: aggregate_type.into(),
5209                aggregate_id: aggregate_id.to_string(),
5210                event_type: event_type.into(),
5211                event_type_raw: None,
5212                payload: payload.clone(),
5213                extensions: None,
5214                metadata: EventMetadata {
5215                    event_id: SnowflakeId::from_u64(base_event_id + version),
5216                    created_at: *timestamp,
5217                    issued_by: None,
5218                    note: None,
5219                },
5220                version,
5221                hash,
5222                merkle_root,
5223            };
5224            store.append_imported_event(record).unwrap();
5225        }
5226    }
5227
5228    #[test]
5229    fn transaction_without_commit_discards_changes() {
5230        let dir = tempfile::tempdir().unwrap();
5231        let path = dir.path().join("event_store");
5232        let store = EventStore::open(path.clone(), None, 0).unwrap();
5233
5234        {
5235            let mut tx = store.transaction().unwrap();
5236            tx.append(AppendEvent {
5237                aggregate_type: "invoice".into(),
5238                aggregate_id: "inv-1".into(),
5239                event_type: "invoice-created".into(),
5240                event_type_raw: None,
5241                payload: serde_json::json!({ "total": "100.00" }),
5242                metadata: None,
5243                issued_by: None,
5244                note: None,
5245                tenant: "default".into(),
5246                reference_targets: Vec::new(),
5247            })
5248            .unwrap();
5249        }
5250
5251        let err = store.list_events("invoice", "inv-1").unwrap_err();
5252        assert!(matches!(err, crate::error::EventError::AggregateNotFound));
5253    }
5254
5255    #[test]
5256    fn snapshots_and_archive_flow() {
5257        let dir = tempfile::tempdir().unwrap();
5258        let path = dir.path().join("event_store");
5259        let store = EventStore::open(path, None, 0).unwrap();
5260
5261        let payload = serde_json::json!({ "status": "active" });
5262
5263        store
5264            .append(AppendEvent {
5265                aggregate_type: "order".into(),
5266                aggregate_id: "order-1".into(),
5267                event_type: "order-created".into(),
5268                event_type_raw: None,
5269                payload,
5270                metadata: None,
5271                issued_by: None,
5272                note: None,
5273                tenant: "default".into(),
5274                reference_targets: Vec::new(),
5275            })
5276            .unwrap();
5277
5278        let snapshot = store
5279            .create_snapshot("order", "order-1", Some("initial".into()))
5280            .unwrap();
5281        assert_eq!(snapshot.version, 1);
5282        assert_eq!(snapshot.comment.as_deref(), Some("initial"));
5283
5284        let state = store
5285            .set_archive("order", "order-1", true, Some("closed".into()))
5286            .unwrap();
5287        assert!(state.archived);
5288
5289        let err = store
5290            .append(AppendEvent {
5291                aggregate_type: "order".into(),
5292                aggregate_id: "order-1".into(),
5293                event_type: "order-updated".into(),
5294                event_type_raw: None,
5295                payload: serde_json::json!({}),
5296                metadata: None,
5297                issued_by: None,
5298                note: None,
5299                tenant: "default".into(),
5300                reference_targets: Vec::new(),
5301            })
5302            .unwrap_err();
5303        assert!(matches!(err, crate::error::EventError::AggregateArchived));
5304
5305        let state = store.set_archive("order", "order-1", false, None).unwrap();
5306        assert!(!state.archived);
5307
5308        // Verify snapshot lookup by id uses index.
5309        let found = store
5310            .find_snapshot_by_id(snapshot.snapshot_id.expect("snapshot id"))
5311            .unwrap();
5312        assert!(found.is_some());
5313    }
5314
5315    #[test]
5316    fn append_event_with_note_persists_metadata() {
5317        let dir = tempfile::tempdir().unwrap();
5318        let path = dir.path().join("event_store");
5319        let store = EventStore::open(path, None, 0).unwrap();
5320
5321        let note_text = "initial import".to_string();
5322        let record = store
5323            .append(AppendEvent {
5324                aggregate_type: "account".into(),
5325                aggregate_id: "acct-1".into(),
5326                event_type: "account-created".into(),
5327                event_type_raw: None,
5328                payload: serde_json::json!({ "status": "active" }),
5329                metadata: None,
5330                issued_by: None,
5331                note: Some(note_text.clone()),
5332                tenant: "default".into(),
5333                reference_targets: Vec::new(),
5334            })
5335            .unwrap();
5336
5337        assert_eq!(record.metadata.note.as_deref(), Some(note_text.as_str()));
5338    }
5339
5340    #[test]
5341    fn reject_event_note_exceeding_limit() {
5342        let dir = tempfile::tempdir().unwrap();
5343        let path = dir.path().join("event_store");
5344        let store = EventStore::open(path, None, 0).unwrap();
5345
5346        let long_note = "x".repeat(MAX_EVENT_NOTE_LENGTH + 1);
5347        let err = store
5348            .append(AppendEvent {
5349                aggregate_type: "account".into(),
5350                aggregate_id: "acct-2".into(),
5351                event_type: "account-created".into(),
5352                event_type_raw: None,
5353                payload: serde_json::json!({ "status": "pending" }),
5354                metadata: None,
5355                issued_by: None,
5356                note: Some(long_note),
5357                tenant: "default".into(),
5358                reference_targets: Vec::new(),
5359            })
5360            .unwrap_err();
5361
5362        assert!(matches!(err, crate::error::EventError::InvalidSchema(_)));
5363    }
5364
5365    #[test]
5366    fn reject_event_type_raw_exceeding_limit() {
5367        let dir = tempfile::tempdir().unwrap();
5368        let path = dir.path().join("event_store");
5369        let store = EventStore::open(path, None, 0).unwrap();
5370
5371        let too_long = "x".repeat(MAX_EVENT_TYPE_LENGTH + 1);
5372        let err = store
5373            .append(AppendEvent {
5374                aggregate_type: "account".into(),
5375                aggregate_id: "acct-3".into(),
5376                event_type: "account-created".into(),
5377                event_type_raw: Some(too_long),
5378                payload: serde_json::json!({ "status": "active" }),
5379                metadata: None,
5380                issued_by: None,
5381                note: None,
5382                tenant: "default".into(),
5383                reference_targets: Vec::new(),
5384            })
5385            .unwrap_err();
5386
5387        assert!(matches!(err, crate::error::EventError::InvalidSchema(_)));
5388    }
5389
5390    #[test]
5391    fn append_event_with_extensions_persists() {
5392        let dir = tempfile::tempdir().unwrap();
5393        let path = dir.path().join("event_store");
5394        let store = EventStore::open(path.clone(), None, 0).unwrap();
5395
5396        let metadata = serde_json::json!({"@plugin": {"mode": "debug"}});
5397        let record = store
5398            .append(AppendEvent {
5399                aggregate_type: "account".into(),
5400                aggregate_id: "acct-3".into(),
5401                event_type: "account-created".into(),
5402                event_type_raw: None,
5403                payload: serde_json::json!({ "status": "active" }),
5404                metadata: Some(metadata.clone()),
5405                issued_by: None,
5406                note: None,
5407                tenant: "default".into(),
5408                reference_targets: Vec::new(),
5409            })
5410            .unwrap();
5411
5412        assert_eq!(record.extensions, Some(metadata.clone()));
5413
5414        drop(store);
5415        let reopened = EventStore::open(path, None, 0).unwrap();
5416        let events = reopened.list_events("account", "acct-3").unwrap();
5417        assert_eq!(events.len(), 1);
5418        assert_eq!(events[0].extensions, Some(metadata));
5419    }
5420
5421    #[test]
5422    fn aggregate_state_includes_extensions_and_flattens() {
5423        let dir = tempfile::tempdir().unwrap();
5424        let path = dir.path().join("event_store");
5425        let store = EventStore::open(path, None, 0).unwrap();
5426
5427        let metadata = serde_json::json!({
5428            "@createdBy": "system",
5429            "@trace": { "id": "abc123" }
5430        });
5431        store
5432            .append(AppendEvent {
5433                aggregate_type: "account".into(),
5434                aggregate_id: "acct-4".into(),
5435                event_type: "account-created".into(),
5436                event_type_raw: None,
5437                payload: serde_json::json!({ "status": "active" }),
5438                metadata: Some(metadata),
5439                issued_by: None,
5440                note: None,
5441                tenant: "default".into(),
5442                reference_targets: Vec::new(),
5443            })
5444            .unwrap();
5445
5446        let state = store
5447            .get_aggregate_state("account", "acct-4")
5448            .expect("state should be readable");
5449
5450        assert_eq!(
5451            state.extensions.get("@created_by"),
5452            Some(&serde_json::json!("system"))
5453        );
5454
5455        let serialized =
5456            serde_json::to_value(&state).expect("aggregate state should serialize to JSON");
5457        assert_eq!(
5458            serialized.get("@created_by"),
5459            Some(&serde_json::json!("system"))
5460        );
5461        assert_eq!(
5462            serialized
5463                .get("@trace")
5464                .and_then(Value::as_object)
5465                .and_then(|trace| trace.get("id")),
5466            Some(&serde_json::json!("abc123"))
5467        );
5468        assert!(serialized.get("@trace").is_some());
5469    }
5470
5471    #[test]
5472    fn aggregate_extensions_merge_across_events() {
5473        let dir = tempfile::tempdir().unwrap();
5474        let path = dir.path().join("event_store");
5475        let store = EventStore::open(path, None, 0).unwrap();
5476
5477        store
5478            .append(AppendEvent {
5479                aggregate_type: "person".into(),
5480                aggregate_id: "p-1".into(),
5481                event_type: "person-created".into(),
5482                event_type_raw: None,
5483                payload: serde_json::json!({ "firstName": "Ada" }),
5484                metadata: Some(serde_json::json!({ "@createdBy": "seed" })),
5485                issued_by: None,
5486                note: None,
5487                tenant: "default".into(),
5488                reference_targets: Vec::new(),
5489            })
5490            .unwrap();
5491
5492        store
5493            .append(AppendEvent {
5494                aggregate_type: "person".into(),
5495                aggregate_id: "p-1".into(),
5496                event_type: "person-updated".into(),
5497                event_type_raw: None,
5498                payload: serde_json::json!({ "lastName": "Lovelace" }),
5499                metadata: Some(serde_json::json!({ "@updatedBy": "cli" })),
5500                issued_by: None,
5501                note: None,
5502                tenant: "default".into(),
5503                reference_targets: Vec::new(),
5504            })
5505            .unwrap();
5506
5507        let state = store
5508            .get_aggregate_state("person", "p-1")
5509            .expect("state should be readable");
5510
5511        assert_eq!(state.version, 2);
5512        assert_eq!(
5513            state.extensions.get("@created_by"),
5514            Some(&serde_json::json!("seed"))
5515        );
5516        assert_eq!(
5517            state.extensions.get("@updated_by"),
5518            Some(&serde_json::json!("cli"))
5519        );
5520    }
5521
5522    #[test]
5523    fn output_value_inlines_extensions_and_hides_raw_type() {
5524        let record = EventRecord {
5525            aggregate_type: "person".into(),
5526            aggregate_id: "p-1".into(),
5527            event_type: "person_updated".into(),
5528            event_type_raw: Some("person.updated".into()),
5529            payload: serde_json::json!({ "status": "active" }),
5530            extensions: Some(serde_json::json!({
5531                "@trace": { "id": "abc123" },
5532                "@channel": "beta"
5533            })),
5534            metadata: EventMetadata {
5535                event_id: SnowflakeId::from_u64(42),
5536                created_at: Utc::now(),
5537                issued_by: None,
5538                note: None,
5539            },
5540            version: 7,
5541            hash: "hash".into(),
5542            merkle_root: "root".into(),
5543        };
5544
5545        let rendered = record.to_output_value().expect("serializes for output");
5546        let object = rendered.as_object().expect("event renders as object");
5547        assert!(
5548            !object.contains_key("extensions"),
5549            "extensions should be folded into metadata"
5550        );
5551        assert!(
5552            !object.contains_key("event_type_raw"),
5553            "raw event type should be hidden in output"
5554        );
5555        let metadata = object
5556            .get("metadata")
5557            .and_then(Value::as_object)
5558            .expect("metadata should be present");
5559        assert_eq!(metadata.get("@channel"), Some(&serde_json::json!("beta")));
5560        assert_eq!(
5561            metadata
5562                .get("@trace")
5563                .and_then(Value::as_object)
5564                .and_then(|trace| trace.get("id")),
5565            Some(&serde_json::json!("abc123"))
5566        );
5567        assert!(metadata.contains_key("created_at"));
5568    }
5569
5570    #[test]
5571    fn prepare_payload_from_patch_updates_nested_fields() {
5572        let dir = tempfile::tempdir().unwrap();
5573        let path = dir.path().join("event_store");
5574        let store = EventStore::open(path.clone(), None, 0).unwrap();
5575
5576        store
5577            .append(AppendEvent {
5578                aggregate_type: "patient".into(),
5579                aggregate_id: "p-1".into(),
5580                event_type: "patient-created".into(),
5581                event_type_raw: None,
5582                payload: serde_json::json!({
5583                    "status": "active",
5584                    "contact": {
5585                        "address": { "city": "Portland" }
5586                    }
5587                }),
5588                metadata: None,
5589                issued_by: None,
5590                note: None,
5591                tenant: "default".into(),
5592                reference_targets: Vec::new(),
5593            })
5594            .unwrap();
5595
5596        let patch = serde_json::json!([
5597            { "op": "replace", "path": "/status", "value": "inactive" },
5598            { "op": "replace", "path": "/contact/address/city", "value": "Spokane" }
5599        ]);
5600
5601        let payload = store
5602            .prepare_payload_from_patch("patient", "p-1", &patch)
5603            .unwrap();
5604
5605        assert_eq!(
5606            payload,
5607            serde_json::json!({
5608                "contact": { "address": { "city": "Spokane" } },
5609                "status": "inactive"
5610            })
5611        );
5612    }
5613
5614    #[test]
5615    fn prepare_payload_from_patch_rejects_unsupported_operations() {
5616        let dir = tempfile::tempdir().unwrap();
5617        let path = dir.path().join("event_store");
5618        let store = EventStore::open(path, None, 0).unwrap();
5619
5620        let patch = serde_json::json!([
5621            { "op": "remove", "path": "/status" }
5622        ]);
5623
5624        let err = store
5625            .prepare_payload_from_patch("patient", "p-unknown", &patch)
5626            .unwrap_err();
5627
5628        assert!(matches!(err, crate::error::EventError::InvalidSchema(_)));
5629    }
5630
5631    #[test]
5632    fn prepare_payload_from_patch_supports_add_on_new_aggregate() {
5633        let dir = tempfile::tempdir().unwrap();
5634        let path = dir.path().join("event_store");
5635        let store = EventStore::open(path, None, 0).unwrap();
5636
5637        let patch = serde_json::json!([
5638            { "op": "add", "path": "/status", "value": "inactive" }
5639        ]);
5640
5641        let payload = store
5642            .prepare_payload_from_patch("patient", "p-new", &patch)
5643            .unwrap();
5644
5645        assert_eq!(
5646            payload,
5647            serde_json::json!({
5648                "status": "inactive"
5649            })
5650        );
5651    }
5652
5653    #[test]
5654    fn select_state_field_handles_nested_paths() {
5655        let mut map = BTreeMap::new();
5656        map.insert("status".into(), "\"active\"".into());
5657        map.insert(
5658            "profile".into(),
5659            r#"{"name":{"first":"Ada"},"scores":[10,20]}"#.into(),
5660        );
5661
5662        assert_eq!(
5663            super::select_state_field(&map, "status"),
5664            Some(Value::String("active".into()))
5665        );
5666        assert_eq!(
5667            super::select_state_field(&map, "profile.name.first"),
5668            Some(Value::String("Ada".into()))
5669        );
5670        assert_eq!(
5671            super::select_state_field(&map, "profile.scores.1"),
5672            Some(Value::from(20))
5673        );
5674        assert_eq!(super::select_state_field(&map, "missing"), None);
5675        assert_eq!(super::select_state_field(&map, ""), None);
5676        assert_eq!(super::select_state_field(&map, "profile.scores.5"), None);
5677    }
5678
5679    #[test]
5680    fn reports_store_in_use_on_active_lock() {
5681        let dir = tempfile::tempdir().unwrap();
5682        let path = dir.path().join("event_store");
5683        let _holder = EventStore::open(path.clone(), None, 0).unwrap();
5684
5685        let result = EventStore::open(path, None, 0);
5686        match result {
5687            Err(EventError::Storage(message)) => {
5688                let lower = message.to_ascii_lowercase();
5689                assert!(
5690                    lower.contains("in use") || lower.contains("lock"),
5691                    "expected lock error message, got {message}"
5692                );
5693            }
5694            Err(other) => panic!("expected storage error for active lock, got {other:?}"),
5695            Ok(_) => panic!("expected store open to fail while lock is held"),
5696        }
5697    }
5698
5699    #[test]
5700    fn recovers_after_lock_is_released() {
5701        use std::{thread, time::Duration};
5702
5703        let dir = tempfile::tempdir().unwrap();
5704        let path = dir.path().join("event_store");
5705
5706        // Create the store so the directory exists before the holder grabs the lock.
5707        EventStore::open(path.clone(), None, 0).unwrap();
5708
5709        let holder_path = path.clone();
5710        let handle = thread::spawn(move || {
5711            let store = EventStore::open(holder_path, None, 0).unwrap();
5712            thread::sleep(Duration::from_millis(150));
5713            drop(store);
5714        });
5715
5716        thread::sleep(Duration::from_millis(25));
5717        let reopened = EventStore::open(path.clone(), None, 0).unwrap();
5718        drop(reopened);
5719
5720        handle.join().unwrap();
5721    }
5722
5723    #[test]
5724    fn snapshot_does_not_leave_lock_held() {
5725        let dir = tempfile::tempdir().unwrap();
5726        let path = dir.path().join("event_store");
5727
5728        {
5729            let store = EventStore::open(path.clone(), None, 0).unwrap();
5730            store
5731                .append(AppendEvent {
5732                    aggregate_type: "account".into(),
5733                    aggregate_id: "acct-42".into(),
5734                    event_type: "created".into(),
5735                    event_type_raw: None,
5736                    payload: serde_json::json!({ "status": "active" }),
5737                    metadata: None,
5738                    issued_by: None,
5739                    note: None,
5740                    tenant: "default".into(),
5741                    reference_targets: Vec::new(),
5742                })
5743                .unwrap();
5744            store
5745                .create_snapshot("account", "acct-42", Some("first".into()))
5746                .unwrap();
5747        }
5748
5749        let reopened = EventStore::open(path, None, 0).unwrap();
5750        let snapshot = reopened
5751            .create_snapshot("account", "acct-42", Some("second".into()))
5752            .unwrap();
5753        assert_eq!(snapshot.aggregate_id, "acct-42");
5754        assert_eq!(snapshot.aggregate_type, "account");
5755    }
5756
5757    #[test]
5758    fn list_snapshots_filters_by_version() {
5759        use std::{thread, time::Duration};
5760
5761        let dir = tempfile::tempdir().unwrap();
5762        let path = dir.path().join("event_store");
5763        let store = EventStore::open(path, None, 0).unwrap();
5764
5765        store
5766            .append(AppendEvent {
5767                aggregate_type: "order".into(),
5768                aggregate_id: "order-9".into(),
5769                event_type: "created".into(),
5770                event_type_raw: None,
5771                payload: serde_json::json!({ "status": "new" }),
5772                metadata: None,
5773                issued_by: None,
5774                note: None,
5775                tenant: "default".into(),
5776                reference_targets: Vec::new(),
5777            })
5778            .unwrap();
5779        store
5780            .create_snapshot("order", "order-9", Some("v1".into()))
5781            .unwrap();
5782
5783        thread::sleep(Duration::from_millis(2));
5784
5785        store
5786            .append(AppendEvent {
5787                aggregate_type: "order".into(),
5788                aggregate_id: "order-9".into(),
5789                event_type: "updated".into(),
5790                event_type_raw: None,
5791                payload: serde_json::json!({ "status": "shipped" }),
5792                metadata: None,
5793                issued_by: None,
5794                note: None,
5795                tenant: "default".into(),
5796                reference_targets: Vec::new(),
5797            })
5798            .unwrap();
5799        store
5800            .create_snapshot("order", "order-9", Some("v2".into()))
5801            .unwrap();
5802
5803        let all = store
5804            .list_snapshots(Some("order"), Some("order-9"), None)
5805            .expect("list snapshots");
5806        assert_eq!(all.len(), 2);
5807        assert!(all.iter().any(|s| s.version == 1));
5808        assert!(all.iter().any(|s| s.version == 2));
5809
5810        let filtered = store
5811            .list_snapshots(Some("order"), Some("order-9"), Some(1))
5812            .expect("list snapshots by version");
5813        assert_eq!(filtered.len(), 1);
5814        assert_eq!(filtered[0].version, 1);
5815    }
5816
5817    #[test]
5818    fn list_snapshots_all_aggregates() {
5819        let dir = tempfile::tempdir().unwrap();
5820        let path = dir.path().join("event_store");
5821        let store = EventStore::open(path, None, 0).unwrap();
5822
5823        for agg_id in &["a-1", "b-2"] {
5824            store
5825                .append(AppendEvent {
5826                    aggregate_type: "person".into(),
5827                    aggregate_id: agg_id.to_string(),
5828                    event_type: "created".into(),
5829                    event_type_raw: None,
5830                    payload: serde_json::json!({ "name": agg_id }),
5831                    metadata: None,
5832                    issued_by: None,
5833                    note: None,
5834                    tenant: "default".into(),
5835                    reference_targets: Vec::new(),
5836                })
5837                .unwrap();
5838            store
5839                .create_snapshot("person", agg_id, Some("checkpoint".into()))
5840                .unwrap();
5841        }
5842
5843        let snapshots = store
5844            .list_snapshots(None, None, None)
5845            .expect("list all snapshots");
5846        assert_eq!(snapshots.len(), 2);
5847    }
5848
5849    #[test]
5850    fn referrers_for_any_tenant_returns_cross_tenant_entries() {
5851        let dir = tempfile::tempdir().unwrap();
5852        let path = dir.path().join("event_store");
5853        let store = EventStore::open(path, None, 0).unwrap();
5854
5855        // target aggregate in tenant beta
5856        store
5857            .append(AppendEvent {
5858                aggregate_type: "address".into(),
5859                aggregate_id: "addr-1".into(),
5860                event_type: "created".into(),
5861                event_type_raw: None,
5862                payload: serde_json::json!({ "status": "active" }),
5863                metadata: None,
5864                issued_by: None,
5865                note: None,
5866                tenant: "beta".into(),
5867                reference_targets: Vec::new(),
5868            })
5869            .unwrap();
5870
5871        // referrer stored under tenant alpha pointing at the beta address
5872        store
5873            .append(AppendEvent {
5874                aggregate_type: "farm".into(),
5875                aggregate_id: "farm-1".into(),
5876                event_type: "created".into(),
5877                event_type_raw: None,
5878                payload: serde_json::json!({ "address": "beta#address#addr-1" }),
5879                metadata: None,
5880                issued_by: None,
5881                note: None,
5882                tenant: "alpha".into(),
5883                reference_targets: vec![("beta#address#addr-1".into(), "/address".into())],
5884            })
5885            .unwrap();
5886
5887        let refs = store
5888            .referrers_for_any_tenant("beta#address#addr-1")
5889            .unwrap();
5890        assert_eq!(refs.len(), 1);
5891        assert_eq!(refs[0].0, "alpha");
5892        assert_eq!(refs[0].1, "farm");
5893        assert_eq!(refs[0].2, "farm-1");
5894        assert_eq!(refs[0].3, "/address");
5895    }
5896}