@@ -762,3 +762,191 @@ export class MemoryRepository implements Repository {
762762 return Promise . resolve ( this . followees [ followeeId . href ] ) ;
763763 }
764764}
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