@@ -16,6 +16,7 @@ import {
1616} from "../mod.ts" ;
1717import { FetchClientProvider } from "./FetchClientProvider.ts" ;
1818import { z , type ZodTypeAny } from "zod" ;
19+ import { buildRateLimitHeader } from "./RateLimiter.ts" ;
1920
2021export const TodoSchema = z . object ( {
2122 userId : z . number ( ) ,
@@ -970,6 +971,123 @@ Deno.test("handles 400 response with non-JSON text", async () => {
970971 ) ;
971972} ) ;
972973
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+
9731091function delay ( time : number ) : Promise < void > {
9741092 return new Promise ( ( resolve ) => setTimeout ( resolve , time ) ) ;
9751093}
0 commit comments