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