[{"data":1,"prerenderedAt":3293},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fpackages\u002Frate-limit":180,"\u002Fdocs\u002Fpackages\u002Frate-limit-surround":3288},[4,21,86,162],{"title":5,"path":6,"stem":7,"children":8,"status":11,"icon":20},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F1.getting-started\u002F1.index",[9,12,16],{"title":10,"path":6,"stem":7,"status":11},"Introduction",null,{"title":13,"path":14,"stem":15,"status":11},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","1.docs\u002F1.getting-started\u002F2.installation",{"title":17,"path":18,"stem":19,"status":11},"Quick Start","\u002Fdocs\u002Fgetting-started\u002Fquick-start","1.docs\u002F1.getting-started\u002F3.quick-start",false,{"title":22,"path":23,"stem":24,"children":25,"status":11,"icon":20},"Core Concepts","\u002Fdocs\u002Fcore-concepts","1.docs\u002F2.core-concepts\u002F1.index",[26,28,32,36,46,50,54,58,62,66,70,74,78,82],{"title":27,"path":23,"stem":24,"status":11},"Overview",{"title":29,"path":30,"stem":31,"status":11},"Response","\u002Fdocs\u002Fcore-concepts\u002Fresponse","1.docs\u002F2.core-concepts\u002F10.response",{"title":33,"path":34,"stem":35,"status":11},"Testing","\u002Fdocs\u002Fcore-concepts\u002Ftesting","1.docs\u002F2.core-concepts\u002F12.testing",{"title":37,"path":38,"stem":39,"children":40,"status":11,"icon":20,"defaultOpen":20},"Decorators","\u002Fdocs\u002Fcore-concepts\u002Fdecorators","1.docs\u002F2.core-concepts\u002F13.decorators\u002F1.index",[41,42],{"title":27,"path":38,"stem":39,"status":11},{"title":43,"path":44,"stem":45,"status":11},"Custom","\u002Fdocs\u002Fcore-concepts\u002Fdecorators\u002Fcustom","1.docs\u002F2.core-concepts\u002F13.decorators\u002F2.custom",{"title":47,"path":48,"stem":49,"status":11},"Discovery Service","\u002Fdocs\u002Fcore-concepts\u002Fdiscovery","1.docs\u002F2.core-concepts\u002F14.discovery",{"title":51,"path":52,"stem":53,"status":11},"Application Lifecycle","\u002Fdocs\u002Fcore-concepts\u002Fapp-lifecycle","1.docs\u002F2.core-concepts\u002F15.app-lifecycle",{"title":55,"path":56,"stem":57,"status":11},"Controllers","\u002Fdocs\u002Fcore-concepts\u002Fcontrollers","1.docs\u002F2.core-concepts\u002F2.controllers",{"title":59,"path":60,"stem":61,"status":11},"Routing","\u002Fdocs\u002Fcore-concepts\u002Frouting","1.docs\u002F2.core-concepts\u002F3.routing",{"title":63,"path":64,"stem":65,"status":11},"Providers","\u002Fdocs\u002Fcore-concepts\u002Fproviders","1.docs\u002F2.core-concepts\u002F4.providers",{"title":67,"path":68,"stem":69,"status":11},"Modules","\u002Fdocs\u002Fcore-concepts\u002Fmodules","1.docs\u002F2.core-concepts\u002F5.modules",{"title":71,"path":72,"stem":73,"status":11},"Configuration","\u002Fdocs\u002Fcore-concepts\u002Fconfiguration","1.docs\u002F2.core-concepts\u002F6.configuration",{"title":75,"path":76,"stem":77,"status":11},"Middleware","\u002Fdocs\u002Fcore-concepts\u002Fmiddleware","1.docs\u002F2.core-concepts\u002F7.middleware",{"title":79,"path":80,"stem":81,"status":11},"Guards","\u002Fdocs\u002Fcore-concepts\u002Fguards","1.docs\u002F2.core-concepts\u002F8.guards",{"title":83,"path":84,"stem":85,"status":11},"Exceptions","\u002Fdocs\u002Fcore-concepts\u002Fexceptions","1.docs\u002F2.core-concepts\u002F9.exceptions",{"title":87,"path":88,"stem":89,"children":90,"status":11,"icon":20},"Packages","\u002Fdocs\u002Fpackages","1.docs\u002F3.packages\u002F1.index",[91,92,97,108,112,116,134,138,142,146,150,154,158],{"title":27,"path":88,"stem":89,"status":11},{"title":93,"path":94,"stem":95,"status":96},"CLI","\u002Fdocs\u002Fpackages\u002Fcli","1.docs\u002F3.packages\u002F10.cli","experimental",{"title":98,"path":99,"stem":100,"children":101,"status":11,"icon":20,"defaultOpen":20},"Events","\u002Fdocs\u002Fpackages\u002Fmessaging","1.docs\u002F3.packages\u002F11.messaging\u002F1.index",[102,104],{"title":27,"path":99,"stem":100,"status":103},"beta",{"title":105,"path":106,"stem":107,"status":96},"Redis","\u002Fdocs\u002Fpackages\u002Fmessaging\u002Fredis","1.docs\u002F3.packages\u002F11.messaging\u002F2.redis",{"title":109,"path":110,"stem":111,"status":103},"Serve Static","\u002Fdocs\u002Fpackages\u002Fserve-static","1.docs\u002F3.packages\u002F12.serve-static",{"title":113,"path":114,"stem":115,"status":103},"Rate Limit","\u002Fdocs\u002Fpackages\u002Frate-limit","1.docs\u002F3.packages\u002F13.rate-limit",{"title":117,"path":118,"stem":119,"children":120,"status":11,"icon":20,"defaultOpen":20},"Auth","\u002Fdocs\u002Fpackages\u002Fauth","1.docs\u002F3.packages\u002F2.auth\u002F1.index",[121,122,126,130],{"title":27,"path":118,"stem":119,"status":103},{"title":123,"path":124,"stem":125,"status":103},"JWT Provider","\u002Fdocs\u002Fpackages\u002Fauth\u002Fjwt","1.docs\u002F3.packages\u002F2.auth\u002F2.jwt",{"title":127,"path":128,"stem":129,"status":103},"Local Provider","\u002Fdocs\u002Fpackages\u002Fauth\u002Flocal","1.docs\u002F3.packages\u002F2.auth\u002F3.local",{"title":131,"path":132,"stem":133,"status":96},"OAuth2 Provider","\u002Fdocs\u002Fpackages\u002Fauth\u002Foauth2","1.docs\u002F3.packages\u002F2.auth\u002F4.oauth2",{"title":135,"path":136,"stem":137,"status":103},"JWT","\u002Fdocs\u002Fpackages\u002Fjwt","1.docs\u002F3.packages\u002F3.jwt",{"title":139,"path":140,"stem":141,"status":103},"Drizzle","\u002Fdocs\u002Fpackages\u002Fdrizzle","1.docs\u002F3.packages\u002F4.drizzle",{"title":143,"path":144,"stem":145,"status":103},"Papr","\u002Fdocs\u002Fpackages\u002Fpapr","1.docs\u002F3.packages\u002F5.papr",{"title":147,"path":148,"stem":149,"status":103},"Mongoose","\u002Fdocs\u002Fpackages\u002Fmongoose","1.docs\u002F3.packages\u002F6.mongoose",{"title":151,"path":152,"stem":153,"status":103},"Swagger","\u002Fdocs\u002Fpackages\u002Fswagger","1.docs\u002F3.packages\u002F7.swagger",{"title":155,"path":156,"stem":157,"status":103},"Node Server","\u002Fdocs\u002Fpackages\u002Fnode-server","1.docs\u002F3.packages\u002F8.node-server",{"title":159,"path":160,"stem":161,"status":103},"uWS Server","\u002Fdocs\u002Fpackages\u002Fuws-server","1.docs\u002F3.packages\u002F9.uws-server",{"title":163,"path":164,"stem":165,"children":166,"status":11,"icon":20},"Roadmap","\u002Fdocs\u002Froadmap","1.docs\u002F4.roadmap\u002F1.index",[167,168,172,176],{"title":27,"path":164,"stem":165,"status":11},{"title":169,"path":170,"stem":171,"status":11},"Short term (0-3 months)","\u002Fdocs\u002Froadmap\u002Fshort-term","1.docs\u002F4.roadmap\u002F2.short-term",{"title":173,"path":174,"stem":175,"status":11},"Mid term (3-9 months)","\u002Fdocs\u002Froadmap\u002Fmid-term","1.docs\u002F4.roadmap\u002F3.mid-term",{"title":177,"path":178,"stem":179,"status":11},"Long term (9-12+ months)","\u002Fdocs\u002Froadmap\u002Flong-term","1.docs\u002F4.roadmap\u002F4.long-term",{"id":181,"title":113,"body":182,"description":3283,"extension":3284,"meta":3285,"navigation":380,"path":114,"seo":3286,"status":103,"stem":115,"__hash__":3287},"docs\u002F1.docs\u002F3.packages\u002F13.rate-limit.md",{"type":183,"value":184,"toc":3261},"minimark",[185,221,225,296,302,306,309,571,577,679,701,705,715,969,989,1016,1044,1074,1078,1084,1154,1165,1172,1176,1181,1193,1339,1343,1348,1473,1477,1495,1499,1508,1611,1628,1634,1736,1740,1746,1803,1820,1828,1832,1840,1933,1937,1968,2099,2118,2138,2142,2159,2164,2220,2224,2230,2434,2448,2452,2459,2598,2601,2611,3022,3026,3210,3214,3257],[186,187,188,192,193,196,197,200,201,204,205,208,209,212,213,216,217,220],"p",{},[189,190,191],"code",{},"@miiajs\u002Frate-limit"," adds fixed-window rate limiting to MiiaJS. The primary path is the guard flow: configure a default policy once, enforce it app-wide with ",[189,194,195],{},"RateLimitGuard",", and tune individual routes with ",[189,198,199],{},"@RateLimit"," \u002F ",[189,202,203],{},"@SkipRateLimit",". A separate ",[189,206,207],{},"rateLimit()"," middleware protects the perimeter - including requests that never match a route. Both are thin layers over the same ",[189,210,211],{},"RateLimiter"," core, share one ",[189,214,215],{},"RateLimitStore"," contract, and emit standard ",[189,218,219],{},"RateLimit-*"," headers. The package has no runtime dependencies.",[222,223,13],"h2",{"id":224},"installation",[226,227,228,253,268,282],"code-group",{},[229,230,236],"pre",{"className":231,"code":232,"filename":233,"language":234,"meta":235,"style":235},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","bun add @miiajs\u002Frate-limit\n","bun","bash","",[189,237,238],{"__ignoreMap":235},[239,240,243,246,250],"span",{"class":241,"line":242},"line",1,[239,244,233],{"class":245},"sBMFI",[239,247,249],{"class":248},"sfazB"," add",[239,251,252],{"class":248}," @miiajs\u002Frate-limit\n",[229,254,257],{"className":231,"code":255,"filename":256,"language":234,"meta":235,"style":235},"npm install @miiajs\u002Frate-limit\n","npm",[189,258,259],{"__ignoreMap":235},[239,260,261,263,266],{"class":241,"line":242},[239,262,256],{"class":245},[239,264,265],{"class":248}," install",[239,267,252],{"class":248},[229,269,272],{"className":231,"code":270,"filename":271,"language":234,"meta":235,"style":235},"pnpm add @miiajs\u002Frate-limit\n","pnpm",[189,273,274],{"__ignoreMap":235},[239,275,276,278,280],{"class":241,"line":242},[239,277,271],{"class":245},[239,279,249],{"class":248},[239,281,252],{"class":248},[229,283,286],{"className":231,"code":284,"filename":285,"language":234,"meta":235,"style":235},"yarn add @miiajs\u002Frate-limit\n","yarn",[189,287,288],{"__ignoreMap":235},[239,289,290,292,294],{"class":241,"line":242},[239,291,285],{"class":245},[239,293,249],{"class":248},[239,295,252],{"class":248},[186,297,298,301],{},[189,299,300],{},"@miiajs\u002Fcore"," is a required peer dependency.",[222,303,305],{"id":304},"quick-start","Quick start",[186,307,308],{},"Configure a default policy in your root module and enforce it on every matched route with the global guard:",[229,310,314],{"className":311,"code":312,"language":313,"meta":235,"style":235},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { Miia, Module } from '@miiajs\u002Fcore'\nimport { RateLimitGuard, RateLimitModule } from '@miiajs\u002Frate-limit'\n\n@Module({\n  imports: [\n    RateLimitModule.configure({ limit: 100, window: '1m' }),\n  ],\n  controllers: [UserController],\n})\nclass AppModule {}\n\nconst app = new Miia().register(AppModule)\napp.useGuard(RateLimitGuard)\nawait app.listen(3000)\n","typescript",[189,315,316,350,375,382,398,411,460,468,481,490,503,508,536,550],{"__ignoreMap":235},[239,317,318,322,326,330,333,336,339,342,345,347],{"class":241,"line":242},[239,319,321],{"class":320},"s7zQu","import",[239,323,325],{"class":324},"sMK4o"," {",[239,327,329],{"class":328},"sTEyZ"," Miia",[239,331,332],{"class":324},",",[239,334,335],{"class":328}," Module",[239,337,338],{"class":324}," }",[239,340,341],{"class":320}," from",[239,343,344],{"class":324}," '",[239,346,300],{"class":248},[239,348,349],{"class":324},"'\n",[239,351,353,355,357,360,362,365,367,369,371,373],{"class":241,"line":352},2,[239,354,321],{"class":320},[239,356,325],{"class":324},[239,358,359],{"class":328}," RateLimitGuard",[239,361,332],{"class":324},[239,363,364],{"class":328}," RateLimitModule",[239,366,338],{"class":324},[239,368,341],{"class":320},[239,370,344],{"class":324},[239,372,191],{"class":248},[239,374,349],{"class":324},[239,376,378],{"class":241,"line":377},3,[239,379,381],{"emptyLinePlaceholder":380},true,"\n",[239,383,385,388,392,395],{"class":241,"line":384},4,[239,386,387],{"class":324},"@",[239,389,391],{"class":390},"s2Zo4","Module",[239,393,394],{"class":328},"(",[239,396,397],{"class":324},"{\n",[239,399,401,405,408],{"class":241,"line":400},5,[239,402,404],{"class":403},"swJcz","  imports",[239,406,407],{"class":324},":",[239,409,410],{"class":328}," [\n",[239,412,414,417,420,423,425,428,431,433,437,439,442,444,446,449,452,454,457],{"class":241,"line":413},6,[239,415,416],{"class":328},"    RateLimitModule",[239,418,419],{"class":324},".",[239,421,422],{"class":390},"configure",[239,424,394],{"class":328},[239,426,427],{"class":324},"{",[239,429,430],{"class":403}," limit",[239,432,407],{"class":324},[239,434,436],{"class":435},"sbssI"," 100",[239,438,332],{"class":324},[239,440,441],{"class":403}," window",[239,443,407],{"class":324},[239,445,344],{"class":324},[239,447,448],{"class":248},"1m",[239,450,451],{"class":324},"'",[239,453,338],{"class":324},[239,455,456],{"class":328},")",[239,458,459],{"class":324},",\n",[239,461,463,466],{"class":241,"line":462},7,[239,464,465],{"class":328},"  ]",[239,467,459],{"class":324},[239,469,471,474,476,479],{"class":241,"line":470},8,[239,472,473],{"class":403},"  controllers",[239,475,407],{"class":324},[239,477,478],{"class":328}," [UserController]",[239,480,459],{"class":324},[239,482,484,487],{"class":241,"line":483},9,[239,485,486],{"class":324},"}",[239,488,489],{"class":328},")\n",[239,491,493,497,500],{"class":241,"line":492},10,[239,494,496],{"class":495},"spNyl","class",[239,498,499],{"class":245}," AppModule",[239,501,502],{"class":324}," {}\n",[239,504,506],{"class":241,"line":505},11,[239,507,381],{"emptyLinePlaceholder":380},[239,509,511,514,517,520,523,525,528,530,533],{"class":241,"line":510},12,[239,512,513],{"class":495},"const",[239,515,516],{"class":328}," app ",[239,518,519],{"class":324},"=",[239,521,522],{"class":324}," new",[239,524,329],{"class":390},[239,526,527],{"class":328},"()",[239,529,419],{"class":324},[239,531,532],{"class":390},"register",[239,534,535],{"class":328},"(AppModule)\n",[239,537,539,542,544,547],{"class":241,"line":538},13,[239,540,541],{"class":328},"app",[239,543,419],{"class":324},[239,545,546],{"class":390},"useGuard",[239,548,549],{"class":328},"(RateLimitGuard)\n",[239,551,553,556,559,561,564,566,569],{"class":241,"line":552},14,[239,554,555],{"class":320},"await",[239,557,558],{"class":328}," app",[239,560,419],{"class":324},[239,562,563],{"class":390},"listen",[239,565,394],{"class":328},[239,567,568],{"class":435},"3000",[239,570,489],{"class":328},[186,572,573,574,407],{},"Each client - keyed by IP by default - gets 100 requests per minute, shared across all matched routes. When the quota runs out, the request is rejected with a structured ",[189,575,576],{},"429",[229,578,582],{"className":579,"code":580,"language":581,"meta":235,"style":235},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"statusCode\": 429,\n  \"error\": \"Too Many Requests\",\n  \"message\": \"Too Many Requests\",\n  \"details\": { \"retryAfter\": 42 }\n}\n","json",[189,583,584,588,606,627,646,674],{"__ignoreMap":235},[239,585,586],{"class":241,"line":242},[239,587,397],{"class":324},[239,589,590,593,596,599,601,604],{"class":241,"line":352},[239,591,592],{"class":324},"  \"",[239,594,595],{"class":495},"statusCode",[239,597,598],{"class":324},"\"",[239,600,407],{"class":324},[239,602,603],{"class":435}," 429",[239,605,459],{"class":324},[239,607,608,610,613,615,617,620,623,625],{"class":241,"line":377},[239,609,592],{"class":324},[239,611,612],{"class":495},"error",[239,614,598],{"class":324},[239,616,407],{"class":324},[239,618,619],{"class":324}," \"",[239,621,622],{"class":248},"Too Many Requests",[239,624,598],{"class":324},[239,626,459],{"class":324},[239,628,629,631,634,636,638,640,642,644],{"class":241,"line":384},[239,630,592],{"class":324},[239,632,633],{"class":495},"message",[239,635,598],{"class":324},[239,637,407],{"class":324},[239,639,619],{"class":324},[239,641,622],{"class":248},[239,643,598],{"class":324},[239,645,459],{"class":324},[239,647,648,650,653,655,657,659,661,664,666,668,671],{"class":241,"line":400},[239,649,592],{"class":324},[239,651,652],{"class":495},"details",[239,654,598],{"class":324},[239,656,407],{"class":324},[239,658,325],{"class":324},[239,660,619],{"class":324},[239,662,663],{"class":245},"retryAfter",[239,665,598],{"class":324},[239,667,407],{"class":324},[239,669,670],{"class":435}," 42",[239,672,673],{"class":324}," }\n",[239,675,676],{"class":241,"line":413},[239,677,678],{"class":324},"}\n",[186,680,681,684,685,688,689,688,692,688,695,688,698,419],{},[189,682,683],{},"window"," accepts milliseconds or a duration string: ",[189,686,687],{},"'500ms'",", ",[189,690,691],{},"'10s'",[189,693,694],{},"'5m'",[189,696,697],{},"'1h'",[189,699,700],{},"'1d'",[222,702,704],{"id":703},"per-route-policies","Per-route policies",[186,706,707,710,711,714],{},[189,708,709],{},"@RateLimit(policy)"," sets a policy for a single route (on a method) or for every route of a controller (on the class). The most specific policy wins - the same precedence as ",[189,712,713],{},"@BodyLimit",": a method policy replaces the class policy and the global guard; a class policy replaces the global guard. A decorated route keeps its own counter and does not consume the global quota.",[229,716,718],{"className":311,"code":717,"language":313,"meta":235,"style":235},"import { Controller, Get, type RequestContext } from '@miiajs\u002Fcore'\nimport { RateLimit, SkipRateLimit } from '@miiajs\u002Frate-limit'\n\n@Controller('search')\nclass SearchController {\n  \u002F\u002F Replaces the global 100\u002Fmin for this route: callers get a true 1000\u002Fmin,\n  \u002F\u002F and these requests do not count against the global quota.\n  @RateLimit({ limit: 1000, window: '1m' })\n  @Get()\n  search(ctx: RequestContext) {\n    \u002F* ... *\u002F\n  }\n\n  \u002F\u002F No rate limit on this route at all.\n  @SkipRateLimit()\n  @Get('health')\n  health() {\n    return { ok: true }\n  }\n}\n",[189,719,720,752,776,780,798,808,814,819,854,864,883,888,893,897,902,912,930,940,959,964],{"__ignoreMap":235},[239,721,722,724,726,729,731,734,736,739,742,744,746,748,750],{"class":241,"line":242},[239,723,321],{"class":320},[239,725,325],{"class":324},[239,727,728],{"class":328}," Controller",[239,730,332],{"class":324},[239,732,733],{"class":328}," Get",[239,735,332],{"class":324},[239,737,738],{"class":320}," type",[239,740,741],{"class":328}," RequestContext",[239,743,338],{"class":324},[239,745,341],{"class":320},[239,747,344],{"class":324},[239,749,300],{"class":248},[239,751,349],{"class":324},[239,753,754,756,758,761,763,766,768,770,772,774],{"class":241,"line":352},[239,755,321],{"class":320},[239,757,325],{"class":324},[239,759,760],{"class":328}," RateLimit",[239,762,332],{"class":324},[239,764,765],{"class":328}," SkipRateLimit",[239,767,338],{"class":324},[239,769,341],{"class":320},[239,771,344],{"class":324},[239,773,191],{"class":248},[239,775,349],{"class":324},[239,777,778],{"class":241,"line":377},[239,779,381],{"emptyLinePlaceholder":380},[239,781,782,784,787,789,791,794,796],{"class":241,"line":384},[239,783,387],{"class":324},[239,785,786],{"class":390},"Controller",[239,788,394],{"class":328},[239,790,451],{"class":324},[239,792,793],{"class":248},"search",[239,795,451],{"class":324},[239,797,489],{"class":328},[239,799,800,802,805],{"class":241,"line":400},[239,801,496],{"class":495},[239,803,804],{"class":245}," SearchController",[239,806,807],{"class":324}," {\n",[239,809,810],{"class":241,"line":413},[239,811,813],{"class":812},"sHwdD","  \u002F\u002F Replaces the global 100\u002Fmin for this route: callers get a true 1000\u002Fmin,\n",[239,815,816],{"class":241,"line":462},[239,817,818],{"class":812},"  \u002F\u002F and these requests do not count against the global quota.\n",[239,820,821,824,827,829,831,833,835,838,840,842,844,846,848,850,852],{"class":241,"line":470},[239,822,823],{"class":324},"  @",[239,825,826],{"class":390},"RateLimit",[239,828,394],{"class":328},[239,830,427],{"class":324},[239,832,430],{"class":403},[239,834,407],{"class":324},[239,836,837],{"class":435}," 1000",[239,839,332],{"class":324},[239,841,441],{"class":403},[239,843,407],{"class":324},[239,845,344],{"class":324},[239,847,448],{"class":248},[239,849,451],{"class":324},[239,851,338],{"class":324},[239,853,489],{"class":328},[239,855,856,858,861],{"class":241,"line":483},[239,857,823],{"class":324},[239,859,860],{"class":390},"Get",[239,862,863],{"class":328},"()\n",[239,865,866,869,871,875,877,879,881],{"class":241,"line":492},[239,867,868],{"class":403},"  search",[239,870,394],{"class":324},[239,872,874],{"class":873},"sHdIc","ctx",[239,876,407],{"class":324},[239,878,741],{"class":245},[239,880,456],{"class":324},[239,882,807],{"class":324},[239,884,885],{"class":241,"line":505},[239,886,887],{"class":812},"    \u002F* ... *\u002F\n",[239,889,890],{"class":241,"line":510},[239,891,892],{"class":324},"  }\n",[239,894,895],{"class":241,"line":538},[239,896,381],{"emptyLinePlaceholder":380},[239,898,899],{"class":241,"line":552},[239,900,901],{"class":812},"  \u002F\u002F No rate limit on this route at all.\n",[239,903,905,907,910],{"class":241,"line":904},15,[239,906,823],{"class":324},[239,908,909],{"class":390},"SkipRateLimit",[239,911,863],{"class":328},[239,913,915,917,919,921,923,926,928],{"class":241,"line":914},16,[239,916,823],{"class":324},[239,918,860],{"class":390},[239,920,394],{"class":328},[239,922,451],{"class":324},[239,924,925],{"class":248},"health",[239,927,451],{"class":324},[239,929,489],{"class":328},[239,931,933,936,938],{"class":241,"line":932},17,[239,934,935],{"class":403},"  health",[239,937,527],{"class":324},[239,939,807],{"class":324},[239,941,943,946,948,951,953,957],{"class":241,"line":942},18,[239,944,945],{"class":320},"    return",[239,947,325],{"class":324},[239,949,950],{"class":403}," ok",[239,952,407],{"class":324},[239,954,956],{"class":955},"sfNiH"," true",[239,958,673],{"class":324},[239,960,962],{"class":241,"line":961},19,[239,963,892],{"class":324},[239,965,967],{"class":241,"line":966},20,[239,968,678],{"class":324},[186,970,971,972,688,975,688,978,981,982,984,985,988],{},"Route policies inherit ",[189,973,974],{},"store",[189,976,977],{},"keyGenerator",[189,979,980],{},"headers",", and ",[189,983,633],{}," from ",[189,986,987],{},"RateLimitModule.configure()"," - the policy fields you pass to the decorator override the configured defaults.",[186,990,991,992,994,995,999,1000,1003,1004,1006,1007,1009,1010,1012,1013,1015],{},"One exception to the ",[189,993,713],{}," analogy: ",[996,997,998],"strong",{},"skip beats specificity",". ",[189,1001,1002],{},"@SkipRateLimit()"," disables rate limiting entirely on its scope - including a ",[189,1005,199],{}," on the same method, and a class-level ",[189,1008,1002],{}," also disables method-level ",[189,1011,199],{}," policies inside that controller. The precedence chain only applies between ",[189,1014,199],{}," policies.",[1017,1018,1019,1032],"note",{},[186,1020,1021,1022,1024,1025,1028,1029,1031],{},"To disable rate limiting on a route, use ",[189,1023,1002],{}," - not ",[189,1026,1027],{},"@SkipGuard(RateLimitGuard)",". The generic skip only catches the global guard and explicit factory guards; policies applied through ",[189,1030,199],{}," are out of its reach.",[186,1033,1034,1035,1038,1039,1041,1042,419],{},"Also note that ",[189,1036,1037],{},"app.useGuard(RateLimitGuard)"," constructs the guard at startup. Without ",[189,1040,987],{}," the app fails fast with a configuration error, even if every route declares its own ",[189,1043,199],{},[1045,1046,1047],"warning",{},[186,1048,1049,1050,1052,1053,1055,1056,1059,1060,1063,1064,1066,1067,1070,1071,1073],{},"Register ",[189,1051,987],{}," in the same module tree as the controllers that use ",[189,1054,199],{},". The DI container is flat, so the placement inside the ",[189,1057,1058],{},"imports"," graph does not matter - but a feature module registered through a separate, earlier ",[189,1061,1062],{},"app.register()"," call constructs its guards before the configuration provider exists. A ",[189,1065,199],{}," with a full policy (",[189,1068,1069],{},"limit"," + ",[189,1072,683],{},") works without the module; the error only fires when neither the decorator nor the module provides them.",[222,1075,1077],{"id":1076},"perimeter-limiting","Perimeter limiting",[186,1079,1080,1081,1083],{},"Global guards run only on matched routes - a scanner probing non-existent paths never reaches them. The ",[189,1082,207],{}," middleware runs before routing, so it covers 404s too, and it works standalone with no module or DI setup:",[229,1085,1087],{"className":311,"code":1086,"language":313,"meta":235,"style":235},"import { rateLimit } from '@miiajs\u002Frate-limit'\n\napp.use(rateLimit({ limit: 300, window: '1m' }))\n",[189,1088,1089,1108,1112],{"__ignoreMap":235},[239,1090,1091,1093,1095,1098,1100,1102,1104,1106],{"class":241,"line":242},[239,1092,321],{"class":320},[239,1094,325],{"class":324},[239,1096,1097],{"class":328}," rateLimit",[239,1099,338],{"class":324},[239,1101,341],{"class":320},[239,1103,344],{"class":324},[239,1105,191],{"class":248},[239,1107,349],{"class":324},[239,1109,1110],{"class":241,"line":352},[239,1111,381],{"emptyLinePlaceholder":380},[239,1113,1114,1116,1118,1121,1123,1126,1128,1130,1132,1134,1137,1139,1141,1143,1145,1147,1149,1151],{"class":241,"line":377},[239,1115,541],{"class":328},[239,1117,419],{"class":324},[239,1119,1120],{"class":390},"use",[239,1122,394],{"class":328},[239,1124,1125],{"class":390},"rateLimit",[239,1127,394],{"class":328},[239,1129,427],{"class":324},[239,1131,430],{"class":403},[239,1133,407],{"class":324},[239,1135,1136],{"class":435}," 300",[239,1138,332],{"class":324},[239,1140,441],{"class":403},[239,1142,407],{"class":324},[239,1144,344],{"class":324},[239,1146,448],{"class":248},[239,1148,451],{"class":324},[239,1150,338],{"class":324},[239,1152,1153],{"class":328},"))\n",[1045,1155,1156],{},[186,1157,1158,1159,1161,1162,1164],{},"Do not combine a global ",[189,1160,207],{}," middleware with the guard flow. The two layers know nothing about each other: ",[189,1163,1002],{}," and per-route replacement have no effect on middleware. Pick the guard flow for business policies; reach for the middleware when you need perimeter coverage or a zero-setup limiter.",[186,1166,1167,1168,1171],{},"The middleware is also the deliberate way to stack limits: a route-bound ",[189,1169,1170],{},"@Use(rateLimit({ ... }))"," counts independently of whatever guard policy is active on the same route.",[222,1173,1175],{"id":1174},"configuration-options","Configuration options",[1177,1178,1180],"h3",{"id":1179},"policy","Policy",[186,1182,1183,1184,688,1186,688,1188,981,1190,407],{},"Accepted by ",[189,1185,199],{},[189,1187,987],{},[189,1189,207],{},[189,1191,1192],{},"new RateLimiter()",[1194,1195,1196,1215],"table",{},[1197,1198,1199],"thead",{},[1200,1201,1202,1206,1209,1212],"tr",{},[1203,1204,1205],"th",{},"Option",[1203,1207,1208],{},"Type",[1203,1210,1211],{},"Default",[1203,1213,1214],{},"Description",[1216,1217,1218,1236,1262,1278,1304,1321],"tbody",{},[1200,1219,1220,1225,1230,1233],{},[1221,1222,1223],"td",{},[189,1224,1069],{},[1221,1226,1227],{},[189,1228,1229],{},"number",[1221,1231,1232],{},"-",[1221,1234,1235],{},"Max requests per window",[1200,1237,1238,1242,1247,1249],{},[1221,1239,1240],{},[189,1241,683],{},[1221,1243,1244],{},[189,1245,1246],{},"number | string",[1221,1248,1232],{},[1221,1250,1251,1252,200,1254,200,1256,200,1258,200,1260],{},"Window length: ms or ",[189,1253,687],{},[189,1255,691],{},[189,1257,694],{},[189,1259,697],{},[189,1261,700],{},[1200,1263,1264,1269,1273,1275],{},[1221,1265,1266],{},[189,1267,1268],{},"blockDuration",[1221,1270,1271],{},[189,1272,1246],{},[1221,1274,1232],{},[1221,1276,1277],{},"Optional ban once the limit is exceeded",[1200,1279,1280,1285,1289,1294],{},[1221,1281,1282],{},[189,1283,1284],{},"blockBackoff",[1221,1286,1287],{},[189,1288,1229],{},[1221,1290,1291],{},[189,1292,1293],{},"1",[1221,1295,1296,1297,1300,1301],{},"Multiplier that grows the ban per repeat offence; any value ",[189,1298,1299],{},"> 1"," requires ",[189,1302,1303],{},"maxBlockDuration",[1200,1305,1306,1310,1314,1318],{},[1221,1307,1308],{},[189,1309,1303],{},[1221,1311,1312],{},[189,1313,1246],{},[1221,1315,1316],{},[189,1317,1268],{},[1221,1319,1320],{},"Ceiling for the escalating ban",[1200,1322,1323,1328,1332,1336],{},[1221,1324,1325],{},[189,1326,1327],{},"strikeReset",[1221,1329,1330],{},[189,1331,1246],{},[1221,1333,1334],{},[189,1335,1303],{},[1221,1337,1338],{},"Clean-behaviour grace (measured from the end of the block) before strikes reset",[1177,1340,1342],{"id":1341},"middleware-options","Middleware options",[186,1344,1345,1347],{},[189,1346,207],{}," extends the policy with:",[1194,1349,1350,1362],{},[1197,1351,1352],{},[1200,1353,1354,1356,1358,1360],{},[1203,1355,1205],{},[1203,1357,1208],{},[1203,1359,1211],{},[1203,1361,1214],{},[1216,1363,1364,1382,1401,1420,1437,1456],{},[1200,1365,1366,1370,1374,1379],{},[1221,1367,1368],{},[189,1369,974],{},[1221,1371,1372],{},[189,1373,215],{},[1221,1375,1376],{},[189,1377,1378],{},"new MemoryStore()",[1221,1380,1381],{},"Counter storage backend",[1200,1383,1384,1388,1393,1398],{},[1221,1385,1386],{},[189,1387,977],{},[1221,1389,1390],{},[189,1391,1392],{},"(ctx) => string",[1221,1394,1395],{},[189,1396,1397],{},"ctx.ip ?? 'unknown'",[1221,1399,1400],{},"Builds the client key",[1200,1402,1403,1407,1412,1417],{},[1221,1404,1405],{},[189,1406,980],{},[1221,1408,1409],{},[189,1410,1411],{},"'draft-6' | 'legacy' | false",[1221,1413,1414],{},[189,1415,1416],{},"'draft-6'",[1221,1418,1419],{},"Response header format",[1200,1421,1422,1427,1432,1434],{},[1221,1423,1424],{},[189,1425,1426],{},"skip",[1221,1428,1429],{},[189,1430,1431],{},"(ctx) => boolean",[1221,1433,1232],{},[1221,1435,1436],{},"Bypass limiting for a request",[1200,1438,1439,1443,1448,1453],{},[1221,1440,1441],{},[189,1442,633],{},[1221,1444,1445],{},[189,1446,1447],{},"string",[1221,1449,1450],{},[189,1451,1452],{},"'Too Many Requests'",[1221,1454,1455],{},"429 message",[1200,1457,1458,1463,1467,1470],{},[1221,1459,1460],{},[189,1461,1462],{},"prefix",[1221,1464,1465],{},[189,1466,1447],{},[1221,1468,1469],{},"unique per instance",[1221,1471,1472],{},"Key namespace; set explicitly to share a bucket",[1177,1474,1476],{"id":1475},"module-options","Module options",[186,1478,1479,1481,1482,1484,1485,1487,1488,1490,1491,1494],{},[189,1480,987],{}," takes the same options minus ",[189,1483,1426],{},". They become the defaults for the global guard and for every ",[189,1486,199],{}," policy; the configured ",[189,1489,974],{}," (or a ",[189,1492,1493],{},"MemoryStore"," created once) is shared by all guards.",[222,1496,1498],{"id":1497},"key-generation","Key generation",[186,1500,1501,1502,999,1504,1507],{},"The default key is ",[189,1503,1397],{},[189,1505,1506],{},"ctx.ip"," is the socket address unless you tell the app which proxy headers to trust:",[229,1509,1511],{"className":311,"code":1510,"language":313,"meta":235,"style":235},"new Miia({ trustProxy: true })                              \u002F\u002F leftmost X-Forwarded-For entry\nnew Miia({ trustProxy: 'cf-connecting-ip' })                \u002F\u002F a single trusted header (Cloudflare)\nnew Miia({ trustProxy: ['cf-connecting-ip', 'x-real-ip'] }) \u002F\u002F first present, in priority order\n",[189,1512,1513,1539,1568],{"__ignoreMap":235},[239,1514,1515,1518,1520,1522,1524,1527,1529,1531,1533,1536],{"class":241,"line":242},[239,1516,1517],{"class":324},"new",[239,1519,329],{"class":390},[239,1521,394],{"class":328},[239,1523,427],{"class":324},[239,1525,1526],{"class":403}," trustProxy",[239,1528,407],{"class":324},[239,1530,956],{"class":955},[239,1532,338],{"class":324},[239,1534,1535],{"class":328},")                              ",[239,1537,1538],{"class":812},"\u002F\u002F leftmost X-Forwarded-For entry\n",[239,1540,1541,1543,1545,1547,1549,1551,1553,1555,1558,1560,1562,1565],{"class":241,"line":352},[239,1542,1517],{"class":324},[239,1544,329],{"class":390},[239,1546,394],{"class":328},[239,1548,427],{"class":324},[239,1550,1526],{"class":403},[239,1552,407],{"class":324},[239,1554,344],{"class":324},[239,1556,1557],{"class":248},"cf-connecting-ip",[239,1559,451],{"class":324},[239,1561,338],{"class":324},[239,1563,1564],{"class":328},")                ",[239,1566,1567],{"class":812},"\u002F\u002F a single trusted header (Cloudflare)\n",[239,1569,1570,1572,1574,1576,1578,1580,1582,1585,1587,1589,1591,1593,1595,1598,1600,1603,1605,1608],{"class":241,"line":377},[239,1571,1517],{"class":324},[239,1573,329],{"class":390},[239,1575,394],{"class":328},[239,1577,427],{"class":324},[239,1579,1526],{"class":403},[239,1581,407],{"class":324},[239,1583,1584],{"class":328}," [",[239,1586,451],{"class":324},[239,1588,1557],{"class":248},[239,1590,451],{"class":324},[239,1592,332],{"class":324},[239,1594,344],{"class":324},[239,1596,1597],{"class":248},"x-real-ip",[239,1599,451],{"class":324},[239,1601,1602],{"class":328},"] ",[239,1604,486],{"class":324},[239,1606,1607],{"class":328},") ",[239,1609,1610],{"class":812},"\u002F\u002F first present, in priority order\n",[1612,1613,1614],"caution",{},[186,1615,1616,1619,1620,1623,1624,1627],{},[189,1617,1618],{},"trustProxy: true"," takes the leftmost ",[189,1621,1622],{},"X-Forwarded-For"," entry, which the client controls when your proxy appends to the header instead of overwriting it - an attacker can rotate fake IPs to bypass limits. Behind Cloudflare, Fly.io, or nginx with ",[189,1625,1626],{},"X-Real-IP",", prefer the vendor header form.",[186,1629,1630,1631,1633],{},"Any other keying strategy is a custom ",[189,1632,977],{}," - for example, per-user limits after authentication:",[229,1635,1637],{"className":311,"code":1636,"language":313,"meta":235,"style":235},"RateLimitModule.configure({\n  limit: 100,\n  window: '1m',\n  keyGenerator: (ctx) => ctx.user?.id ?? ctx.ip ?? 'unknown',\n})\n",[189,1638,1639,1652,1663,1678,1730],{"__ignoreMap":235},[239,1640,1641,1644,1646,1648,1650],{"class":241,"line":242},[239,1642,1643],{"class":328},"RateLimitModule",[239,1645,419],{"class":324},[239,1647,422],{"class":390},[239,1649,394],{"class":328},[239,1651,397],{"class":324},[239,1653,1654,1657,1659,1661],{"class":241,"line":352},[239,1655,1656],{"class":403},"  limit",[239,1658,407],{"class":324},[239,1660,436],{"class":435},[239,1662,459],{"class":324},[239,1664,1665,1668,1670,1672,1674,1676],{"class":241,"line":377},[239,1666,1667],{"class":403},"  window",[239,1669,407],{"class":324},[239,1671,344],{"class":324},[239,1673,448],{"class":248},[239,1675,451],{"class":324},[239,1677,459],{"class":324},[239,1679,1680,1683,1685,1688,1690,1692,1695,1698,1700,1703,1706,1709,1712,1714,1716,1719,1721,1723,1726,1728],{"class":241,"line":384},[239,1681,1682],{"class":390},"  keyGenerator",[239,1684,407],{"class":324},[239,1686,1687],{"class":324}," (",[239,1689,874],{"class":873},[239,1691,456],{"class":324},[239,1693,1694],{"class":495}," =>",[239,1696,1697],{"class":328}," ctx",[239,1699,419],{"class":324},[239,1701,1702],{"class":328},"user",[239,1704,1705],{"class":324},"?.",[239,1707,1708],{"class":328},"id ",[239,1710,1711],{"class":324},"??",[239,1713,1697],{"class":328},[239,1715,419],{"class":324},[239,1717,1718],{"class":328},"ip ",[239,1720,1711],{"class":324},[239,1722,344],{"class":324},[239,1724,1725],{"class":248},"unknown",[239,1727,451],{"class":324},[239,1729,459],{"class":324},[239,1731,1732,1734],{"class":241,"line":400},[239,1733,486],{"class":324},[239,1735,489],{"class":328},[222,1737,1739],{"id":1738},"response-headers","Response headers",[186,1741,1742,1743,1745],{},"With the default ",[189,1744,1416],{}," mode every response carries:",[1194,1747,1748,1758],{},[1197,1749,1750],{},[1200,1751,1752,1755],{},[1203,1753,1754],{},"Header",[1203,1756,1757],{},"Value",[1216,1759,1760,1770,1780,1790],{},[1200,1761,1762,1767],{},[1221,1763,1764],{},[189,1765,1766],{},"RateLimit-Limit",[1221,1768,1769],{},"Policy limit",[1200,1771,1772,1777],{},[1221,1773,1774],{},[189,1775,1776],{},"RateLimit-Remaining",[1221,1778,1779],{},"Requests left in the current window",[1200,1781,1782,1787],{},[1221,1783,1784],{},[189,1785,1786],{},"RateLimit-Reset",[1221,1788,1789],{},"Seconds until the window resets (delta)",[1200,1791,1792,1797],{},[1221,1793,1794],{},[189,1795,1796],{},"RateLimit-Policy",[1221,1798,1799,1802],{},[189,1800,1801],{},"100;w=60"," (limit and window seconds)",[186,1804,1805,1808,1809,1812,1813,1816,1817,419],{},[189,1806,1807],{},"'legacy'"," emits the same values as ",[189,1810,1811],{},"X-RateLimit-*",". A blocked request always gets ",[189,1814,1815],{},"Retry-After"," in seconds - even with ",[189,1818,1819],{},"headers: false",[1017,1821,1822],{},[186,1823,1824,1825,1827],{},"Setting any header opts the response out of the inline-JSON fast path and the adapters' response caching. ",[189,1826,1819],{}," restores the fast path if you measure the difference and the headers are not needed.",[222,1829,1831],{"id":1830},"blocking-repeat-offenders","Blocking repeat offenders",[186,1833,1834,1836,1837,1839],{},[189,1835,1268],{}," turns the limit into a ban: once a client exceeds the limit, the key is blocked for the given duration and requests are rejected without counting. The request that triggers the block already gets ",[189,1838,1815],{}," equal to the full block duration, and once the block expires the client starts with a fresh window.",[229,1841,1843],{"className":311,"code":1842,"language":313,"meta":235,"style":235},"\u002F\u002F 5 attempts per minute; exceeding locks the key for 15 minutes\n@RateLimit({ limit: 5, window: '1m', blockDuration: '15m' })\n@Post('login')\nlogin(ctx: RequestContext) {\n  \u002F* ... *\u002F\n}\n",[189,1844,1845,1850,1897,1915,1924,1929],{"__ignoreMap":235},[239,1846,1847],{"class":241,"line":242},[239,1848,1849],{"class":812},"\u002F\u002F 5 attempts per minute; exceeding locks the key for 15 minutes\n",[239,1851,1852,1854,1856,1858,1860,1862,1864,1867,1869,1871,1873,1875,1877,1879,1881,1884,1886,1888,1891,1893,1895],{"class":241,"line":352},[239,1853,387],{"class":324},[239,1855,826],{"class":390},[239,1857,394],{"class":328},[239,1859,427],{"class":324},[239,1861,430],{"class":403},[239,1863,407],{"class":324},[239,1865,1866],{"class":435}," 5",[239,1868,332],{"class":324},[239,1870,441],{"class":403},[239,1872,407],{"class":324},[239,1874,344],{"class":324},[239,1876,448],{"class":248},[239,1878,451],{"class":324},[239,1880,332],{"class":324},[239,1882,1883],{"class":403}," blockDuration",[239,1885,407],{"class":324},[239,1887,344],{"class":324},[239,1889,1890],{"class":248},"15m",[239,1892,451],{"class":324},[239,1894,338],{"class":324},[239,1896,489],{"class":328},[239,1898,1899,1901,1904,1906,1908,1911,1913],{"class":241,"line":377},[239,1900,387],{"class":324},[239,1902,1903],{"class":390},"Post",[239,1905,394],{"class":328},[239,1907,451],{"class":324},[239,1909,1910],{"class":248},"login",[239,1912,451],{"class":324},[239,1914,489],{"class":328},[239,1916,1917,1919,1922],{"class":241,"line":384},[239,1918,1910],{"class":390},[239,1920,1921],{"class":328},"(ctx: RequestContext) ",[239,1923,397],{"class":324},[239,1925,1926],{"class":241,"line":400},[239,1927,1928],{"class":812},"  \u002F* ... *\u002F\n",[239,1930,1931],{"class":241,"line":413},[239,1932,678],{"class":324},[1177,1934,1936],{"id":1935},"geometric-backoff","Geometric backoff",[186,1938,1939,1940,1942,1943,1945,1946,1949,1950,1953,1954,1956,1957,1959,1960,1962,1963,1300,1965,1967],{},"A fixed ban treats the first offence the same as the hundredth. ",[189,1941,1284],{}," makes the ban grow geometrically per repeat offence: the first block is ",[189,1944,1268],{},", the next ",[189,1947,1948],{},"blockDuration × blockBackoff",", then ",[189,1951,1952],{},"blockDuration × blockBackoff²",", and so on, clamped at ",[189,1955,1303],{},". This is opt-in - the default ",[189,1958,1284],{}," of ",[189,1961,1293],{}," keeps the fixed-ban behaviour, and any value above ",[189,1964,1293],{},[189,1966,1303],{}," so escalation is always bounded.",[229,1969,1971],{"className":311,"code":1970,"language":313,"meta":235,"style":235},"\u002F\u002F 1s ban, doubling each repeat offence, capped at 1 hour:\n\u002F\u002F 1s -> 2s -> 4s -> 8s -> ... -> 1h -> 1h\n@RateLimit({\n  limit: 5,\n  window: '1m',\n  blockDuration: '1s',\n  blockBackoff: 2,\n  maxBlockDuration: '1h',\n})\n@Post('login')\nlogin(ctx: RequestContext) {\n  \u002F* ... *\u002F\n}\n",[189,1972,1973,1978,1983,1993,2003,2017,2033,2045,2061,2067,2083,2091,2095],{"__ignoreMap":235},[239,1974,1975],{"class":241,"line":242},[239,1976,1977],{"class":812},"\u002F\u002F 1s ban, doubling each repeat offence, capped at 1 hour:\n",[239,1979,1980],{"class":241,"line":352},[239,1981,1982],{"class":812},"\u002F\u002F 1s -> 2s -> 4s -> 8s -> ... -> 1h -> 1h\n",[239,1984,1985,1987,1989,1991],{"class":241,"line":377},[239,1986,387],{"class":324},[239,1988,826],{"class":390},[239,1990,394],{"class":328},[239,1992,397],{"class":324},[239,1994,1995,1997,1999,2001],{"class":241,"line":384},[239,1996,1656],{"class":403},[239,1998,407],{"class":324},[239,2000,1866],{"class":435},[239,2002,459],{"class":324},[239,2004,2005,2007,2009,2011,2013,2015],{"class":241,"line":400},[239,2006,1667],{"class":403},[239,2008,407],{"class":324},[239,2010,344],{"class":324},[239,2012,448],{"class":248},[239,2014,451],{"class":324},[239,2016,459],{"class":324},[239,2018,2019,2022,2024,2026,2029,2031],{"class":241,"line":413},[239,2020,2021],{"class":403},"  blockDuration",[239,2023,407],{"class":324},[239,2025,344],{"class":324},[239,2027,2028],{"class":248},"1s",[239,2030,451],{"class":324},[239,2032,459],{"class":324},[239,2034,2035,2038,2040,2043],{"class":241,"line":462},[239,2036,2037],{"class":403},"  blockBackoff",[239,2039,407],{"class":324},[239,2041,2042],{"class":435}," 2",[239,2044,459],{"class":324},[239,2046,2047,2050,2052,2054,2057,2059],{"class":241,"line":470},[239,2048,2049],{"class":403},"  maxBlockDuration",[239,2051,407],{"class":324},[239,2053,344],{"class":324},[239,2055,2056],{"class":248},"1h",[239,2058,451],{"class":324},[239,2060,459],{"class":324},[239,2062,2063,2065],{"class":241,"line":483},[239,2064,486],{"class":324},[239,2066,489],{"class":328},[239,2068,2069,2071,2073,2075,2077,2079,2081],{"class":241,"line":492},[239,2070,387],{"class":324},[239,2072,1903],{"class":390},[239,2074,394],{"class":328},[239,2076,451],{"class":324},[239,2078,1910],{"class":248},[239,2080,451],{"class":324},[239,2082,489],{"class":328},[239,2084,2085,2087,2089],{"class":241,"line":505},[239,2086,1910],{"class":390},[239,2088,1921],{"class":328},[239,2090,397],{"class":324},[239,2092,2093],{"class":241,"line":510},[239,2094,1928],{"class":812},[239,2096,2097],{"class":241,"line":538},[239,2098,678],{"class":324},[186,2100,2101,2102,1687,2105,2108,2109,2111,2112,2114,2115,2117],{},"Escalation is tracked by an accumulated strike count. Strikes reset to zero in full after a grace period of clean behaviour, measured from the ",[996,2103,2104],{},"end of the block",[189,2106,2107],{},"strikesExpireAt = blockExpiresAt + strikeReset","). ",[189,2110,1327],{}," defaults to ",[189,2113,1303],{}," - so once a client has served the longest possible ban and then stays quiet for that same span, the next offence starts again at the base ",[189,2116,1268],{},". There is no gradual decay: it is full memory until the grace elapses, then a clean slate.",[1017,2119,2120],{},[186,2121,2122,2123,2125,2126,2129,2130,2132,2133,688,2135,2137],{},"Backoff state lives in the store. ",[189,2124,1493],{}," keeps strikes per process; a shared store (Redis) carries the decision atomically in its ",[189,2127,2128],{},"increment"," script. With ",[189,2131,1284],{}," unset or ",[189,2134,1293],{},[189,2136,1493],{}," behaves exactly as before - it drops the key the moment the block expires, with no strike memory.",[222,2139,2141],{"id":2140},"bucket-scope","Bucket scope",[2143,2144,2145,2151,2156],"ul",{},[2146,2147,2148,2150],"li",{},[189,2149,199],{}," on a method - one counter per route.",[2146,2152,2153,2155],{},[189,2154,199],{}," on a class - one counter shared by all routes of that controller.",[2146,2157,2158],{},"The global guard - one counter shared by all matched routes.",[186,2160,2161,2162,407],{},"Replacement means exactly one guard policy is active per route, so buckets of different levels never interact. To share one bucket between routes or controllers deliberately, give their policies the same explicit ",[189,2163,1462],{},[229,2165,2167],{"className":311,"code":2166,"language":313,"meta":235,"style":235},"\u002F\u002F login and password reset drain the same 5\u002Fmin budget\n@RateLimit({ limit: 5, window: '1m', prefix: 'auth-attempts' })\n",[189,2168,2169,2174],{"__ignoreMap":235},[239,2170,2171],{"class":241,"line":242},[239,2172,2173],{"class":812},"\u002F\u002F login and password reset drain the same 5\u002Fmin budget\n",[239,2175,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2198,2200,2202,2204,2207,2209,2211,2214,2216,2218],{"class":241,"line":352},[239,2177,387],{"class":324},[239,2179,826],{"class":390},[239,2181,394],{"class":328},[239,2183,427],{"class":324},[239,2185,430],{"class":403},[239,2187,407],{"class":324},[239,2189,1866],{"class":435},[239,2191,332],{"class":324},[239,2193,441],{"class":403},[239,2195,407],{"class":324},[239,2197,344],{"class":324},[239,2199,448],{"class":248},[239,2201,451],{"class":324},[239,2203,332],{"class":324},[239,2205,2206],{"class":403}," prefix",[239,2208,407],{"class":324},[239,2210,344],{"class":324},[239,2212,2213],{"class":248},"auth-attempts",[239,2215,451],{"class":324},[239,2217,338],{"class":324},[239,2219,489],{"class":328},[222,2221,2223],{"id":2222},"custom-stores","Custom stores",[186,2225,2226,2227,2229],{},"Storage implements two methods. ",[189,2228,2128],{}," both counts the hit and decides blocking atomically - the contract is shaped for a Redis Lua script where the whole decision is a single round trip:",[229,2231,2233],{"className":311,"code":2232,"language":313,"meta":235,"style":235},"import type { IncrementOptions, RateLimitStore, StoreRecord } from '@miiajs\u002Frate-limit'\n\nclass MyStore implements RateLimitStore {\n  increment(key: string, opts: IncrementOptions): Promise\u003CStoreRecord> {\n    \u002F\u002F opts: { windowMs, limit, blockDurationMs, blockBackoff, maxBlockDurationMs, strikeResetMs }\n    \u002F\u002F return { totalHits, timeToExpireMs, isBlocked, timeToBlockExpireMs, strikes }\n  }\n\n  reset(key: string): Promise\u003Cvoid> {\n    \u002F\u002F drop the key\n  }\n}\n\nRateLimitModule.configure({ limit: 100, window: '1m', store: new MyStore() })\n",[189,2234,2235,2266,2270,2284,2325,2330,2335,2339,2343,2369,2374,2378,2382,2386],{"__ignoreMap":235},[239,2236,2237,2239,2241,2243,2246,2248,2251,2253,2256,2258,2260,2262,2264],{"class":241,"line":242},[239,2238,321],{"class":320},[239,2240,738],{"class":320},[239,2242,325],{"class":324},[239,2244,2245],{"class":328}," IncrementOptions",[239,2247,332],{"class":324},[239,2249,2250],{"class":328}," RateLimitStore",[239,2252,332],{"class":324},[239,2254,2255],{"class":328}," StoreRecord",[239,2257,338],{"class":324},[239,2259,341],{"class":320},[239,2261,344],{"class":324},[239,2263,191],{"class":248},[239,2265,349],{"class":324},[239,2267,2268],{"class":241,"line":352},[239,2269,381],{"emptyLinePlaceholder":380},[239,2271,2272,2274,2277,2280,2282],{"class":241,"line":377},[239,2273,496],{"class":495},[239,2275,2276],{"class":245}," MyStore",[239,2278,2279],{"class":495}," implements",[239,2281,2250],{"class":245},[239,2283,807],{"class":324},[239,2285,2286,2289,2291,2294,2296,2299,2301,2304,2306,2308,2311,2314,2317,2320,2323],{"class":241,"line":384},[239,2287,2288],{"class":403},"  increment",[239,2290,394],{"class":324},[239,2292,2293],{"class":873},"key",[239,2295,407],{"class":324},[239,2297,2298],{"class":245}," string",[239,2300,332],{"class":324},[239,2302,2303],{"class":873}," opts",[239,2305,407],{"class":324},[239,2307,2245],{"class":245},[239,2309,2310],{"class":324},"):",[239,2312,2313],{"class":245}," Promise",[239,2315,2316],{"class":324},"\u003C",[239,2318,2319],{"class":245},"StoreRecord",[239,2321,2322],{"class":324},">",[239,2324,807],{"class":324},[239,2326,2327],{"class":241,"line":400},[239,2328,2329],{"class":812},"    \u002F\u002F opts: { windowMs, limit, blockDurationMs, blockBackoff, maxBlockDurationMs, strikeResetMs }\n",[239,2331,2332],{"class":241,"line":413},[239,2333,2334],{"class":812},"    \u002F\u002F return { totalHits, timeToExpireMs, isBlocked, timeToBlockExpireMs, strikes }\n",[239,2336,2337],{"class":241,"line":462},[239,2338,892],{"class":324},[239,2340,2341],{"class":241,"line":470},[239,2342,381],{"emptyLinePlaceholder":380},[239,2344,2345,2348,2350,2352,2354,2356,2358,2360,2362,2365,2367],{"class":241,"line":483},[239,2346,2347],{"class":403},"  reset",[239,2349,394],{"class":324},[239,2351,2293],{"class":873},[239,2353,407],{"class":324},[239,2355,2298],{"class":245},[239,2357,2310],{"class":324},[239,2359,2313],{"class":245},[239,2361,2316],{"class":324},[239,2363,2364],{"class":245},"void",[239,2366,2322],{"class":324},[239,2368,807],{"class":324},[239,2370,2371],{"class":241,"line":492},[239,2372,2373],{"class":812},"    \u002F\u002F drop the key\n",[239,2375,2376],{"class":241,"line":505},[239,2377,892],{"class":324},[239,2379,2380],{"class":241,"line":510},[239,2381,678],{"class":324},[239,2383,2384],{"class":241,"line":538},[239,2385,381],{"emptyLinePlaceholder":380},[239,2387,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2416,2418,2421,2423,2425,2427,2430,2432],{"class":241,"line":552},[239,2389,1643],{"class":328},[239,2391,419],{"class":324},[239,2393,422],{"class":390},[239,2395,394],{"class":328},[239,2397,427],{"class":324},[239,2399,430],{"class":403},[239,2401,407],{"class":324},[239,2403,436],{"class":435},[239,2405,332],{"class":324},[239,2407,441],{"class":403},[239,2409,407],{"class":324},[239,2411,344],{"class":324},[239,2413,448],{"class":248},[239,2415,451],{"class":324},[239,2417,332],{"class":324},[239,2419,2420],{"class":403}," store",[239,2422,407],{"class":324},[239,2424,522],{"class":324},[239,2426,2276],{"class":390},[239,2428,2429],{"class":328},"() ",[239,2431,486],{"class":324},[239,2433,489],{"class":328},[186,2435,2436,2437,2439,2440,2443,2444,419],{},"The bundled ",[189,2438,1493],{}," keeps counters in a ",[189,2441,2442],{},"Map"," with lazy expiry and no timers - suitable for a single process. Multi-instance deployments need a shared store; a first-party Redis store is on the ",[2445,2446,2447],"a",{"href":170},"roadmap",[222,2449,2451],{"id":2450},"standalone-ratelimiter","Standalone RateLimiter",[186,2453,2454,2455,2458],{},"The core class works outside HTTP - useful for queues, jobs, or anything with a string key. ",[189,2456,2457],{},"limit()"," returns a result object and never throws:",[229,2460,2462],{"className":311,"code":2461,"language":313,"meta":235,"style":235},"import { RateLimiter } from '@miiajs\u002Frate-limit'\n\nconst limiter = new RateLimiter({ limit: 10, window: '1s' })\n\nconst result = await limiter.limit(`webhook:${tenantId}`)\nif (!result.success) {\n  \u002F\u002F result.retryAfterMs tells how long to back off\n}\n",[189,2463,2464,2483,2487,2527,2531,2569,2589,2594],{"__ignoreMap":235},[239,2465,2466,2468,2470,2473,2475,2477,2479,2481],{"class":241,"line":242},[239,2467,321],{"class":320},[239,2469,325],{"class":324},[239,2471,2472],{"class":328}," RateLimiter",[239,2474,338],{"class":324},[239,2476,341],{"class":320},[239,2478,344],{"class":324},[239,2480,191],{"class":248},[239,2482,349],{"class":324},[239,2484,2485],{"class":241,"line":352},[239,2486,381],{"emptyLinePlaceholder":380},[239,2488,2489,2491,2494,2496,2498,2500,2502,2504,2506,2508,2511,2513,2515,2517,2519,2521,2523,2525],{"class":241,"line":377},[239,2490,513],{"class":495},[239,2492,2493],{"class":328}," limiter ",[239,2495,519],{"class":324},[239,2497,522],{"class":324},[239,2499,2472],{"class":390},[239,2501,394],{"class":328},[239,2503,427],{"class":324},[239,2505,430],{"class":403},[239,2507,407],{"class":324},[239,2509,2510],{"class":435}," 10",[239,2512,332],{"class":324},[239,2514,441],{"class":403},[239,2516,407],{"class":324},[239,2518,344],{"class":324},[239,2520,2028],{"class":248},[239,2522,451],{"class":324},[239,2524,338],{"class":324},[239,2526,489],{"class":328},[239,2528,2529],{"class":241,"line":384},[239,2530,381],{"emptyLinePlaceholder":380},[239,2532,2533,2535,2538,2540,2543,2546,2548,2550,2552,2555,2558,2561,2564,2567],{"class":241,"line":400},[239,2534,513],{"class":495},[239,2536,2537],{"class":328}," result ",[239,2539,519],{"class":324},[239,2541,2542],{"class":320}," await",[239,2544,2545],{"class":328}," limiter",[239,2547,419],{"class":324},[239,2549,1069],{"class":390},[239,2551,394],{"class":328},[239,2553,2554],{"class":324},"`",[239,2556,2557],{"class":248},"webhook:",[239,2559,2560],{"class":324},"${",[239,2562,2563],{"class":328},"tenantId",[239,2565,2566],{"class":324},"}`",[239,2568,489],{"class":328},[239,2570,2571,2574,2576,2579,2582,2584,2587],{"class":241,"line":413},[239,2572,2573],{"class":320},"if",[239,2575,1687],{"class":328},[239,2577,2578],{"class":324},"!",[239,2580,2581],{"class":328},"result",[239,2583,419],{"class":324},[239,2585,2586],{"class":328},"success) ",[239,2588,397],{"class":324},[239,2590,2591],{"class":241,"line":462},[239,2592,2593],{"class":812},"  \u002F\u002F result.retryAfterMs tells how long to back off\n",[239,2595,2596],{"class":241,"line":470},[239,2597,678],{"class":324},[222,2599,33],{"id":2600},"testing",[186,2602,2603,2606,2607,2610],{},[189,2604,2605],{},"TestApp.request()"," accepts an ",[189,2608,2609],{},"ip"," option, so per-client limits are easy to exercise:",[229,2612,2614],{"className":311,"code":2613,"language":313,"meta":235,"style":235},"import { describe, expect, it } from 'bun:test'\nimport { TestApp } from '@miiajs\u002Fcore\u002Ftesting'\n\nit('limits each client independently', async () => {\n  const app = await TestApp.create(AppModule).useGuard(RateLimitGuard).compile()\n\n  for (let i = 0; i \u003C 3; i++) {\n    await app.request('GET', '\u002Fusers', { ip: '10.0.0.1' })\n  }\n  const blocked = await app.request('GET', '\u002Fusers', { ip: '10.0.0.1' })\n  const other = await app.request('GET', '\u002Fusers', { ip: '10.0.0.2' })\n\n  expect(blocked.status).toBe(429)\n  expect(other.status).toBe(200)\n\n  await app.close()\n})\n",[189,2615,2616,2646,2666,2670,2696,2739,2743,2783,2833,2837,2888,2940,2944,2972,2998,3002,3016],{"__ignoreMap":235},[239,2617,2618,2620,2622,2625,2627,2630,2632,2635,2637,2639,2641,2644],{"class":241,"line":242},[239,2619,321],{"class":320},[239,2621,325],{"class":324},[239,2623,2624],{"class":328}," describe",[239,2626,332],{"class":324},[239,2628,2629],{"class":328}," expect",[239,2631,332],{"class":324},[239,2633,2634],{"class":328}," it",[239,2636,338],{"class":324},[239,2638,341],{"class":320},[239,2640,344],{"class":324},[239,2642,2643],{"class":248},"bun:test",[239,2645,349],{"class":324},[239,2647,2648,2650,2652,2655,2657,2659,2661,2664],{"class":241,"line":352},[239,2649,321],{"class":320},[239,2651,325],{"class":324},[239,2653,2654],{"class":328}," TestApp",[239,2656,338],{"class":324},[239,2658,341],{"class":320},[239,2660,344],{"class":324},[239,2662,2663],{"class":248},"@miiajs\u002Fcore\u002Ftesting",[239,2665,349],{"class":324},[239,2667,2668],{"class":241,"line":377},[239,2669,381],{"emptyLinePlaceholder":380},[239,2671,2672,2675,2677,2679,2682,2684,2686,2689,2692,2694],{"class":241,"line":384},[239,2673,2674],{"class":390},"it",[239,2676,394],{"class":328},[239,2678,451],{"class":324},[239,2680,2681],{"class":248},"limits each client independently",[239,2683,451],{"class":324},[239,2685,332],{"class":324},[239,2687,2688],{"class":495}," async",[239,2690,2691],{"class":324}," ()",[239,2693,1694],{"class":495},[239,2695,807],{"class":324},[239,2697,2698,2701,2703,2706,2708,2710,2712,2715,2717,2720,2722,2724,2726,2728,2730,2732,2734,2737],{"class":241,"line":400},[239,2699,2700],{"class":495},"  const",[239,2702,558],{"class":328},[239,2704,2705],{"class":324}," =",[239,2707,2542],{"class":320},[239,2709,2654],{"class":328},[239,2711,419],{"class":324},[239,2713,2714],{"class":390},"create",[239,2716,394],{"class":403},[239,2718,2719],{"class":328},"AppModule",[239,2721,456],{"class":403},[239,2723,419],{"class":324},[239,2725,546],{"class":390},[239,2727,394],{"class":403},[239,2729,195],{"class":328},[239,2731,456],{"class":403},[239,2733,419],{"class":324},[239,2735,2736],{"class":390},"compile",[239,2738,863],{"class":403},[239,2740,2741],{"class":241,"line":413},[239,2742,381],{"emptyLinePlaceholder":380},[239,2744,2745,2748,2750,2753,2756,2758,2761,2764,2766,2769,2772,2774,2776,2779,2781],{"class":241,"line":462},[239,2746,2747],{"class":320},"  for",[239,2749,1687],{"class":403},[239,2751,2752],{"class":495},"let",[239,2754,2755],{"class":328}," i",[239,2757,2705],{"class":324},[239,2759,2760],{"class":435}," 0",[239,2762,2763],{"class":324},";",[239,2765,2755],{"class":328},[239,2767,2768],{"class":324}," \u003C",[239,2770,2771],{"class":435}," 3",[239,2773,2763],{"class":324},[239,2775,2755],{"class":328},[239,2777,2778],{"class":324},"++",[239,2780,1607],{"class":403},[239,2782,397],{"class":324},[239,2784,2785,2788,2790,2792,2795,2797,2799,2802,2804,2806,2808,2811,2813,2815,2817,2820,2822,2824,2827,2829,2831],{"class":241,"line":470},[239,2786,2787],{"class":320},"    await",[239,2789,558],{"class":328},[239,2791,419],{"class":324},[239,2793,2794],{"class":390},"request",[239,2796,394],{"class":403},[239,2798,451],{"class":324},[239,2800,2801],{"class":248},"GET",[239,2803,451],{"class":324},[239,2805,332],{"class":324},[239,2807,344],{"class":324},[239,2809,2810],{"class":248},"\u002Fusers",[239,2812,451],{"class":324},[239,2814,332],{"class":324},[239,2816,325],{"class":324},[239,2818,2819],{"class":403}," ip",[239,2821,407],{"class":324},[239,2823,344],{"class":324},[239,2825,2826],{"class":248},"10.0.0.1",[239,2828,451],{"class":324},[239,2830,338],{"class":324},[239,2832,489],{"class":403},[239,2834,2835],{"class":241,"line":483},[239,2836,892],{"class":324},[239,2838,2839,2841,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876,2878,2880,2882,2884,2886],{"class":241,"line":492},[239,2840,2700],{"class":495},[239,2842,2843],{"class":328}," blocked",[239,2845,2705],{"class":324},[239,2847,2542],{"class":320},[239,2849,558],{"class":328},[239,2851,419],{"class":324},[239,2853,2794],{"class":390},[239,2855,394],{"class":403},[239,2857,451],{"class":324},[239,2859,2801],{"class":248},[239,2861,451],{"class":324},[239,2863,332],{"class":324},[239,2865,344],{"class":324},[239,2867,2810],{"class":248},[239,2869,451],{"class":324},[239,2871,332],{"class":324},[239,2873,325],{"class":324},[239,2875,2819],{"class":403},[239,2877,407],{"class":324},[239,2879,344],{"class":324},[239,2881,2826],{"class":248},[239,2883,451],{"class":324},[239,2885,338],{"class":324},[239,2887,489],{"class":403},[239,2889,2890,2892,2895,2897,2899,2901,2903,2905,2907,2909,2911,2913,2915,2917,2919,2921,2923,2925,2927,2929,2931,2934,2936,2938],{"class":241,"line":505},[239,2891,2700],{"class":495},[239,2893,2894],{"class":328}," other",[239,2896,2705],{"class":324},[239,2898,2542],{"class":320},[239,2900,558],{"class":328},[239,2902,419],{"class":324},[239,2904,2794],{"class":390},[239,2906,394],{"class":403},[239,2908,451],{"class":324},[239,2910,2801],{"class":248},[239,2912,451],{"class":324},[239,2914,332],{"class":324},[239,2916,344],{"class":324},[239,2918,2810],{"class":248},[239,2920,451],{"class":324},[239,2922,332],{"class":324},[239,2924,325],{"class":324},[239,2926,2819],{"class":403},[239,2928,407],{"class":324},[239,2930,344],{"class":324},[239,2932,2933],{"class":248},"10.0.0.2",[239,2935,451],{"class":324},[239,2937,338],{"class":324},[239,2939,489],{"class":403},[239,2941,2942],{"class":241,"line":510},[239,2943,381],{"emptyLinePlaceholder":380},[239,2945,2946,2949,2951,2954,2956,2959,2961,2963,2966,2968,2970],{"class":241,"line":538},[239,2947,2948],{"class":390},"  expect",[239,2950,394],{"class":403},[239,2952,2953],{"class":328},"blocked",[239,2955,419],{"class":324},[239,2957,2958],{"class":328},"status",[239,2960,456],{"class":403},[239,2962,419],{"class":324},[239,2964,2965],{"class":390},"toBe",[239,2967,394],{"class":403},[239,2969,576],{"class":435},[239,2971,489],{"class":403},[239,2973,2974,2976,2978,2981,2983,2985,2987,2989,2991,2993,2996],{"class":241,"line":552},[239,2975,2948],{"class":390},[239,2977,394],{"class":403},[239,2979,2980],{"class":328},"other",[239,2982,419],{"class":324},[239,2984,2958],{"class":328},[239,2986,456],{"class":403},[239,2988,419],{"class":324},[239,2990,2965],{"class":390},[239,2992,394],{"class":403},[239,2994,2995],{"class":435},"200",[239,2997,489],{"class":403},[239,2999,3000],{"class":241,"line":904},[239,3001,381],{"emptyLinePlaceholder":380},[239,3003,3004,3007,3009,3011,3014],{"class":241,"line":914},[239,3005,3006],{"class":320},"  await",[239,3008,558],{"class":328},[239,3010,419],{"class":324},[239,3012,3013],{"class":390},"close",[239,3015,863],{"class":403},[239,3017,3018,3020],{"class":241,"line":932},[239,3019,486],{"class":324},[239,3021,489],{"class":328},[222,3023,3025],{"id":3024},"exports","Exports",[229,3027,3029],{"className":311,"code":3028,"language":313,"meta":235,"style":235},"import {\n  MemoryStore,\n  RATE_LIMIT_OPTIONS,\n  RateLimit,\n  RateLimitGuard,\n  RateLimitModule,\n  RateLimiter,\n  SkipRateLimit,\n  parseWindow,\n  rateLimit,\n  setRateLimitHeaders,\n} from '@miiajs\u002Frate-limit'\n\nimport type {\n  HeadersMode,\n  KeyGenerator,\n  RateLimitModuleOptions,\n  RateLimitOptions,\n  RateLimitPolicy,\n  RateLimitResult,\n  RateLimitStore,\n  RateLimiterOptions,\n  StoreRecord,\n} from '@miiajs\u002Frate-limit'\n",[189,3030,3031,3037,3044,3051,3058,3065,3072,3079,3086,3093,3100,3107,3119,3123,3131,3138,3145,3152,3159,3166,3173,3181,3189,3197],{"__ignoreMap":235},[239,3032,3033,3035],{"class":241,"line":242},[239,3034,321],{"class":320},[239,3036,807],{"class":324},[239,3038,3039,3042],{"class":241,"line":352},[239,3040,3041],{"class":328},"  MemoryStore",[239,3043,459],{"class":324},[239,3045,3046,3049],{"class":241,"line":377},[239,3047,3048],{"class":328},"  RATE_LIMIT_OPTIONS",[239,3050,459],{"class":324},[239,3052,3053,3056],{"class":241,"line":384},[239,3054,3055],{"class":328},"  RateLimit",[239,3057,459],{"class":324},[239,3059,3060,3063],{"class":241,"line":400},[239,3061,3062],{"class":328},"  RateLimitGuard",[239,3064,459],{"class":324},[239,3066,3067,3070],{"class":241,"line":413},[239,3068,3069],{"class":328},"  RateLimitModule",[239,3071,459],{"class":324},[239,3073,3074,3077],{"class":241,"line":462},[239,3075,3076],{"class":328},"  RateLimiter",[239,3078,459],{"class":324},[239,3080,3081,3084],{"class":241,"line":470},[239,3082,3083],{"class":328},"  SkipRateLimit",[239,3085,459],{"class":324},[239,3087,3088,3091],{"class":241,"line":483},[239,3089,3090],{"class":328},"  parseWindow",[239,3092,459],{"class":324},[239,3094,3095,3098],{"class":241,"line":492},[239,3096,3097],{"class":328},"  rateLimit",[239,3099,459],{"class":324},[239,3101,3102,3105],{"class":241,"line":505},[239,3103,3104],{"class":328},"  setRateLimitHeaders",[239,3106,459],{"class":324},[239,3108,3109,3111,3113,3115,3117],{"class":241,"line":510},[239,3110,486],{"class":324},[239,3112,341],{"class":320},[239,3114,344],{"class":324},[239,3116,191],{"class":248},[239,3118,349],{"class":324},[239,3120,3121],{"class":241,"line":538},[239,3122,381],{"emptyLinePlaceholder":380},[239,3124,3125,3127,3129],{"class":241,"line":552},[239,3126,321],{"class":320},[239,3128,738],{"class":320},[239,3130,807],{"class":324},[239,3132,3133,3136],{"class":241,"line":904},[239,3134,3135],{"class":328},"  HeadersMode",[239,3137,459],{"class":324},[239,3139,3140,3143],{"class":241,"line":914},[239,3141,3142],{"class":328},"  KeyGenerator",[239,3144,459],{"class":324},[239,3146,3147,3150],{"class":241,"line":932},[239,3148,3149],{"class":328},"  RateLimitModuleOptions",[239,3151,459],{"class":324},[239,3153,3154,3157],{"class":241,"line":942},[239,3155,3156],{"class":328},"  RateLimitOptions",[239,3158,459],{"class":324},[239,3160,3161,3164],{"class":241,"line":961},[239,3162,3163],{"class":328},"  RateLimitPolicy",[239,3165,459],{"class":324},[239,3167,3168,3171],{"class":241,"line":966},[239,3169,3170],{"class":328},"  RateLimitResult",[239,3172,459],{"class":324},[239,3174,3176,3179],{"class":241,"line":3175},21,[239,3177,3178],{"class":328},"  RateLimitStore",[239,3180,459],{"class":324},[239,3182,3184,3187],{"class":241,"line":3183},22,[239,3185,3186],{"class":328},"  RateLimiterOptions",[239,3188,459],{"class":324},[239,3190,3192,3195],{"class":241,"line":3191},23,[239,3193,3194],{"class":328},"  StoreRecord",[239,3196,459],{"class":324},[239,3198,3200,3202,3204,3206,3208],{"class":241,"line":3199},24,[239,3201,486],{"class":324},[239,3203,341],{"class":320},[239,3205,344],{"class":324},[239,3207,191],{"class":248},[239,3209,349],{"class":324},[222,3211,3213],{"id":3212},"see-also","See also",[2143,3215,3216,3224,3236,3245],{},[2146,3217,3218,3220,3221,3223],{},[2445,3219,75],{"href":76}," - the onion model the ",[189,3222,207],{}," middleware plugs into",[2146,3225,3226,3228,3229,3231,3232,3235],{},[2445,3227,79],{"href":80}," - how ",[189,3230,195],{}," and ",[189,3233,3234],{},"@SkipGuard"," work",[2146,3237,3238,3240,3241,3244],{},[2445,3239,83],{"href":84}," - the ",[189,3242,3243],{},"TooManyRequestsException"," (429) shape",[2146,3246,3247,3249,3250,3253,3254,3256],{},[2445,3248,33],{"href":34}," - ",[189,3251,3252],{},"TestApp"," and the ",[189,3255,2609],{}," request option",[3258,3259,3260],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":235,"searchDepth":352,"depth":352,"links":3262},[3263,3264,3265,3266,3267,3272,3273,3274,3277,3278,3279,3280,3281,3282],{"id":224,"depth":352,"text":13},{"id":304,"depth":352,"text":305},{"id":703,"depth":352,"text":704},{"id":1076,"depth":352,"text":1077},{"id":1174,"depth":352,"text":1175,"children":3268},[3269,3270,3271],{"id":1179,"depth":377,"text":1180},{"id":1341,"depth":377,"text":1342},{"id":1475,"depth":377,"text":1476},{"id":1497,"depth":352,"text":1498},{"id":1738,"depth":352,"text":1739},{"id":1830,"depth":352,"text":1831,"children":3275},[3276],{"id":1935,"depth":377,"text":1936},{"id":2140,"depth":352,"text":2141},{"id":2222,"depth":352,"text":2223},{"id":2450,"depth":352,"text":2451},{"id":2600,"depth":352,"text":33},{"id":3024,"depth":352,"text":3025},{"id":3212,"depth":352,"text":3213},"Fixed-window rate limiting with guard, decorators, and perimeter middleware - pluggable stores, standard RateLimit headers.","md",{},{"title":113,"description":3283},"V1NsF_Hb4eL5MZZCzcUwQSE31ut-EfvDCbc1sCg6OPM",[3289,3291],{"title":109,"path":110,"stem":111,"description":3290,"children":-1},"Static file server with Range, conditional GET, charset, dotfile guard, and SPA fallback.",{"title":27,"path":118,"stem":119,"description":3292,"children":-1},"Lightweight auth primitives - AuthProvider interface, AuthGuard factory, HTTP token extractors, timing-safe utilities.",1782051696309]