@@ -762,3 +762,191 @@ export class MemoryRepository implements Repository {
762
762
return Promise . resolve ( this . followees [ followeeId . href ] ) ;
763
763
}
764
764
}
765
+
766
+ /**
767
+ * A repository decorator that adds an in-memory cache layer on top of another
768
+ * repository. This is useful for improving performance by reducing the number
769
+ * of accesses to the underlying persistent storage, but it increases memory
770
+ * usage. The cache is not persistent and will be lost when the process exits.
771
+ *
772
+ * Note: List operations like `getMessages` and `getFollowers`, and count
773
+ * operations like `countMessages` and `countFollowers` are not cached and
774
+ * always delegate to the underlying repository.
775
+ */
776
+ export class MemoryCachedRepository implements Repository {
777
+ private underlying : Repository ;
778
+ private cache : MemoryRepository ;
779
+
780
+ /**
781
+ * Creates a new memory-cached repository.
782
+ * @param underlying The underlying repository to cache.
783
+ * @param cache An optional `MemoryRepository` instance to use as the cache.
784
+ * If not provided, a new one will be created internally.
785
+ */
786
+ constructor ( underlying : Repository , cache ?: MemoryRepository ) {
787
+ this . underlying = underlying ;
788
+ this . cache = cache ?? new MemoryRepository ( ) ;
789
+ }
790
+
791
+ async setKeyPairs ( keyPairs : CryptoKeyPair [ ] ) : Promise < void > {
792
+ await this . underlying . setKeyPairs ( keyPairs ) ;
793
+ await this . cache . setKeyPairs ( keyPairs ) ;
794
+ }
795
+
796
+ async getKeyPairs ( ) : Promise < CryptoKeyPair [ ] | undefined > {
797
+ let keyPairs = await this . cache . getKeyPairs ( ) ;
798
+ if ( keyPairs === undefined ) {
799
+ keyPairs = await this . underlying . getKeyPairs ( ) ;
800
+ if ( keyPairs !== undefined ) await this . cache . setKeyPairs ( keyPairs ) ;
801
+ }
802
+ return keyPairs ;
803
+ }
804
+
805
+ async addMessage ( id : Uuid , activity : Create | Announce ) : Promise < void > {
806
+ await this . underlying . addMessage ( id , activity ) ;
807
+ await this . cache . addMessage ( id , activity ) ;
808
+ }
809
+
810
+ async updateMessage (
811
+ id : Uuid ,
812
+ updater : (
813
+ existing : Create | Announce ,
814
+ ) => Create | Announce | undefined | Promise < Create | Announce | undefined > ,
815
+ ) : Promise < boolean > {
816
+ // Apply update to underlying first
817
+ const updated = await this . underlying . updateMessage ( id , updater ) ;
818
+ if ( updated ) {
819
+ // If successful, fetch the updated message and update the cache
820
+ const updatedMessage = await this . underlying . getMessage ( id ) ;
821
+ if ( updatedMessage ) {
822
+ await this . cache . addMessage ( id , updatedMessage ) ; // Use addMessage which acts like set
823
+ } else {
824
+ // Should not happen if updateMessage returned true, but handle defensively
825
+ await this . cache . removeMessage ( id ) ;
826
+ }
827
+ }
828
+ return updated ;
829
+ }
830
+
831
+ async removeMessage ( id : Uuid ) : Promise < Create | Announce | undefined > {
832
+ const removedActivity = await this . underlying . removeMessage ( id ) ;
833
+ if ( removedActivity !== undefined ) {
834
+ await this . cache . removeMessage ( id ) ;
835
+ }
836
+ return removedActivity ;
837
+ }
838
+
839
+ // getMessages is not cached due to complexity with options
840
+ getMessages (
841
+ options ?: RepositoryGetMessagesOptions ,
842
+ ) : AsyncIterable < Create | Announce > {
843
+ return this . underlying . getMessages ( options ) ;
844
+ }
845
+
846
+ async getMessage ( id : Uuid ) : Promise < Create | Announce | undefined > {
847
+ let message = await this . cache . getMessage ( id ) ;
848
+ if ( message === undefined ) {
849
+ message = await this . underlying . getMessage ( id ) ;
850
+ if ( message !== undefined ) {
851
+ await this . cache . addMessage ( id , message ) ; // Use addMessage which acts like set
852
+ }
853
+ }
854
+ return message ;
855
+ }
856
+
857
+ // countMessages is not cached
858
+ countMessages ( ) : Promise < number > {
859
+ return this . underlying . countMessages ( ) ;
860
+ }
861
+
862
+ async addFollower ( followId : URL , follower : Actor ) : Promise < void > {
863
+ await this . underlying . addFollower ( followId , follower ) ;
864
+ await this . cache . addFollower ( followId , follower ) ;
865
+ }
866
+
867
+ async removeFollower (
868
+ followId : URL ,
869
+ followerId : URL ,
870
+ ) : Promise < Actor | undefined > {
871
+ const removedFollower = await this . underlying . removeFollower (
872
+ followId ,
873
+ followerId ,
874
+ ) ;
875
+ if ( removedFollower !== undefined ) {
876
+ await this . cache . removeFollower ( followId , followerId ) ;
877
+ }
878
+ return removedFollower ;
879
+ }
880
+
881
+ async hasFollower ( followerId : URL ) : Promise < boolean > {
882
+ // Check cache first for potentially faster response
883
+ if ( await this . cache . hasFollower ( followerId ) ) {
884
+ return true ;
885
+ }
886
+ // If not in cache, check underlying and update cache if found
887
+ const exists = await this . underlying . hasFollower ( followerId ) ;
888
+ // Note: We don't automatically add to cache here, as we don't have the Actor object
889
+ // It will be cached if addFollower is called or if getFollowers iterates over it (though getFollowers isn't cached)
890
+ return exists ;
891
+ }
892
+
893
+ // getFollowers is not cached due to complexity with options
894
+ getFollowers ( options ?: RepositoryGetFollowersOptions ) : AsyncIterable < Actor > {
895
+ // We could potentially cache followers as they are iterated,
896
+ // but for simplicity, delegate directly for now.
897
+ return this . underlying . getFollowers ( options ) ;
898
+ }
899
+
900
+ // countFollowers is not cached
901
+ countFollowers ( ) : Promise < number > {
902
+ return this . underlying . countFollowers ( ) ;
903
+ }
904
+
905
+ async addSentFollow ( id : Uuid , follow : Follow ) : Promise < void > {
906
+ await this . underlying . addSentFollow ( id , follow ) ;
907
+ await this . cache . addSentFollow ( id , follow ) ;
908
+ }
909
+
910
+ async removeSentFollow ( id : Uuid ) : Promise < Follow | undefined > {
911
+ const removedFollow = await this . underlying . removeSentFollow ( id ) ;
912
+ if ( removedFollow !== undefined ) {
913
+ await this . cache . removeSentFollow ( id ) ;
914
+ }
915
+ return removedFollow ;
916
+ }
917
+
918
+ async getSentFollow ( id : Uuid ) : Promise < Follow | undefined > {
919
+ let follow = await this . cache . getSentFollow ( id ) ;
920
+ if ( follow === undefined ) {
921
+ follow = await this . underlying . getSentFollow ( id ) ;
922
+ if ( follow !== undefined ) {
923
+ await this . cache . addSentFollow ( id , follow ) ;
924
+ }
925
+ }
926
+ return follow ;
927
+ }
928
+
929
+ async addFollowee ( followeeId : URL , follow : Follow ) : Promise < void > {
930
+ await this . underlying . addFollowee ( followeeId , follow ) ;
931
+ await this . cache . addFollowee ( followeeId , follow ) ;
932
+ }
933
+
934
+ async removeFollowee ( followeeId : URL ) : Promise < Follow | undefined > {
935
+ const removedFollow = await this . underlying . removeFollowee ( followeeId ) ;
936
+ if ( removedFollow !== undefined ) {
937
+ await this . cache . removeFollowee ( followeeId ) ;
938
+ }
939
+ return removedFollow ;
940
+ }
941
+
942
+ async getFollowee ( followeeId : URL ) : Promise < Follow | undefined > {
943
+ let follow = await this . cache . getFollowee ( followeeId ) ;
944
+ if ( follow === undefined ) {
945
+ follow = await this . underlying . getFollowee ( followeeId ) ;
946
+ if ( follow !== undefined ) {
947
+ await this . cache . addFollowee ( followeeId , follow ) ;
948
+ }
949
+ }
950
+ return follow ;
951
+ }
952
+ }
0 commit comments