@@ -16,6 +16,7 @@ import {
16
16
} from "../mod.ts" ;
17
17
import { FetchClientProvider } from "./FetchClientProvider.ts" ;
18
18
import { z , type ZodTypeAny } from "zod" ;
19
+ import { buildRateLimitHeader } from "./RateLimiter.ts" ;
19
20
20
21
export const TodoSchema = z . object ( {
21
22
userId : z . number ( ) ,
@@ -970,6 +971,123 @@ Deno.test("handles 400 response with non-JSON text", async () => {
970
971
) ;
971
972
} ) ;
972
973
974
+ Deno . test ( "can use per-domain rate limiting with auto-update from headers" , async ( ) => {
975
+ const provider = new FetchClientProvider ( ) ;
976
+
977
+ const groupTracker = new Map < string , number > ( ) ;
978
+
979
+ const startTime = Date . now ( ) ;
980
+
981
+ groupTracker . set ( "api.example.com" , 100 ) ;
982
+ groupTracker . set ( "slow-api.example.com" , 5 ) ;
983
+
984
+ provider . usePerDomainRateLimit ( {
985
+ maxRequests : 50 , // Default limit
986
+ windowSeconds : 60 , // 1 minute default window
987
+ autoUpdateFromHeaders : true ,
988
+ groups : {
989
+ "api.example.com" : {
990
+ maxRequests : 100 ,
991
+ windowSeconds : 60 ,
992
+ } ,
993
+ "slow-api.example.com" : {
994
+ maxRequests : 5 ,
995
+ windowSeconds : 30 ,
996
+ } ,
997
+ } ,
998
+ } ) ;
999
+
1000
+ provider . fetch = (
1001
+ input : RequestInfo | URL ,
1002
+ _init ?: RequestInit ,
1003
+ ) : Promise < Response > => {
1004
+ let url : URL ;
1005
+ if ( input instanceof Request ) {
1006
+ url = new URL ( input . url ) ;
1007
+ } else {
1008
+ url = new URL ( input . toString ( ) ) ;
1009
+ }
1010
+
1011
+ const headers = new Headers ( {
1012
+ "Content-Type" : "application/json" ,
1013
+ } ) ;
1014
+
1015
+ // Simulate different rate limits for different domains
1016
+ if ( url . hostname === "api.example.com" ) {
1017
+ headers . set ( "X-RateLimit-Limit" , "100" ) ;
1018
+ let remaining = groupTracker . get ( "api.example.com" ) ?? 0 ;
1019
+ remaining = remaining > 0 ? remaining - 1 : 0 ;
1020
+ groupTracker . set ( "api.example.com" , remaining ) ;
1021
+ headers . set ( "X-RateLimit-Remaining" , String ( remaining ) ) ;
1022
+ } else if ( url . hostname === "slow-api.example.com" ) {
1023
+ let remaining = groupTracker . get ( "slow-api.example.com" ) ?? 0 ;
1024
+ remaining = remaining > 0 ? remaining - 1 : 0 ;
1025
+ groupTracker . set ( "slow-api.example.com" , remaining ) ;
1026
+
1027
+ headers . set (
1028
+ "RateLimit-Policy" ,
1029
+ buildRateLimitHeader ( {
1030
+ policy : "slow-api.example.com" ,
1031
+ remaining : remaining ,
1032
+ resetSeconds : 30 - ( ( Date . now ( ) - startTime ) / 1000 ) ,
1033
+ } ) ,
1034
+ ) ;
1035
+ headers . set (
1036
+ "RateLimit" ,
1037
+ buildRateLimitHeader ( {
1038
+ policy : "slow-api.example.com" ,
1039
+ remaining : remaining ,
1040
+ resetSeconds : 30 - ( ( Date . now ( ) - startTime ) / 1000 ) ,
1041
+ } ) ,
1042
+ ) ;
1043
+ }
1044
+ // other-api.example.com gets no rate limit headers
1045
+
1046
+ return Promise . resolve (
1047
+ new Response ( JSON . stringify ( { success : true } ) , {
1048
+ status : 200 ,
1049
+ statusText : "OK" ,
1050
+ headers,
1051
+ } ) ,
1052
+ ) ;
1053
+ } ;
1054
+
1055
+ const client = provider . getFetchClient ( ) ;
1056
+
1057
+ const response1 = await client . getJSON (
1058
+ "https://api.example.com/data" ,
1059
+ ) ;
1060
+ assertEquals ( response1 . status , 200 ) ;
1061
+
1062
+ const response2 = await client . getJSON (
1063
+ "https://slow-api.example.com/data" ,
1064
+ ) ;
1065
+ assertEquals ( response2 . status , 200 ) ;
1066
+
1067
+ const response3 = await client . getJSON (
1068
+ "https://other-api.example.com/data" ,
1069
+ ) ;
1070
+ assertEquals ( response3 . status , 200 ) ;
1071
+
1072
+ assert ( provider . rateLimiter ) ;
1073
+
1074
+ const apiOptions = provider . rateLimiter . getGroupOptions ( "api.example.com" ) ;
1075
+ assertEquals ( apiOptions . maxRequests , 100 ) ;
1076
+ assertEquals ( apiOptions . windowSeconds , 60 ) ;
1077
+
1078
+ const slowApiOptions = provider . rateLimiter . getGroupOptions (
1079
+ "slow-api.example.com" ,
1080
+ ) ;
1081
+ assertEquals ( slowApiOptions . maxRequests , 5 ) ;
1082
+ assertEquals ( slowApiOptions . windowSeconds , 30 ) ;
1083
+
1084
+ const otherOptions = provider . rateLimiter . getGroupOptions (
1085
+ "other-api.example.com" ,
1086
+ ) ;
1087
+ assertEquals ( otherOptions . maxRequests , undefined ) ;
1088
+ assertEquals ( otherOptions . windowSeconds , undefined ) ;
1089
+ } ) ;
1090
+
973
1091
function delay ( time : number ) : Promise < void > {
974
1092
return new Promise ( ( resolve ) => setTimeout ( resolve , time ) ) ;
975
1093
}
0 commit comments