1use crate::rocksdb_storage::{SessionCheckpoint, StoredWorkspace};
27use anyhow::Result;
28use async_trait::async_trait;
29use post_cortex_core::core::context_update::{
30 ContextUpdate, EntityData, EntityRelationship, RelationType,
31};
32use post_cortex_core::graph::entity_graph::EntityNetwork;
33use post_cortex_core::session::active_session::ActiveSession;
34use post_cortex_core::workspace::SessionRole;
35use post_cortex_embeddings::{SearchMatch, VectorMetadata};
36use post_cortex_proto::pb::{CascadeInvalidateReport, FreshnessEntry, SourceReference, SymbolId};
37use uuid::Uuid;
38
39#[derive(Debug, Clone)]
41pub struct FreshnessReportExt {
42 pub entries: Vec<FreshnessEntry>,
44}
45
46#[derive(Debug, Clone)]
50pub struct StaleEntryInfo {
51 pub entry_id: String,
53 pub symbol_name: Option<String>,
55 pub symbol_type: Option<String>,
57}
58#[async_trait]
64pub trait Storage: FreshnessStorage + Send + Sync {
65 async fn save_session(&self, session: &ActiveSession) -> Result<()>;
69
70 async fn load_session(&self, session_id: Uuid) -> Result<ActiveSession>;
72
73 async fn delete_session(&self, session_id: Uuid) -> Result<()>;
75
76 async fn clear_session_entities(&self, session_id: Uuid) -> Result<()>;
78
79 async fn list_sessions(&self) -> Result<Vec<Uuid>>;
81
82 async fn session_exists(&self, session_id: Uuid) -> Result<bool>;
84
85 async fn batch_save_updates(&self, session_id: Uuid, updates: Vec<ContextUpdate>)
89 -> Result<()>;
90
91 async fn save_session_with_updates(
95 &self,
96 session: &ActiveSession,
97 session_id: Uuid,
98 updates: Vec<ContextUpdate>,
99 ) -> Result<()> {
100 self.save_session(session).await?;
101 if !updates.is_empty() {
102 self.batch_save_updates(session_id, updates).await?;
103 }
104 Ok(())
105 }
106
107 async fn load_session_updates(&self, session_id: Uuid) -> Result<Vec<ContextUpdate>>;
109
110 async fn save_checkpoint(&self, checkpoint: &SessionCheckpoint) -> Result<()>;
114
115 async fn load_checkpoint(&self, checkpoint_id: Uuid) -> Result<SessionCheckpoint>;
117
118 async fn list_checkpoints(&self) -> Result<Vec<SessionCheckpoint>>;
120
121 async fn save_workspace_metadata(
125 &self,
126 workspace_id: Uuid,
127 name: &str,
128 description: &str,
129 session_ids: &[Uuid],
130 ) -> Result<()>;
131
132 async fn delete_workspace(&self, workspace_id: Uuid) -> Result<()>;
134
135 async fn list_workspaces(&self) -> Result<Vec<StoredWorkspace>>;
137
138 async fn add_session_to_workspace(
140 &self,
141 workspace_id: Uuid,
142 session_id: Uuid,
143 role: SessionRole,
144 ) -> Result<()>;
145
146 async fn remove_session_from_workspace(
148 &self,
149 workspace_id: Uuid,
150 session_id: Uuid,
151 ) -> Result<()>;
152
153 async fn compact(&self) -> Result<()>;
157
158 async fn get_key_count(&self) -> Result<usize>;
160
161 async fn get_stats(&self) -> Result<String>;
163}
164
165#[async_trait]
171pub trait GraphStorage: Storage {
172 async fn upsert_entity(&self, session_id: Uuid, entity: &EntityData) -> Result<()>;
176
177 async fn get_entity(&self, session_id: Uuid, name: &str) -> Result<Option<EntityData>>;
179
180 async fn list_entities(&self, session_id: Uuid) -> Result<Vec<EntityData>>;
182
183 async fn delete_entity(&self, session_id: Uuid, name: &str) -> Result<()>;
185
186 async fn create_relationship(
190 &self,
191 session_id: Uuid,
192 relationship: &EntityRelationship,
193 ) -> Result<()>;
194
195 async fn find_related_entities(
197 &self,
198 session_id: Uuid,
199 entity_name: &str,
200 ) -> Result<Vec<String>>;
201
202 async fn find_related_by_type(
204 &self,
205 session_id: Uuid,
206 entity_name: &str,
207 relation_type: &RelationType,
208 ) -> Result<Vec<String>>;
209
210 async fn find_shortest_path(
212 &self,
213 session_id: Uuid,
214 from: &str,
215 to: &str,
216 ) -> Result<Option<Vec<String>>>;
217
218 async fn get_entity_network(
220 &self,
221 session_id: Uuid,
222 center: &str,
223 max_depth: usize,
224 ) -> Result<EntityNetwork>;
225}
226
227#[async_trait]
234pub trait VectorStorage: Send + Sync {
235 async fn add_vector(&self, vector: Vec<f32>, metadata: VectorMetadata) -> Result<String>;
237
238 async fn add_vectors_batch(
240 &self,
241 vectors: Vec<(Vec<f32>, VectorMetadata)>,
242 ) -> Result<Vec<String>>;
243
244 async fn search(&self, query: &[f32], k: usize) -> Result<Vec<SearchMatch>>;
246
247 async fn search_in_session(
249 &self,
250 query: &[f32],
251 k: usize,
252 session_id: &str,
253 ) -> Result<Vec<SearchMatch>>;
254
255 async fn search_by_content_type(
257 &self,
258 query: &[f32],
259 k: usize,
260 content_type: &str,
261 ) -> Result<Vec<SearchMatch>>;
262
263 async fn remove_vector(&self, id: &str) -> Result<bool>;
265
266 async fn has_session_embeddings(&self, session_id: &str) -> bool;
268
269 async fn count_session_embeddings(&self, session_id: &str) -> usize;
271
272 async fn total_count(&self) -> usize;
274
275 async fn get_session_vectors(
277 &self,
278 session_id: &str,
279 ) -> Result<Vec<(Vec<f32>, VectorMetadata)>>;
280
281 async fn get_all_vectors(&self) -> Result<Vec<(Vec<f32>, VectorMetadata)>>;
283}
284
285#[async_trait]
290pub trait FreshnessStorage: Send + Sync {
291 async fn register_source(&self, session_id: Uuid, reference: SourceReference) -> Result<()>;
293
294 async fn check_freshness(&self, entry_id: &str, file_hash: &[u8]) -> Result<FreshnessEntry>;
298
299 async fn check_freshness_semantic(
303 &self,
304 entry_id: &str,
305 file_hash: &[u8],
306 _ast_hash: Option<&[u8]>,
307 _symbol_name: Option<&str>,
308 ) -> Result<FreshnessEntry> {
309 self.check_freshness(entry_id, file_hash).await
311 }
312
313 async fn invalidate_source(&self, file_path: &str) -> Result<u32>;
316
317 async fn get_entries_by_source(&self, file_path: &str) -> Result<Vec<SourceReference>>;
319
320 async fn get_stale_entries_by_source(&self, file_path: &str) -> Result<Vec<StaleEntryInfo>>;
325
326 async fn register_symbol_dependencies(
329 &self,
330 from: SymbolId,
331 to_symbols: Vec<SymbolId>,
332 ) -> Result<u32>;
333
334 async fn cascade_invalidate(
337 &self,
338 changed: SymbolId,
339 new_ast_hash: Vec<u8>,
340 max_depth: u32,
341 ) -> Result<CascadeInvalidateReport>;
342
343 async fn check_freshness_batch(
352 &self,
353 entries: Vec<(String, Vec<u8>, Option<Vec<u8>>, Option<String>)>,
354 ) -> Result<Vec<FreshnessEntry>> {
355 let mut results = Vec::with_capacity(entries.len());
356 for (entry_id, file_hash, ast_hash, symbol_name) in entries {
357 let result = self
358 .check_freshness_semantic(
359 &entry_id,
360 &file_hash,
361 ast_hash.as_deref(),
362 symbol_name.as_deref(),
363 )
364 .await
365 .unwrap_or_else(|_| FreshnessEntry {
366 entry_id: entry_id.clone(),
367 file_path: String::new(),
368 status: post_cortex_proto::pb::FreshnessStatus::Unknown as i32,
369 stored_hash: Vec::new(),
370 current_hash: file_hash,
371 });
372 results.push(result);
373 }
374 Ok(results)
375 }
376}
377
378#[derive(Clone)]
383pub enum StorageBackend {
384 RocksDB(crate::RealRocksDBStorage),
386
387 #[cfg(feature = "surrealdb-storage")]
389 SurrealDB(std::sync::Arc<crate::surrealdb_storage::SurrealDBStorage>),
390}
391
392impl StorageBackend {
393 pub fn supports_native_graph(&self) -> bool {
395 match self {
396 StorageBackend::RocksDB(_) => false,
397 #[cfg(feature = "surrealdb-storage")]
398 StorageBackend::SurrealDB(_) => true,
399 }
400 }
401
402 pub fn supports_native_vectors(&self) -> bool {
404 match self {
405 StorageBackend::RocksDB(_) => false,
406 #[cfg(feature = "surrealdb-storage")]
407 StorageBackend::SurrealDB(_) => true,
408 }
409 }
410}
411
412#[async_trait]
414impl Storage for StorageBackend {
415 async fn save_session(&self, session: &ActiveSession) -> Result<()> {
416 match self {
417 StorageBackend::RocksDB(storage) => storage.save_session(session).await,
418 #[cfg(feature = "surrealdb-storage")]
419 StorageBackend::SurrealDB(storage) => storage.save_session(session).await,
420 }
421 }
422
423 async fn load_session(&self, session_id: Uuid) -> Result<ActiveSession> {
424 match self {
425 StorageBackend::RocksDB(storage) => storage.load_session(session_id).await,
426 #[cfg(feature = "surrealdb-storage")]
427 StorageBackend::SurrealDB(storage) => storage.load_session(session_id).await,
428 }
429 }
430
431 async fn delete_session(&self, session_id: Uuid) -> Result<()> {
432 match self {
433 StorageBackend::RocksDB(storage) => storage.delete_session(session_id).await,
434 #[cfg(feature = "surrealdb-storage")]
435 StorageBackend::SurrealDB(storage) => storage.delete_session(session_id).await,
436 }
437 }
438
439 async fn clear_session_entities(&self, session_id: Uuid) -> Result<()> {
440 match self {
441 StorageBackend::RocksDB(storage) => storage.clear_session_entities(session_id).await,
442 #[cfg(feature = "surrealdb-storage")]
443 StorageBackend::SurrealDB(storage) => storage.clear_session_entities(session_id).await,
444 }
445 }
446
447 async fn list_sessions(&self) -> Result<Vec<Uuid>> {
448 match self {
449 StorageBackend::RocksDB(storage) => storage.list_sessions().await,
450 #[cfg(feature = "surrealdb-storage")]
451 StorageBackend::SurrealDB(storage) => storage.list_sessions().await,
452 }
453 }
454
455 async fn session_exists(&self, session_id: Uuid) -> Result<bool> {
456 match self {
457 StorageBackend::RocksDB(storage) => storage.session_exists(session_id).await,
458 #[cfg(feature = "surrealdb-storage")]
459 StorageBackend::SurrealDB(storage) => storage.session_exists(session_id).await,
460 }
461 }
462
463 async fn batch_save_updates(
464 &self,
465 session_id: Uuid,
466 updates: Vec<ContextUpdate>,
467 ) -> Result<()> {
468 match self {
469 StorageBackend::RocksDB(storage) => {
470 storage.batch_save_updates(session_id, updates).await
471 }
472 #[cfg(feature = "surrealdb-storage")]
473 StorageBackend::SurrealDB(storage) => {
474 storage.batch_save_updates(session_id, updates).await
475 }
476 }
477 }
478
479 async fn save_session_with_updates(
480 &self,
481 session: &ActiveSession,
482 session_id: Uuid,
483 updates: Vec<ContextUpdate>,
484 ) -> Result<()> {
485 match self {
486 StorageBackend::RocksDB(storage) => {
487 storage
488 .save_session_with_updates(session, session_id, updates)
489 .await
490 }
491 #[cfg(feature = "surrealdb-storage")]
492 StorageBackend::SurrealDB(_) => {
493 self.save_session(session).await?;
495 if !updates.is_empty() {
496 self.batch_save_updates(session_id, updates).await?;
497 }
498 Ok(())
499 }
500 }
501 }
502
503 async fn load_session_updates(&self, session_id: Uuid) -> Result<Vec<ContextUpdate>> {
504 match self {
505 StorageBackend::RocksDB(storage) => storage.load_session_updates(session_id).await,
506 #[cfg(feature = "surrealdb-storage")]
507 StorageBackend::SurrealDB(storage) => storage.load_session_updates(session_id).await,
508 }
509 }
510
511 async fn save_checkpoint(&self, checkpoint: &SessionCheckpoint) -> Result<()> {
512 match self {
513 StorageBackend::RocksDB(storage) => storage.save_checkpoint(checkpoint).await,
514 #[cfg(feature = "surrealdb-storage")]
515 StorageBackend::SurrealDB(storage) => storage.save_checkpoint(checkpoint).await,
516 }
517 }
518
519 async fn load_checkpoint(&self, checkpoint_id: Uuid) -> Result<SessionCheckpoint> {
520 match self {
521 StorageBackend::RocksDB(storage) => storage.load_checkpoint(checkpoint_id).await,
522 #[cfg(feature = "surrealdb-storage")]
523 StorageBackend::SurrealDB(storage) => storage.load_checkpoint(checkpoint_id).await,
524 }
525 }
526
527 async fn list_checkpoints(&self) -> Result<Vec<SessionCheckpoint>> {
528 match self {
529 StorageBackend::RocksDB(storage) => storage.list_checkpoints().await,
530 #[cfg(feature = "surrealdb-storage")]
531 StorageBackend::SurrealDB(storage) => storage.list_checkpoints().await,
532 }
533 }
534
535 async fn save_workspace_metadata(
536 &self,
537 workspace_id: Uuid,
538 name: &str,
539 description: &str,
540 session_ids: &[Uuid],
541 ) -> Result<()> {
542 match self {
543 StorageBackend::RocksDB(storage) => {
544 storage
545 .save_workspace_metadata(workspace_id, name, description, session_ids)
546 .await
547 }
548 #[cfg(feature = "surrealdb-storage")]
549 StorageBackend::SurrealDB(storage) => {
550 storage
551 .save_workspace_metadata(workspace_id, name, description, session_ids)
552 .await
553 }
554 }
555 }
556
557 async fn delete_workspace(&self, workspace_id: Uuid) -> Result<()> {
558 match self {
559 StorageBackend::RocksDB(storage) => storage.delete_workspace(workspace_id).await,
560 #[cfg(feature = "surrealdb-storage")]
561 StorageBackend::SurrealDB(storage) => storage.delete_workspace(workspace_id).await,
562 }
563 }
564
565 async fn list_workspaces(&self) -> Result<Vec<StoredWorkspace>> {
566 match self {
567 StorageBackend::RocksDB(storage) => storage.list_workspaces().await,
568 #[cfg(feature = "surrealdb-storage")]
569 StorageBackend::SurrealDB(storage) => storage.list_workspaces().await,
570 }
571 }
572
573 async fn add_session_to_workspace(
574 &self,
575 workspace_id: Uuid,
576 session_id: Uuid,
577 role: SessionRole,
578 ) -> Result<()> {
579 match self {
580 StorageBackend::RocksDB(storage) => {
581 storage
582 .add_session_to_workspace(workspace_id, session_id, role)
583 .await
584 }
585 #[cfg(feature = "surrealdb-storage")]
586 StorageBackend::SurrealDB(storage) => {
587 storage
588 .add_session_to_workspace(workspace_id, session_id, role)
589 .await
590 }
591 }
592 }
593
594 async fn remove_session_from_workspace(
595 &self,
596 workspace_id: Uuid,
597 session_id: Uuid,
598 ) -> Result<()> {
599 match self {
600 StorageBackend::RocksDB(storage) => {
601 storage
602 .remove_session_from_workspace(workspace_id, session_id)
603 .await
604 }
605 #[cfg(feature = "surrealdb-storage")]
606 StorageBackend::SurrealDB(storage) => {
607 storage
608 .remove_session_from_workspace(workspace_id, session_id)
609 .await
610 }
611 }
612 }
613
614 async fn compact(&self) -> Result<()> {
615 match self {
616 StorageBackend::RocksDB(storage) => storage.compact().await,
617 #[cfg(feature = "surrealdb-storage")]
618 StorageBackend::SurrealDB(storage) => storage.compact().await,
619 }
620 }
621
622 async fn get_key_count(&self) -> Result<usize> {
623 match self {
624 StorageBackend::RocksDB(storage) => storage.get_key_count().await,
625 #[cfg(feature = "surrealdb-storage")]
626 StorageBackend::SurrealDB(storage) => storage.get_key_count().await,
627 }
628 }
629
630 async fn get_stats(&self) -> Result<String> {
631 match self {
632 StorageBackend::RocksDB(storage) => storage.get_stats().await,
633 #[cfg(feature = "surrealdb-storage")]
634 StorageBackend::SurrealDB(storage) => storage.get_stats().await,
635 }
636 }
637}
638
639#[async_trait]
641impl GraphStorage for StorageBackend {
642 async fn upsert_entity(&self, session_id: Uuid, entity: &EntityData) -> Result<()> {
643 match self {
644 StorageBackend::RocksDB(storage) => storage.upsert_entity(session_id, entity).await,
645 #[cfg(feature = "surrealdb-storage")]
646 StorageBackend::SurrealDB(storage) => storage.upsert_entity(session_id, entity).await,
647 }
648 }
649
650 async fn get_entity(&self, session_id: Uuid, name: &str) -> Result<Option<EntityData>> {
651 match self {
652 StorageBackend::RocksDB(storage) => storage.get_entity(session_id, name).await,
653 #[cfg(feature = "surrealdb-storage")]
654 StorageBackend::SurrealDB(storage) => storage.get_entity(session_id, name).await,
655 }
656 }
657
658 async fn list_entities(&self, session_id: Uuid) -> Result<Vec<EntityData>> {
659 match self {
660 StorageBackend::RocksDB(storage) => storage.list_entities(session_id).await,
661 #[cfg(feature = "surrealdb-storage")]
662 StorageBackend::SurrealDB(storage) => storage.list_entities(session_id).await,
663 }
664 }
665
666 async fn delete_entity(&self, session_id: Uuid, name: &str) -> Result<()> {
667 match self {
668 StorageBackend::RocksDB(storage) => storage.delete_entity(session_id, name).await,
669 #[cfg(feature = "surrealdb-storage")]
670 StorageBackend::SurrealDB(storage) => storage.delete_entity(session_id, name).await,
671 }
672 }
673
674 async fn create_relationship(
675 &self,
676 session_id: Uuid,
677 relationship: &EntityRelationship,
678 ) -> Result<()> {
679 match self {
680 StorageBackend::RocksDB(storage) => {
681 storage.create_relationship(session_id, relationship).await
682 }
683 #[cfg(feature = "surrealdb-storage")]
684 StorageBackend::SurrealDB(storage) => {
685 storage.create_relationship(session_id, relationship).await
686 }
687 }
688 }
689
690 async fn find_related_entities(
691 &self,
692 session_id: Uuid,
693 entity_name: &str,
694 ) -> Result<Vec<String>> {
695 match self {
696 StorageBackend::RocksDB(storage) => {
697 storage.find_related_entities(session_id, entity_name).await
698 }
699 #[cfg(feature = "surrealdb-storage")]
700 StorageBackend::SurrealDB(storage) => {
701 storage.find_related_entities(session_id, entity_name).await
702 }
703 }
704 }
705
706 async fn find_related_by_type(
707 &self,
708 session_id: Uuid,
709 entity_name: &str,
710 relation_type: &RelationType,
711 ) -> Result<Vec<String>> {
712 match self {
713 StorageBackend::RocksDB(storage) => {
714 storage
715 .find_related_by_type(session_id, entity_name, relation_type)
716 .await
717 }
718 #[cfg(feature = "surrealdb-storage")]
719 StorageBackend::SurrealDB(storage) => {
720 storage
721 .find_related_by_type(session_id, entity_name, relation_type)
722 .await
723 }
724 }
725 }
726
727 async fn find_shortest_path(
728 &self,
729 session_id: Uuid,
730 from: &str,
731 to: &str,
732 ) -> Result<Option<Vec<String>>> {
733 match self {
734 StorageBackend::RocksDB(storage) => {
735 storage.find_shortest_path(session_id, from, to).await
736 }
737 #[cfg(feature = "surrealdb-storage")]
738 StorageBackend::SurrealDB(storage) => {
739 storage.find_shortest_path(session_id, from, to).await
740 }
741 }
742 }
743
744 async fn get_entity_network(
745 &self,
746 session_id: Uuid,
747 center: &str,
748 max_depth: usize,
749 ) -> Result<EntityNetwork> {
750 match self {
751 StorageBackend::RocksDB(storage) => {
752 storage
753 .get_entity_network(session_id, center, max_depth)
754 .await
755 }
756 #[cfg(feature = "surrealdb-storage")]
757 StorageBackend::SurrealDB(storage) => {
758 storage
759 .get_entity_network(session_id, center, max_depth)
760 .await
761 }
762 }
763 }
764}
765
766#[async_trait]
768impl FreshnessStorage for StorageBackend {
769 async fn register_source(&self, session_id: Uuid, reference: SourceReference) -> Result<()> {
770 match self {
771 StorageBackend::RocksDB(storage) => {
772 storage.register_source(session_id, reference).await
773 }
774 #[cfg(feature = "surrealdb-storage")]
775 StorageBackend::SurrealDB(storage) => {
776 storage.register_source(session_id, reference).await
777 }
778 }
779 }
780
781 async fn check_freshness(&self, entry_id: &str, file_hash: &[u8]) -> Result<FreshnessEntry> {
782 match self {
783 StorageBackend::RocksDB(storage) => storage.check_freshness(entry_id, file_hash).await,
784 #[cfg(feature = "surrealdb-storage")]
785 StorageBackend::SurrealDB(storage) => {
786 storage.check_freshness(entry_id, file_hash).await
787 }
788 }
789 }
790
791 async fn invalidate_source(&self, file_path: &str) -> Result<u32> {
792 match self {
793 StorageBackend::RocksDB(storage) => storage.invalidate_source(file_path).await,
794 #[cfg(feature = "surrealdb-storage")]
795 StorageBackend::SurrealDB(storage) => storage.invalidate_source(file_path).await,
796 }
797 }
798
799 async fn get_entries_by_source(&self, file_path: &str) -> Result<Vec<SourceReference>> {
800 match self {
801 StorageBackend::RocksDB(storage) => storage.get_entries_by_source(file_path).await,
802 #[cfg(feature = "surrealdb-storage")]
803 StorageBackend::SurrealDB(storage) => storage.get_entries_by_source(file_path).await,
804 }
805 }
806
807 async fn get_stale_entries_by_source(&self, file_path: &str) -> Result<Vec<StaleEntryInfo>> {
808 match self {
809 StorageBackend::RocksDB(storage) => {
810 storage.get_stale_entries_by_source(file_path).await
811 }
812 #[cfg(feature = "surrealdb-storage")]
813 StorageBackend::SurrealDB(storage) => {
814 storage.get_stale_entries_by_source(file_path).await
815 }
816 }
817 }
818
819 async fn check_freshness_semantic(
820 &self,
821 entry_id: &str,
822 file_hash: &[u8],
823 ast_hash: Option<&[u8]>,
824 symbol_name: Option<&str>,
825 ) -> Result<FreshnessEntry> {
826 match self {
827 StorageBackend::RocksDB(storage) => {
828 storage
829 .check_freshness_semantic(entry_id, file_hash, ast_hash, symbol_name)
830 .await
831 }
832 #[cfg(feature = "surrealdb-storage")]
833 StorageBackend::SurrealDB(storage) => {
834 storage
835 .check_freshness_semantic(entry_id, file_hash, ast_hash, symbol_name)
836 .await
837 }
838 }
839 }
840
841 async fn register_symbol_dependencies(
842 &self,
843 from: SymbolId,
844 to_symbols: Vec<SymbolId>,
845 ) -> Result<u32> {
846 match self {
847 StorageBackend::RocksDB(storage) => {
848 storage.register_symbol_dependencies(from, to_symbols).await
849 }
850 #[cfg(feature = "surrealdb-storage")]
851 StorageBackend::SurrealDB(storage) => {
852 storage.register_symbol_dependencies(from, to_symbols).await
853 }
854 }
855 }
856
857 async fn cascade_invalidate(
858 &self,
859 changed: SymbolId,
860 new_ast_hash: Vec<u8>,
861 max_depth: u32,
862 ) -> Result<CascadeInvalidateReport> {
863 match self {
864 StorageBackend::RocksDB(storage) => {
865 storage
866 .cascade_invalidate(changed, new_ast_hash, max_depth)
867 .await
868 }
869 #[cfg(feature = "surrealdb-storage")]
870 StorageBackend::SurrealDB(storage) => {
871 storage
872 .cascade_invalidate(changed, new_ast_hash, max_depth)
873 .await
874 }
875 }
876 }
877
878 async fn check_freshness_batch(
879 &self,
880 entries: Vec<(String, Vec<u8>, Option<Vec<u8>>, Option<String>)>,
881 ) -> Result<Vec<FreshnessEntry>> {
882 match self {
883 StorageBackend::RocksDB(storage) => storage.check_freshness_batch(entries).await,
884 #[cfg(feature = "surrealdb-storage")]
885 StorageBackend::SurrealDB(storage) => storage.check_freshness_batch(entries).await,
886 }
887 }
888}
889
890#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
892pub struct StorageConfig {
893 pub backend: StorageBackendType,
895
896 pub path: std::path::PathBuf,
898
899 #[cfg(feature = "surrealdb-storage")]
901 pub surrealdb: Option<SurrealDBConfig>,
902}
903
904#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
906#[serde(rename_all = "lowercase")]
907#[derive(Default)]
908pub enum StorageBackendType {
909 #[default]
911 RocksDB,
912
913 #[cfg(feature = "surrealdb-storage")]
915 SurrealDB,
916}
917
918impl StorageBackendType {
919 pub fn from_str(s: &str) -> Option<Self> {
921 match s.to_lowercase().as_str() {
922 "rocksdb" | "rocks" => Some(StorageBackendType::RocksDB),
923 #[cfg(feature = "surrealdb-storage")]
924 "surrealdb" | "surreal" => Some(StorageBackendType::SurrealDB),
925 _ => None,
926 }
927 }
928}
929
930#[cfg(feature = "surrealdb-storage")]
932#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
933pub struct SurrealDBConfig {
934 pub namespace: String,
936
937 pub database: String,
939
940 pub tikv_endpoints: Option<Vec<String>>,
942}
943
944#[cfg(feature = "surrealdb-storage")]
945impl Default for SurrealDBConfig {
946 fn default() -> Self {
947 Self {
948 namespace: "post_cortex".to_string(),
949 database: "main".to_string(),
950 tikv_endpoints: None,
951 }
952 }
953}
954
955impl Default for StorageConfig {
956 fn default() -> Self {
957 Self {
958 backend: StorageBackendType::default(),
959 path: dirs::data_local_dir()
960 .unwrap_or_else(|| std::path::PathBuf::from("."))
961 .join("post-cortex")
962 .join("data"),
963 #[cfg(feature = "surrealdb-storage")]
964 surrealdb: None,
965 }
966 }
967}