@winible/winible-typed 2.118.0 → 2.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/typed-model/banner-creative.d.ts +1 -0
- package/dist/typed-model/banner-creative.js +5 -0
- package/dist/typed-model/banner-creative.js.map +1 -1
- package/dist/utils/cacheService/cacheService.d.ts +43 -0
- package/dist/utils/cacheService/cacheService.js +172 -6
- package/dist/utils/cacheService/cacheService.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +3 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/lambdaUtils/initialization.d.ts +13 -4
- package/dist/utils/lambdaUtils/initialization.js +27 -5
- package/dist/utils/lambdaUtils/initialization.js.map +1 -1
- package/dist/utils/openSearch/openSearchClient.d.ts +2 -0
- package/dist/utils/openSearch/openSearchClient.js +47 -0
- package/dist/utils/openSearch/openSearchClient.js.map +1 -0
- package/package.json +2 -1
- package/typed-model/banner-creative.ts +6 -0
- package/utils/cacheService/cacheService.test.ts +435 -0
- package/utils/cacheService/cacheService.ts +187 -6
- package/utils/index.ts +1 -0
- package/utils/lambdaUtils/initialization.test.ts +275 -5
- package/utils/lambdaUtils/initialization.ts +50 -8
- package/utils/openSearch/openSearchClient.ts +43 -0
|
@@ -643,4 +643,439 @@ describe('CacheService', () => {
|
|
|
643
643
|
expect(result).toEqual({ value: null, text: 'example' });
|
|
644
644
|
});
|
|
645
645
|
});
|
|
646
|
+
|
|
647
|
+
describe('Sorted Set Operations (ZSET)', () => {
|
|
648
|
+
describe('zadd', () => {
|
|
649
|
+
it('should add single member with score to sorted set', async () => {
|
|
650
|
+
const result = await cacheService.zadd('leaderboard', [
|
|
651
|
+
{ score: 100, member: 'player1' },
|
|
652
|
+
]);
|
|
653
|
+
expect(result).toBe(1);
|
|
654
|
+
|
|
655
|
+
const count = await cacheService.zcard('leaderboard');
|
|
656
|
+
expect(count).toBe(1);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('should add multiple members with scores to sorted set', async () => {
|
|
660
|
+
const result = await cacheService.zadd('leaderboard', [
|
|
661
|
+
{ score: 100, member: 'player1' },
|
|
662
|
+
{ score: 200, member: 'player2' },
|
|
663
|
+
{ score: 150, member: 'player3' },
|
|
664
|
+
]);
|
|
665
|
+
expect(result).toBe(3);
|
|
666
|
+
|
|
667
|
+
const count = await cacheService.zcard('leaderboard');
|
|
668
|
+
expect(count).toBe(3);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('should update score when member already exists', async () => {
|
|
672
|
+
await cacheService.zadd('scores', [{ score: 100, member: 'user1' }]);
|
|
673
|
+
const result = await cacheService.zadd('scores', [{ score: 200, member: 'user1' }]);
|
|
674
|
+
|
|
675
|
+
// zadd returns 0 when updating existing member
|
|
676
|
+
expect(result).toBe(0);
|
|
677
|
+
|
|
678
|
+
const score = await cacheService.zscore('scores', 'user1');
|
|
679
|
+
expect(score).toBe(200);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should handle floating point scores', async () => {
|
|
683
|
+
const result = await cacheService.zadd('ratings', [
|
|
684
|
+
{ score: 4.5, member: 'item1' },
|
|
685
|
+
{ score: 3.2, member: 'item2' },
|
|
686
|
+
{ score: 4.8, member: 'item3' },
|
|
687
|
+
]);
|
|
688
|
+
expect(result).toBe(3);
|
|
689
|
+
|
|
690
|
+
const score = await cacheService.zscore('ratings', 'item1');
|
|
691
|
+
expect(score).toBe(4.5);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('should handle negative scores', async () => {
|
|
695
|
+
const result = await cacheService.zadd('deltas', [
|
|
696
|
+
{ score: -10, member: 'loss1' },
|
|
697
|
+
{ score: 5, member: 'gain1' },
|
|
698
|
+
{ score: -3, member: 'loss2' },
|
|
699
|
+
]);
|
|
700
|
+
expect(result).toBe(3);
|
|
701
|
+
|
|
702
|
+
const score = await cacheService.zscore('deltas', 'loss1');
|
|
703
|
+
expect(score).toBe(-10);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('should return 0 when not connected', async () => {
|
|
707
|
+
await cacheService.disconnect();
|
|
708
|
+
const result = await cacheService.zadd('test', [{ score: 100, member: 'item' }]);
|
|
709
|
+
expect(result).toBe(0);
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
describe('zrevrange', () => {
|
|
714
|
+
beforeEach(async () => {
|
|
715
|
+
await cacheService.zadd('leaderboard', [
|
|
716
|
+
{ score: 100, member: 'player1' },
|
|
717
|
+
{ score: 300, member: 'player2' },
|
|
718
|
+
{ score: 200, member: 'player3' },
|
|
719
|
+
{ score: 400, member: 'player4' },
|
|
720
|
+
{ score: 150, member: 'player5' },
|
|
721
|
+
]);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('should get members in descending order by score', async () => {
|
|
725
|
+
const members = await cacheService.zrevrange('leaderboard', 0, 2);
|
|
726
|
+
expect(members).toEqual(['player4', 'player2', 'player3']);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('should get all members when range covers entire set', async () => {
|
|
730
|
+
const members = await cacheService.zrevrange('leaderboard', 0, -1);
|
|
731
|
+
expect(members).toEqual(['player4', 'player2', 'player3', 'player5', 'player1']);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('should get members with scores when withScores is true', async () => {
|
|
735
|
+
const result = await cacheService.zrevrange('leaderboard', 0, 2, true) as Array<{ member: string; score: number }>;
|
|
736
|
+
expect(result).toHaveLength(3);
|
|
737
|
+
expect(result[0]).toEqual({ member: 'player4', score: 400 });
|
|
738
|
+
expect(result[1]).toEqual({ member: 'player2', score: 300 });
|
|
739
|
+
expect(result[2]).toEqual({ member: 'player3', score: 200 });
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('should handle pagination with cursor ranges', async () => {
|
|
743
|
+
const firstPage = await cacheService.zrevrange('leaderboard', 0, 1);
|
|
744
|
+
expect(firstPage).toEqual(['player4', 'player2']);
|
|
745
|
+
|
|
746
|
+
const secondPage = await cacheService.zrevrange('leaderboard', 2, 3);
|
|
747
|
+
expect(secondPage).toEqual(['player3', 'player5']);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it('should return empty array when range is out of bounds', async () => {
|
|
751
|
+
const members = await cacheService.zrevrange('leaderboard', 10, 20);
|
|
752
|
+
expect(members).toEqual([]);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it('should return empty array for non-existent key', async () => {
|
|
756
|
+
const members = await cacheService.zrevrange('nonexistent', 0, -1);
|
|
757
|
+
expect(members).toEqual([]);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('should return empty array when not connected', async () => {
|
|
761
|
+
await cacheService.disconnect();
|
|
762
|
+
const members = await cacheService.zrevrange('leaderboard', 0, -1);
|
|
763
|
+
expect(members).toEqual([]);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it('should handle single member result', async () => {
|
|
767
|
+
const members = await cacheService.zrevrange('leaderboard', 0, 0);
|
|
768
|
+
expect(members).toEqual(['player4']);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it('should handle negative indices', async () => {
|
|
772
|
+
const members = await cacheService.zrevrange('leaderboard', -2, -1);
|
|
773
|
+
expect(members).toEqual(['player5', 'player1']);
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
describe('zcard', () => {
|
|
778
|
+
it('should return 0 for non-existent sorted set', async () => {
|
|
779
|
+
const count = await cacheService.zcard('nonexistent');
|
|
780
|
+
expect(count).toBe(0);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it('should return count of members in sorted set', async () => {
|
|
784
|
+
await cacheService.zadd('trending', [
|
|
785
|
+
{ score: 10, member: 'item1' },
|
|
786
|
+
{ score: 20, member: 'item2' },
|
|
787
|
+
{ score: 15, member: 'item3' },
|
|
788
|
+
]);
|
|
789
|
+
|
|
790
|
+
const count = await cacheService.zcard('trending');
|
|
791
|
+
expect(count).toBe(3);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it('should return updated count after adding more members', async () => {
|
|
795
|
+
await cacheService.zadd('posts', [{ score: 5, member: 'post1' }]);
|
|
796
|
+
let count = await cacheService.zcard('posts');
|
|
797
|
+
expect(count).toBe(1);
|
|
798
|
+
|
|
799
|
+
await cacheService.zadd('posts', [
|
|
800
|
+
{ score: 10, member: 'post2' },
|
|
801
|
+
{ score: 7, member: 'post3' },
|
|
802
|
+
]);
|
|
803
|
+
count = await cacheService.zcard('posts');
|
|
804
|
+
expect(count).toBe(3);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should return same count when updating existing member', async () => {
|
|
808
|
+
await cacheService.zadd('votes', [
|
|
809
|
+
{ score: 100, member: 'comment1' },
|
|
810
|
+
{ score: 50, member: 'comment2' },
|
|
811
|
+
]);
|
|
812
|
+
|
|
813
|
+
let count = await cacheService.zcard('votes');
|
|
814
|
+
expect(count).toBe(2);
|
|
815
|
+
|
|
816
|
+
// Update score of existing member
|
|
817
|
+
await cacheService.zadd('votes', [{ score: 150, member: 'comment1' }]);
|
|
818
|
+
count = await cacheService.zcard('votes');
|
|
819
|
+
expect(count).toBe(2);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('should return 0 when not connected', async () => {
|
|
823
|
+
await cacheService.disconnect();
|
|
824
|
+
const count = await cacheService.zcard('test');
|
|
825
|
+
expect(count).toBe(0);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('should handle large sorted sets', async () => {
|
|
829
|
+
const members = Array.from({ length: 100 }, (_, i) => ({
|
|
830
|
+
score: i,
|
|
831
|
+
member: `item${i}`,
|
|
832
|
+
}));
|
|
833
|
+
await cacheService.zadd('large-set', members);
|
|
834
|
+
|
|
835
|
+
const count = await cacheService.zcard('large-set');
|
|
836
|
+
expect(count).toBe(100);
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
describe('zscore', () => {
|
|
841
|
+
beforeEach(async () => {
|
|
842
|
+
await cacheService.zadd('rankings', [
|
|
843
|
+
{ score: 100, member: 'user1' },
|
|
844
|
+
{ score: 200, member: 'user2' },
|
|
845
|
+
{ score: 150.5, member: 'user3' },
|
|
846
|
+
]);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
it('should return score for existing member', async () => {
|
|
850
|
+
const score = await cacheService.zscore('rankings', 'user1');
|
|
851
|
+
expect(score).toBe(100);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('should return null for non-existent member', async () => {
|
|
855
|
+
const score = await cacheService.zscore('rankings', 'nonexistent');
|
|
856
|
+
expect(score).toBeNull();
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it('should return null for non-existent key', async () => {
|
|
860
|
+
const score = await cacheService.zscore('nonexistent', 'user1');
|
|
861
|
+
expect(score).toBeNull();
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('should handle floating point scores', async () => {
|
|
865
|
+
const score = await cacheService.zscore('rankings', 'user3');
|
|
866
|
+
expect(score).toBe(150.5);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it('should return updated score after member update', async () => {
|
|
870
|
+
let score = await cacheService.zscore('rankings', 'user1');
|
|
871
|
+
expect(score).toBe(100);
|
|
872
|
+
|
|
873
|
+
await cacheService.zadd('rankings', [{ score: 300, member: 'user1' }]);
|
|
874
|
+
score = await cacheService.zscore('rankings', 'user1');
|
|
875
|
+
expect(score).toBe(300);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it('should handle negative scores', async () => {
|
|
879
|
+
await cacheService.zadd('deltas', [{ score: -42.5, member: 'negative' }]);
|
|
880
|
+
const score = await cacheService.zscore('deltas', 'negative');
|
|
881
|
+
expect(score).toBe(-42.5);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it('should return null when not connected', async () => {
|
|
885
|
+
await cacheService.disconnect();
|
|
886
|
+
const score = await cacheService.zscore('rankings', 'user1');
|
|
887
|
+
expect(score).toBeNull();
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
describe('zremrangebyrank', () => {
|
|
892
|
+
beforeEach(async () => {
|
|
893
|
+
await cacheService.zadd('cleanup', [
|
|
894
|
+
{ score: 10, member: 'item1' },
|
|
895
|
+
{ score: 20, member: 'item2' },
|
|
896
|
+
{ score: 30, member: 'item3' },
|
|
897
|
+
{ score: 40, member: 'item4' },
|
|
898
|
+
{ score: 50, member: 'item5' },
|
|
899
|
+
]);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it('should remove members by rank range (ascending)', async () => {
|
|
903
|
+
const removed = await cacheService.zremrangebyrank('cleanup', 0, 1);
|
|
904
|
+
expect(removed).toBe(2);
|
|
905
|
+
|
|
906
|
+
const remaining = await cacheService.zcard('cleanup');
|
|
907
|
+
expect(remaining).toBe(3);
|
|
908
|
+
|
|
909
|
+
// Verify lowest scores were removed
|
|
910
|
+
const members = await cacheService.zrevrange('cleanup', 0, -1);
|
|
911
|
+
expect(members).toEqual(['item5', 'item4', 'item3']);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('should remove all members when range covers entire set', async () => {
|
|
915
|
+
const removed = await cacheService.zremrangebyrank('cleanup', 0, -1);
|
|
916
|
+
expect(removed).toBe(5);
|
|
917
|
+
|
|
918
|
+
const count = await cacheService.zcard('cleanup');
|
|
919
|
+
expect(count).toBe(0);
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
it('should remove single member', async () => {
|
|
923
|
+
const removed = await cacheService.zremrangebyrank('cleanup', 0, 0);
|
|
924
|
+
expect(removed).toBe(1);
|
|
925
|
+
|
|
926
|
+
const count = await cacheService.zcard('cleanup');
|
|
927
|
+
expect(count).toBe(4);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it('should handle negative indices', async () => {
|
|
931
|
+
// Remove last 2 members (highest scores)
|
|
932
|
+
const removed = await cacheService.zremrangebyrank('cleanup', -2, -1);
|
|
933
|
+
expect(removed).toBe(2);
|
|
934
|
+
|
|
935
|
+
const members = await cacheService.zrevrange('cleanup', 0, -1);
|
|
936
|
+
expect(members).toEqual(['item3', 'item2', 'item1']);
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it('should return 0 for non-existent key', async () => {
|
|
940
|
+
const removed = await cacheService.zremrangebyrank('nonexistent', 0, 10);
|
|
941
|
+
expect(removed).toBe(0);
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
it('should return 0 when range is out of bounds', async () => {
|
|
945
|
+
const removed = await cacheService.zremrangebyrank('cleanup', 10, 20);
|
|
946
|
+
expect(removed).toBe(0);
|
|
947
|
+
|
|
948
|
+
const count = await cacheService.zcard('cleanup');
|
|
949
|
+
expect(count).toBe(5);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it('should return 0 when not connected', async () => {
|
|
953
|
+
await cacheService.disconnect();
|
|
954
|
+
const removed = await cacheService.zremrangebyrank('cleanup', 0, 1);
|
|
955
|
+
expect(removed).toBe(0);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
it('should remove middle range of members', async () => {
|
|
959
|
+
const removed = await cacheService.zremrangebyrank('cleanup', 1, 3);
|
|
960
|
+
expect(removed).toBe(3);
|
|
961
|
+
|
|
962
|
+
const members = await cacheService.zrevrange('cleanup', 0, -1);
|
|
963
|
+
expect(members).toEqual(['item5', 'item1']);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('should handle cleanup of oldest entries to maintain limit', async () => {
|
|
967
|
+
// Add 100 items
|
|
968
|
+
const items = Array.from({ length: 100 }, (_, i) => ({
|
|
969
|
+
score: i,
|
|
970
|
+
member: `fixture${i}`,
|
|
971
|
+
}));
|
|
972
|
+
await cacheService.zadd('trending-fixtures', items);
|
|
973
|
+
|
|
974
|
+
const initialCount = await cacheService.zcard('trending-fixtures');
|
|
975
|
+
expect(initialCount).toBe(100);
|
|
976
|
+
|
|
977
|
+
// Keep only top 50 by removing bottom 50 (ranks 0-49)
|
|
978
|
+
const removed = await cacheService.zremrangebyrank('trending-fixtures', 0, 49);
|
|
979
|
+
expect(removed).toBe(50);
|
|
980
|
+
|
|
981
|
+
const finalCount = await cacheService.zcard('trending-fixtures');
|
|
982
|
+
expect(finalCount).toBe(50);
|
|
983
|
+
|
|
984
|
+
// Verify highest scores remain
|
|
985
|
+
const topMember = await cacheService.zrevrange('trending-fixtures', 0, 0);
|
|
986
|
+
expect(topMember).toEqual(['fixture99']);
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
describe('ZSET integration scenarios', () => {
|
|
991
|
+
it('should support trending fixtures use case', async () => {
|
|
992
|
+
// Add trending fixtures with bet counts
|
|
993
|
+
await cacheService.zadd('trending-fixtures', [
|
|
994
|
+
{ score: 15, member: 'fixture-abc' },
|
|
995
|
+
{ score: 42, member: 'fixture-def' },
|
|
996
|
+
{ score: 28, member: 'fixture-ghi' },
|
|
997
|
+
{ score: 9, member: 'fixture-jkl' },
|
|
998
|
+
]);
|
|
999
|
+
|
|
1000
|
+
// Get top 3 trending fixtures
|
|
1001
|
+
const topFixtures = await cacheService.zrevrange('trending-fixtures', 0, 2);
|
|
1002
|
+
expect(topFixtures).toEqual(['fixture-def', 'fixture-ghi', 'fixture-abc']);
|
|
1003
|
+
|
|
1004
|
+
// Get fixture with score
|
|
1005
|
+
const withScores = await cacheService.zrevrange('trending-fixtures', 0, 0, true) as Array<{ member: string; score: number }>;
|
|
1006
|
+
expect(withScores[0]).toEqual({ member: 'fixture-def', score: 42 });
|
|
1007
|
+
|
|
1008
|
+
// Check total count
|
|
1009
|
+
const total = await cacheService.zcard('trending-fixtures');
|
|
1010
|
+
expect(total).toBe(4);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('should support pagination for infinite scroll', async () => {
|
|
1014
|
+
// Add 50 fixtures
|
|
1015
|
+
const fixtures = Array.from({ length: 50 }, (_, i) => ({
|
|
1016
|
+
score: 50 - i, // Descending scores
|
|
1017
|
+
member: `fixture-${i}`,
|
|
1018
|
+
}));
|
|
1019
|
+
await cacheService.zadd('paginated-fixtures', fixtures);
|
|
1020
|
+
|
|
1021
|
+
// First page (0-19)
|
|
1022
|
+
const page1 = await cacheService.zrevrange('paginated-fixtures', 0, 19);
|
|
1023
|
+
expect(page1).toHaveLength(20);
|
|
1024
|
+
expect(page1[0]).toBe('fixture-0'); // Highest score
|
|
1025
|
+
|
|
1026
|
+
// Second page (20-39)
|
|
1027
|
+
const page2 = await cacheService.zrevrange('paginated-fixtures', 20, 39);
|
|
1028
|
+
expect(page2).toHaveLength(20);
|
|
1029
|
+
expect(page2[0]).toBe('fixture-20');
|
|
1030
|
+
|
|
1031
|
+
// Last page (40-49)
|
|
1032
|
+
const page3 = await cacheService.zrevrange('paginated-fixtures', 40, 49);
|
|
1033
|
+
expect(page3).toHaveLength(10);
|
|
1034
|
+
|
|
1035
|
+
// Beyond last page
|
|
1036
|
+
const page4 = await cacheService.zrevrange('paginated-fixtures', 50, 69);
|
|
1037
|
+
expect(page4).toHaveLength(0);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
it('should update trending fixture score when bet count changes', async () => {
|
|
1041
|
+
await cacheService.zadd('live-trending', [
|
|
1042
|
+
{ score: 10, member: 'fixture-123' },
|
|
1043
|
+
]);
|
|
1044
|
+
|
|
1045
|
+
let score = await cacheService.zscore('live-trending', 'fixture-123');
|
|
1046
|
+
expect(score).toBe(10);
|
|
1047
|
+
|
|
1048
|
+
// Simulate bet count increase
|
|
1049
|
+
await cacheService.zadd('live-trending', [
|
|
1050
|
+
{ score: 25, member: 'fixture-123' },
|
|
1051
|
+
]);
|
|
1052
|
+
|
|
1053
|
+
score = await cacheService.zscore('live-trending', 'fixture-123');
|
|
1054
|
+
expect(score).toBe(25);
|
|
1055
|
+
|
|
1056
|
+
// Verify count didn't change
|
|
1057
|
+
const count = await cacheService.zcard('live-trending');
|
|
1058
|
+
expect(count).toBe(1);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it('should maintain top 100 limit by removing oldest entries', async () => {
|
|
1062
|
+
// Add 120 fixtures
|
|
1063
|
+
const fixtures = Array.from({ length: 120 }, (_, i) => ({
|
|
1064
|
+
score: Math.floor(Math.random() * 1000),
|
|
1065
|
+
member: `fixture-${i}`,
|
|
1066
|
+
}));
|
|
1067
|
+
await cacheService.zadd('limited-trending', fixtures);
|
|
1068
|
+
|
|
1069
|
+
let count = await cacheService.zcard('limited-trending');
|
|
1070
|
+
expect(count).toBe(120);
|
|
1071
|
+
|
|
1072
|
+
// Remove bottom 20 to keep only top 100
|
|
1073
|
+
const removed = await cacheService.zremrangebyrank('limited-trending', 0, 19);
|
|
1074
|
+
expect(removed).toBe(20);
|
|
1075
|
+
|
|
1076
|
+
count = await cacheService.zcard('limited-trending');
|
|
1077
|
+
expect(count).toBe(100);
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
});
|
|
646
1081
|
});
|
|
@@ -57,14 +57,32 @@ class CacheService {
|
|
|
57
57
|
additionalData?: any;
|
|
58
58
|
}
|
|
59
59
|
): void {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
try {
|
|
61
|
+
if (this.errorHandler) {
|
|
62
|
+
// Override the name property using Object.defineProperty to avoid read-only restriction
|
|
63
|
+
if (context?.method) {
|
|
64
|
+
Object.defineProperty(error, 'name', {
|
|
65
|
+
value: context.method,
|
|
66
|
+
writable: true,
|
|
67
|
+
enumerable: false,
|
|
68
|
+
configurable: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
this.errorHandler(error, undefined, context);
|
|
72
|
+
} else {
|
|
73
|
+
// Fallback to logger if no error handler is set
|
|
74
|
+
logger(message, {
|
|
75
|
+
level: "error",
|
|
76
|
+
method: context?.method || "CacheService",
|
|
77
|
+
action: context?.action || "error",
|
|
78
|
+
error,
|
|
79
|
+
additionalData: context?.additionalData,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
65
83
|
logger(message, {
|
|
66
84
|
level: "error",
|
|
67
|
-
method:
|
|
85
|
+
method: "CacheService.ErrorHandler",
|
|
68
86
|
action: context?.action || "error",
|
|
69
87
|
error,
|
|
70
88
|
additionalData: context?.additionalData,
|
|
@@ -615,6 +633,169 @@ class CacheService {
|
|
|
615
633
|
}
|
|
616
634
|
}
|
|
617
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Add members with scores to a sorted set
|
|
638
|
+
* @param key - The sorted set key
|
|
639
|
+
* @param members - Array of member-score pairs
|
|
640
|
+
* @returns The number of elements added to the sorted set (not including elements already existing for which the score was updated)
|
|
641
|
+
*/
|
|
642
|
+
async zadd(
|
|
643
|
+
key: string,
|
|
644
|
+
members: Array<{ score: number; member: string }>
|
|
645
|
+
): Promise<number> {
|
|
646
|
+
if (!this.isConnected) return 0;
|
|
647
|
+
|
|
648
|
+
try {
|
|
649
|
+
const args: Array<number | string> = [];
|
|
650
|
+
members.forEach(({ score, member }) => {
|
|
651
|
+
args.push(score, member);
|
|
652
|
+
});
|
|
653
|
+
const result = await this.client!.zadd(key, ...args);
|
|
654
|
+
return result;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
this.handleError(error, null, {
|
|
657
|
+
method: "CacheService.zadd",
|
|
658
|
+
action: "error",
|
|
659
|
+
additionalData: {
|
|
660
|
+
message: "Failed to add members to sorted set",
|
|
661
|
+
key,
|
|
662
|
+
count: members.length,
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Get members from a sorted set by score, highest to lowest (descending order)
|
|
671
|
+
* @param key - The sorted set key
|
|
672
|
+
* @param start - Start index (0-based)
|
|
673
|
+
* @param stop - Stop index (-1 for all remaining)
|
|
674
|
+
* @param withScores - Whether to include scores in the result
|
|
675
|
+
* @returns Array of members, or array of {member, score} objects if withScores is true
|
|
676
|
+
*/
|
|
677
|
+
async zrevrange(
|
|
678
|
+
key: string,
|
|
679
|
+
start: number,
|
|
680
|
+
stop: number,
|
|
681
|
+
withScores?: boolean
|
|
682
|
+
): Promise<string[] | Array<{ member: string; score: number }>> {
|
|
683
|
+
if (!this.isConnected) return [];
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
if (withScores) {
|
|
687
|
+
const result = await this.client!.zrevrange(
|
|
688
|
+
key,
|
|
689
|
+
start,
|
|
690
|
+
stop,
|
|
691
|
+
"WITHSCORES"
|
|
692
|
+
);
|
|
693
|
+
const pairs: Array<{ member: string; score: number }> = [];
|
|
694
|
+
for (let i = 0; i < result.length; i += 2) {
|
|
695
|
+
pairs.push({
|
|
696
|
+
member: result[i],
|
|
697
|
+
score: parseFloat(result[i + 1]),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return pairs;
|
|
701
|
+
} else {
|
|
702
|
+
return await this.client!.zrevrange(key, start, stop);
|
|
703
|
+
}
|
|
704
|
+
} catch (error) {
|
|
705
|
+
this.handleError(error, null, {
|
|
706
|
+
method: "CacheService.zrevrange",
|
|
707
|
+
action: "error",
|
|
708
|
+
additionalData: {
|
|
709
|
+
message: "Failed to get members from sorted set",
|
|
710
|
+
key,
|
|
711
|
+
start,
|
|
712
|
+
stop,
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Get the number of members in a sorted set
|
|
721
|
+
* @param key - The sorted set key
|
|
722
|
+
* @returns The cardinality (number of members) of the sorted set
|
|
723
|
+
*/
|
|
724
|
+
async zcard(key: string): Promise<number> {
|
|
725
|
+
if (!this.isConnected) return 0;
|
|
726
|
+
|
|
727
|
+
try {
|
|
728
|
+
return await this.client!.zcard(key);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
this.handleError(error, null, {
|
|
731
|
+
method: "CacheService.zcard",
|
|
732
|
+
action: "error",
|
|
733
|
+
additionalData: {
|
|
734
|
+
message: "Failed to get cardinality of sorted set",
|
|
735
|
+
key,
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
return 0;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Get the score of a member in a sorted set
|
|
744
|
+
* @param key - The sorted set key
|
|
745
|
+
* @param member - The member to get the score for
|
|
746
|
+
* @returns The score of the member, or null if the member doesn't exist
|
|
747
|
+
*/
|
|
748
|
+
async zscore(key: string, member: string): Promise<number | null> {
|
|
749
|
+
if (!this.isConnected) return null;
|
|
750
|
+
|
|
751
|
+
try {
|
|
752
|
+
const score = await this.client!.zscore(key, member);
|
|
753
|
+
return score !== null ? parseFloat(score) : null;
|
|
754
|
+
} catch (error) {
|
|
755
|
+
this.handleError(error, null, {
|
|
756
|
+
method: "CacheService.zscore",
|
|
757
|
+
action: "error",
|
|
758
|
+
additionalData: {
|
|
759
|
+
message: "Failed to get score from sorted set",
|
|
760
|
+
key,
|
|
761
|
+
member,
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Remove all members in a sorted set within the given rank range
|
|
770
|
+
* @param key - The sorted set key
|
|
771
|
+
* @param start - Start rank (0-based)
|
|
772
|
+
* @param stop - Stop rank
|
|
773
|
+
* @returns The number of members removed
|
|
774
|
+
*/
|
|
775
|
+
async zremrangebyrank(
|
|
776
|
+
key: string,
|
|
777
|
+
start: number,
|
|
778
|
+
stop: number
|
|
779
|
+
): Promise<number> {
|
|
780
|
+
if (!this.isConnected) return 0;
|
|
781
|
+
|
|
782
|
+
try {
|
|
783
|
+
return await this.client!.zremrangebyrank(key, start, stop);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
this.handleError(error, null, {
|
|
786
|
+
method: "CacheService.zremrangebyrank",
|
|
787
|
+
action: "error",
|
|
788
|
+
additionalData: {
|
|
789
|
+
message: "Failed to remove members from sorted set by rank",
|
|
790
|
+
key,
|
|
791
|
+
start,
|
|
792
|
+
stop,
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
return 0;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
618
799
|
/**
|
|
619
800
|
* Disconnect the cache client
|
|
620
801
|
* Use this for graceful shutdown
|
package/utils/index.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { default as campaignUtils } from "./campaignUtils";
|
|
|
2
2
|
export { default as generalUtils } from "./generalUtils";
|
|
3
3
|
export { default as logger } from "./logger";
|
|
4
4
|
export { default as CacheService, cacheService } from "./cacheService/cacheService";
|
|
5
|
+
export { initializeOpenSearchClient } from "./openSearch/openSearchClient";
|