1use post_cortex_core::core::context_update::{ContextUpdate, EntityData, EntityRelationship, RelationType};
27use post_cortex_embeddings::{SearchMatch, VectorMetadata};
28use post_cortex_proto::pb::{
29 CascadeInvalidateReport, FreshnessEntry, SourceReference, SymbolId,
30};
31use post_cortex_core::graph::entity_graph::EntityNetwork;
32use post_cortex_core::session::active_session::ActiveSession;
33use crate::rocksdb_storage::{SessionCheckpoint, StoredWorkspace};
34use post_cortex_core::workspace::SessionRole;
35use anyhow::Result;
36use async_trait::async_trait;
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(
325 &self,
326 file_path: &str,
327 ) -> Result<Vec<StaleEntryInfo>>;
328
329 async fn register_symbol_dependencies(
332 &self,
333 from: SymbolId,
334 to_symbols: Vec<SymbolId>,
335 ) -> Result<u32>;
336
337 async fn cascade_invalidate(
340 &self,
341 changed: SymbolId,
342 new_ast_hash: Vec<u8>,
343 max_depth: u32,
344 ) -> Result<CascadeInvalidateReport>;
345
346 async fn check_freshness_batch(
355 &self,
356 entries: Vec<(String, Vec<u8>, Option<Vec<u8>>, Option<String>)>,
357 ) -> Result<Vec<FreshnessEntry>> {
358 let mut results = Vec::with_capacity(entries.len());
359 for (entry_id, file_hash, ast_hash, symbol_name) in entries {
360 let result = self
361 .check_freshness_semantic(
362 &entry_id,
363 &file_hash,
364 ast_hash.as_deref(),
365 symbol_name.as_deref(),
366 )
367 .await
368 .unwrap_or_else(|_| FreshnessEntry {
369 entry_id: entry_id.clone(),
370 file_path: String::new(),
371 status: post_cortex_proto::pb::FreshnessStatus::Unknown as i32,
372 stored_hash: Vec::new(),
373 current_hash: file_hash,
374 });
375 results.push(result);
376 }
377 Ok(results)
378 }
379}
380
381#[derive(Clone)]
386pub enum StorageBackend {
387 RocksDB(crate::RealRocksDBStorage),
389
390 #[cfg(feature = "surrealdb-storage")]
392 SurrealDB(std::sync::Arc<crate::surrealdb_storage::SurrealDBStorage>),
393}
394
395impl StorageBackend {
396 pub fn supports_native_graph(&self) -> bool {
398 match self {
399 StorageBackend::RocksDB(_) => false,
400 #[cfg(feature = "surrealdb-storage")]
401 StorageBackend::SurrealDB(_) => true,
402 }
403 }
404
405 pub fn supports_native_vectors(&self) -> bool {
407 match self {
408 StorageBackend::RocksDB(_) => false,
409 #[cfg(feature = "surrealdb-storage")]
410 StorageBackend::SurrealDB(_) => true,
411 }
412 }
413}
414
415#[async_trait]
417impl Storage for StorageBackend {
418 async fn save_session(&self, session: &ActiveSession) -> Result<()> {
419 match self {
420 StorageBackend::RocksDB(storage) => storage.save_session(session).await,
421 #[cfg(feature = "surrealdb-storage")]
422 StorageBackend::SurrealDB(storage) => storage.save_session(session).await,
423 }
424 }
425
426 async fn load_session(&self, session_id: Uuid) -> Result<ActiveSession> {
427 match self {
428 StorageBackend::RocksDB(storage) => storage.load_session(session_id).await,
429 #[cfg(feature = "surrealdb-storage")]
430 StorageBackend::SurrealDB(storage) => storage.load_session(session_id).await,
431 }
432 }
433
434 async fn delete_session(&self, session_id: Uuid) -> Result<()> {
435 match self {
436 StorageBackend::RocksDB(storage) => storage.delete_session(session_id).await,
437 #[cfg(feature = "surrealdb-storage")]
438 StorageBackend::SurrealDB(storage) => storage.delete_session(session_id).await,
439 }
440 }
441
442 async fn clear_session_entities(&self, session_id: Uuid) -> Result<()> {
443 match self {
444 StorageBackend::RocksDB(storage) => storage.clear_session_entities(session_id).await,
445 #[cfg(feature = "surrealdb-storage")]
446 StorageBackend::SurrealDB(storage) => storage.clear_session_entities(session_id).await,
447 }
448 }
449
450 async fn list_sessions(&self) -> Result<Vec<Uuid>> {
451 match self {
452 StorageBackend::RocksDB(storage) => storage.list_sessions().await,
453 #[cfg(feature = "surrealdb-storage")]
454 StorageBackend::SurrealDB(storage) => storage.list_sessions().await,
455 }
456 }
457
458 async fn session_exists(&self, session_id: Uuid) -> Result<bool> {
459 match self {
460 StorageBackend::RocksDB(storage) => storage.session_exists(session_id).await,
461 #[cfg(feature = "surrealdb-storage")]
462 StorageBackend::SurrealDB(storage) => storage.session_exists(session_id).await,
463 }
464 }
465
466 async fn batch_save_updates(
467 &self,
468 session_id: Uuid,
469 updates: Vec<ContextUpdate>,
470 ) -> Result<()> {
471 match self {
472 StorageBackend::RocksDB(storage) => {
473 storage.batch_save_updates(session_id, updates).await
474 }
475 #[cfg(feature = "surrealdb-storage")]
476 StorageBackend::SurrealDB(storage) => {
477 storage.batch_save_updates(session_id, updates).await
478 }
479 }
480 }
481
482 async fn save_session_with_updates(
483 &self,
484 session: &ActiveSession,
485 session_id: Uuid,
486 updates: Vec<ContextUpdate>,
487 ) -> Result<()> {
488 match self {
489 StorageBackend::RocksDB(storage) => {
490 storage
491 .save_session_with_updates(session, session_id, updates)
492 .await
493 }
494 #[cfg(feature = "surrealdb-storage")]
495 StorageBackend::SurrealDB(_) => {
496 self.save_session(session).await?;
498 if !updates.is_empty() {
499 self.batch_save_updates(session_id, updates).await?;
500 }
501 Ok(())
502 }
503 }
504 }
505
506 async fn load_session_updates(&self, session_id: Uuid) -> Result<Vec<ContextUpdate>> {
507 match self {
508 StorageBackend::RocksDB(storage) => storage.load_session_updates(session_id).await,
509 #[cfg(feature = "surrealdb-storage")]
510 StorageBackend::SurrealDB(storage) => storage.load_session_updates(session_id).await,
511 }
512 }
513
514 async fn save_checkpoint(&self, checkpoint: &SessionCheckpoint) -> Result<()> {
515 match self {
516 StorageBackend::RocksDB(storage) => storage.save_checkpoint(checkpoint).await,
517 #[cfg(feature = "surrealdb-storage")]
518 StorageBackend::SurrealDB(storage) => storage.save_checkpoint(checkpoint).await,
519 }
520 }
521
522 async fn load_checkpoint(&self, checkpoint_id: Uuid) -> Result<SessionCheckpoint> {
523 match self {
524 StorageBackend::RocksDB(storage) => storage.load_checkpoint(checkpoint_id).await,
525 #[cfg(feature = "surrealdb-storage")]
526 StorageBackend::SurrealDB(storage) => storage.load_checkpoint(checkpoint_id).await,
527 }
528 }
529
530 async fn list_checkpoints(&self) -> Result<Vec<SessionCheckpoint>> {
531 match self {
532 StorageBackend::RocksDB(storage) => storage.list_checkpoints().await,
533 #[cfg(feature = "surrealdb-storage")]
534 StorageBackend::SurrealDB(storage) => storage.list_checkpoints().await,
535 }
536 }
537
538 async fn save_workspace_metadata(
539 &self,
540 workspace_id: Uuid,
541 name: &str,
542 description: &str,
543 session_ids: &[Uuid],
544 ) -> Result<()> {
545 match self {
546 StorageBackend::RocksDB(storage) => {
547 storage
548 .save_workspace_metadata(workspace_id, name, description, session_ids)
549 .await
550 }
551 #[cfg(feature = "surrealdb-storage")]
552 StorageBackend::SurrealDB(storage) => {
553 storage
554 .save_workspace_metadata(workspace_id, name, description, session_ids)
555 .await
556 }
557 }
558 }
559
560 async fn delete_workspace(&self, workspace_id: Uuid) -> Result<()> {
561 match self {
562 StorageBackend::RocksDB(storage) => storage.delete_workspace(workspace_id).await,
563 #[cfg(feature = "surrealdb-storage")]
564 StorageBackend::SurrealDB(storage) => storage.delete_workspace(workspace_id).await,
565 }
566 }
567
568 async fn list_workspaces(&self) -> Result<Vec<StoredWorkspace>> {
569 match self {
570 StorageBackend::RocksDB(storage) => storage.list_workspaces().await,
571 #[cfg(feature = "surrealdb-storage")]
572 StorageBackend::SurrealDB(storage) => storage.list_workspaces().await,
573 }
574 }
575
576 async fn add_session_to_workspace(
577 &self,
578 workspace_id: Uuid,
579 session_id: Uuid,
580 role: SessionRole,
581 ) -> Result<()> {
582 match self {
583 StorageBackend::RocksDB(storage) => {
584 storage
585 .add_session_to_workspace(workspace_id, session_id, role)
586 .await
587 }
588 #[cfg(feature = "surrealdb-storage")]
589 StorageBackend::SurrealDB(storage) => {
590 storage
591 .add_session_to_workspace(workspace_id, session_id, role)
592 .await
593 }
594 }
595 }
596
597 async fn remove_session_from_workspace(
598 &self,
599 workspace_id: Uuid,
600 session_id: Uuid,
601 ) -> Result<()> {
602 match self {
603 StorageBackend::RocksDB(storage) => {
604 storage
605 .remove_session_from_workspace(workspace_id, session_id)
606 .await
607 }
608 #[cfg(feature = "surrealdb-storage")]
609 StorageBackend::SurrealDB(storage) => {
610 storage
611 .remove_session_from_workspace(workspace_id, session_id)
612 .await
613 }
614 }
615 }
616
617 async fn compact(&self) -> Result<()> {
618 match self {
619 StorageBackend::RocksDB(storage) => storage.compact().await,
620 #[cfg(feature = "surrealdb-storage")]
621 StorageBackend::SurrealDB(storage) => storage.compact().await,
622 }
623 }
624
625 async fn get_key_count(&self) -> Result<usize> {
626 match self {
627 StorageBackend::RocksDB(storage) => storage.get_key_count().await,
628 #[cfg(feature = "surrealdb-storage")]
629 StorageBackend::SurrealDB(storage) => storage.get_key_count().await,
630 }
631 }
632
633 async fn get_stats(&self) -> Result<String> {
634 match self {
635 StorageBackend::RocksDB(storage) => storage.get_stats().await,
636 #[cfg(feature = "surrealdb-storage")]
637 StorageBackend::SurrealDB(storage) => storage.get_stats().await,
638 }
639 }
640}
641
642#[async_trait]
644impl GraphStorage for StorageBackend {
645 async fn upsert_entity(&self, session_id: Uuid, entity: &EntityData) -> Result<()> {
646 match self {
647 StorageBackend::RocksDB(storage) => storage.upsert_entity(session_id, entity).await,
648 #[cfg(feature = "surrealdb-storage")]
649 StorageBackend::SurrealDB(storage) => storage.upsert_entity(session_id, entity).await,
650 }
651 }
652
653 async fn get_entity(&self, session_id: Uuid, name: &str) -> Result<Option<EntityData>> {
654 match self {
655 StorageBackend::RocksDB(storage) => storage.get_entity(session_id, name).await,
656 #[cfg(feature = "surrealdb-storage")]
657 StorageBackend::SurrealDB(storage) => storage.get_entity(session_id, name).await,
658 }
659 }
660
661 async fn list_entities(&self, session_id: Uuid) -> Result<Vec<EntityData>> {
662 match self {
663 StorageBackend::RocksDB(storage) => storage.list_entities(session_id).await,
664 #[cfg(feature = "surrealdb-storage")]
665 StorageBackend::SurrealDB(storage) => storage.list_entities(session_id).await,
666 }
667 }
668
669 async fn delete_entity(&self, session_id: Uuid, name: &str) -> Result<()> {
670 match self {
671 StorageBackend::RocksDB(storage) => storage.delete_entity(session_id, name).await,
672 #[cfg(feature = "surrealdb-storage")]
673 StorageBackend::SurrealDB(storage) => storage.delete_entity(session_id, name).await,
674 }
675 }
676
677 async fn create_relationship(
678 &self,
679 session_id: Uuid,
680 relationship: &EntityRelationship,
681 ) -> Result<()> {
682 match self {
683 StorageBackend::RocksDB(storage) => {
684 storage.create_relationship(session_id, relationship).await
685 }
686 #[cfg(feature = "surrealdb-storage")]
687 StorageBackend::SurrealDB(storage) => {
688 storage.create_relationship(session_id, relationship).await
689 }
690 }
691 }
692
693 async fn find_related_entities(
694 &self,
695 session_id: Uuid,
696 entity_name: &str,
697 ) -> Result<Vec<String>> {
698 match self {
699 StorageBackend::RocksDB(storage) => {
700 storage.find_related_entities(session_id, entity_name).await
701 }
702 #[cfg(feature = "surrealdb-storage")]
703 StorageBackend::SurrealDB(storage) => {
704 storage.find_related_entities(session_id, entity_name).await
705 }
706 }
707 }
708
709 async fn find_related_by_type(
710 &self,
711 session_id: Uuid,
712 entity_name: &str,
713 relation_type: &RelationType,
714 ) -> Result<Vec<String>> {
715 match self {
716 StorageBackend::RocksDB(storage) => {
717 storage
718 .find_related_by_type(session_id, entity_name, relation_type)
719 .await
720 }
721 #[cfg(feature = "surrealdb-storage")]
722 StorageBackend::SurrealDB(storage) => {
723 storage
724 .find_related_by_type(session_id, entity_name, relation_type)
725 .await
726 }
727 }
728 }
729
730 async fn find_shortest_path(
731 &self,
732 session_id: Uuid,
733 from: &str,
734 to: &str,
735 ) -> Result<Option<Vec<String>>> {
736 match self {
737 StorageBackend::RocksDB(storage) => {
738 storage.find_shortest_path(session_id, from, to).await
739 }
740 #[cfg(feature = "surrealdb-storage")]
741 StorageBackend::SurrealDB(storage) => {
742 storage.find_shortest_path(session_id, from, to).await
743 }
744 }
745 }
746
747 async fn get_entity_network(
748 &self,
749 session_id: Uuid,
750 center: &str,
751 max_depth: usize,
752 ) -> Result<EntityNetwork> {
753 match self {
754 StorageBackend::RocksDB(storage) => {
755 storage
756 .get_entity_network(session_id, center, max_depth)
757 .await
758 }
759 #[cfg(feature = "surrealdb-storage")]
760 StorageBackend::SurrealDB(storage) => {
761 storage
762 .get_entity_network(session_id, center, max_depth)
763 .await
764 }
765 }
766 }
767}
768
769#[async_trait]
771impl FreshnessStorage for StorageBackend {
772 async fn register_source(&self, session_id: Uuid, reference: SourceReference) -> Result<()> {
773 match self {
774 StorageBackend::RocksDB(storage) => {
775 storage.register_source(session_id, reference).await
776 }
777 #[cfg(feature = "surrealdb-storage")]
778 StorageBackend::SurrealDB(storage) => {
779 storage.register_source(session_id, reference).await
780 }
781 }
782 }
783
784 async fn check_freshness(&self, entry_id: &str, file_hash: &[u8]) -> Result<FreshnessEntry> {
785 match self {
786 StorageBackend::RocksDB(storage) => storage.check_freshness(entry_id, file_hash).await,
787 #[cfg(feature = "surrealdb-storage")]
788 StorageBackend::SurrealDB(storage) => {
789 storage.check_freshness(entry_id, file_hash).await
790 }
791 }
792 }
793
794 async fn invalidate_source(&self, file_path: &str) -> Result<u32> {
795 match self {
796 StorageBackend::RocksDB(storage) => storage.invalidate_source(file_path).await,
797 #[cfg(feature = "surrealdb-storage")]
798 StorageBackend::SurrealDB(storage) => storage.invalidate_source(file_path).await,
799 }
800 }
801
802 async fn get_entries_by_source(&self, file_path: &str) -> Result<Vec<SourceReference>> {
803 match self {
804 StorageBackend::RocksDB(storage) => storage.get_entries_by_source(file_path).await,
805 #[cfg(feature = "surrealdb-storage")]
806 StorageBackend::SurrealDB(storage) => storage.get_entries_by_source(file_path).await,
807 }
808 }
809
810 async fn get_stale_entries_by_source(
811 &self,
812 file_path: &str,
813 ) -> Result<Vec<StaleEntryInfo>> {
814 match self {
815 StorageBackend::RocksDB(storage) => {
816 storage.get_stale_entries_by_source(file_path).await
817 }
818 #[cfg(feature = "surrealdb-storage")]
819 StorageBackend::SurrealDB(storage) => {
820 storage.get_stale_entries_by_source(file_path).await
821 }
822 }
823 }
824
825 async fn check_freshness_semantic(
826 &self,
827 entry_id: &str,
828 file_hash: &[u8],
829 ast_hash: Option<&[u8]>,
830 symbol_name: Option<&str>,
831 ) -> Result<FreshnessEntry> {
832 match self {
833 StorageBackend::RocksDB(storage) => {
834 storage
835 .check_freshness_semantic(entry_id, file_hash, ast_hash, symbol_name)
836 .await
837 }
838 #[cfg(feature = "surrealdb-storage")]
839 StorageBackend::SurrealDB(storage) => {
840 storage
841 .check_freshness_semantic(entry_id, file_hash, ast_hash, symbol_name)
842 .await
843 }
844 }
845 }
846
847 async fn register_symbol_dependencies(
848 &self,
849 from: SymbolId,
850 to_symbols: Vec<SymbolId>,
851 ) -> Result<u32> {
852 match self {
853 StorageBackend::RocksDB(storage) => {
854 storage.register_symbol_dependencies(from, to_symbols).await
855 }
856 #[cfg(feature = "surrealdb-storage")]
857 StorageBackend::SurrealDB(storage) => {
858 storage.register_symbol_dependencies(from, to_symbols).await
859 }
860 }
861 }
862
863 async fn cascade_invalidate(
864 &self,
865 changed: SymbolId,
866 new_ast_hash: Vec<u8>,
867 max_depth: u32,
868 ) -> Result<CascadeInvalidateReport> {
869 match self {
870 StorageBackend::RocksDB(storage) => {
871 storage
872 .cascade_invalidate(changed, new_ast_hash, max_depth)
873 .await
874 }
875 #[cfg(feature = "surrealdb-storage")]
876 StorageBackend::SurrealDB(storage) => {
877 storage
878 .cascade_invalidate(changed, new_ast_hash, max_depth)
879 .await
880 }
881 }
882 }
883
884 async fn check_freshness_batch(
885 &self,
886 entries: Vec<(String, Vec<u8>, Option<Vec<u8>>, Option<String>)>,
887 ) -> Result<Vec<FreshnessEntry>> {
888 match self {
889 StorageBackend::RocksDB(storage) => storage.check_freshness_batch(entries).await,
890 #[cfg(feature = "surrealdb-storage")]
891 StorageBackend::SurrealDB(storage) => storage.check_freshness_batch(entries).await,
892 }
893 }
894}
895
896#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
898pub struct StorageConfig {
899 pub backend: StorageBackendType,
901
902 pub path: std::path::PathBuf,
904
905 #[cfg(feature = "surrealdb-storage")]
907 pub surrealdb: Option<SurrealDBConfig>,
908}
909
910#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
912#[serde(rename_all = "lowercase")]
913#[derive(Default)]
914pub enum StorageBackendType {
915 #[default]
917 RocksDB,
918
919 #[cfg(feature = "surrealdb-storage")]
921 SurrealDB,
922}
923
924
925impl StorageBackendType {
926 pub fn from_str(s: &str) -> Option<Self> {
928 match s.to_lowercase().as_str() {
929 "rocksdb" | "rocks" => Some(StorageBackendType::RocksDB),
930 #[cfg(feature = "surrealdb-storage")]
931 "surrealdb" | "surreal" => Some(StorageBackendType::SurrealDB),
932 _ => None,
933 }
934 }
935}
936
937#[cfg(feature = "surrealdb-storage")]
939#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
940pub struct SurrealDBConfig {
941 pub namespace: String,
943
944 pub database: String,
946
947 pub tikv_endpoints: Option<Vec<String>>,
949}
950
951#[cfg(feature = "surrealdb-storage")]
952impl Default for SurrealDBConfig {
953 fn default() -> Self {
954 Self {
955 namespace: "post_cortex".to_string(),
956 database: "main".to_string(),
957 tikv_endpoints: None,
958 }
959 }
960}
961
962impl Default for StorageConfig {
963 fn default() -> Self {
964 Self {
965 backend: StorageBackendType::default(),
966 path: dirs::data_local_dir()
967 .unwrap_or_else(|| std::path::PathBuf::from("."))
968 .join("post-cortex")
969 .join("data"),
970 #[cfg(feature = "surrealdb-storage")]
971 surrealdb: None,
972 }
973 }
974}