@lunora/ratelimit 1.0.0-alpha.80 → 1.0.0-alpha.82
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/README.md +2 -2
- package/dist/index.d.mts +2 -17
- package/dist/index.d.ts +2 -17
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/RateLimiter-DKXPLL9z.mjs +1 -0
- package/dist/packem_shared/availableAt-BSKYrouo.mjs +1 -0
- package/dist/packem_shared/{dbRateLimit-Cjcp6OlW.mjs → dbRateLimit-rlAL4c2M.mjs} +1 -1
- package/dist/packem_shared/tokenBudget-BRlWn1im.mjs +1 -0
- package/package.json +2 -2
- package/dist/packem_shared/RateLimiter-Bj5bkehV.mjs +0 -1
- package/dist/packem_shared/availableAt-BzjRBFIT.mjs +0 -1
- package/dist/packem_shared/tokenBudget-R_GR6ipU.mjs +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
</a>
|
|
8
8
|
|
|
9
|
-
<h3 align="center">Rate limiting: token-bucket / fixed-window / sliding-window algorithms, deny list,
|
|
9
|
+
<h3 align="center">Rate limiting: token-bucket / fixed-window / sliding-window algorithms, deny list, pluggable stores, and procedure middleware</h3>
|
|
10
10
|
|
|
11
11
|
<!-- END_PACKAGE_OG_IMAGE_PLACEHOLDER -->
|
|
12
12
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
Rate limiting for Lunora: token-bucket, fixed-window, and sliding-window algorithms, a deny list,
|
|
37
|
+
Rate limiting for Lunora: token-bucket, fixed-window, and sliding-window algorithms, a deny list, pluggable stores (in-memory, SQL, or the Lunora ORM), and procedure middleware that rides the `.use()` chain.
|
|
38
38
|
|
|
39
39
|
Part of the [Lunora](https://github.com/anolilab/lunora) framework — a type-safe, real-time backend on Cloudflare Workers + Durable Objects with a Vite-first DX.
|
|
40
40
|
|
package/dist/index.d.mts
CHANGED
|
@@ -19,18 +19,6 @@ interface RateLimitConfig {
|
|
|
19
19
|
period: number;
|
|
20
20
|
/** Tokens granted per `period`. */
|
|
21
21
|
rate: number;
|
|
22
|
-
/**
|
|
23
|
-
* Split a hot limit across N independent sub-buckets to avoid a single
|
|
24
|
-
* contended key/Durable Object. Each shard enforces `rate / shards` (and
|
|
25
|
-
* `capacity / shards`); a request is routed to a shard via a deterministic
|
|
26
|
-
* hash of `(name, key)`, so the same key always lands on the same shard and
|
|
27
|
-
* a single key's effective throughput is exactly `rate / shards`. Aggregate
|
|
28
|
-
* throughput across many distinct keys approaches `rate` as keys spread
|
|
29
|
-
* uniformly across shards. Reserve it for high-volume limits where
|
|
30
|
-
* contention bites; leave unset (one bucket) otherwise. Must be a positive
|
|
31
|
-
* integer — `1` is equivalent to unset.
|
|
32
|
-
*/
|
|
33
|
-
shards?: number;
|
|
34
22
|
/**
|
|
35
23
|
* Phase offset in epoch milliseconds for windowed algorithms — windows
|
|
36
24
|
* align to `start + n * period`. Ignored by token buckets. Defaults to `0`.
|
|
@@ -135,8 +123,7 @@ interface RateLimiterOptions<Names extends string> {
|
|
|
135
123
|
denyList?: Iterable<string>;
|
|
136
124
|
/**
|
|
137
125
|
* Optional key normalizer applied to every incoming `args.key` (the
|
|
138
|
-
* deny-list check
|
|
139
|
-
* the normalized form). Use for case-folding, trimming, or canonicalizing
|
|
126
|
+
* deny-list check and the storage key both see the normalized form). Use for case-folding, trimming, or canonicalizing
|
|
140
127
|
* IPs/emails so equivalent inputs share a single bucket. The deny-list
|
|
141
128
|
* itself is consulted as-is; normalize the deny-list entries up front to
|
|
142
129
|
* match.
|
|
@@ -166,9 +153,7 @@ declare class RateLimiter<Names extends string = string> {
|
|
|
166
153
|
* Read the current config and the units admittable right now for a
|
|
167
154
|
* `(name, key)` pair. The value is projected forward to the current clock
|
|
168
155
|
* (token-bucket refill, fixed-window rollover, sliding-window decay), not
|
|
169
|
-
* the last persisted figure.
|
|
170
|
-
* shard `limit()`/`run()` would route this key to — the sibling shards are
|
|
171
|
-
* never touched by this key, so summing them would over-report.
|
|
156
|
+
* the last persisted figure.
|
|
172
157
|
*/
|
|
173
158
|
getValue(name: Names, args?: {
|
|
174
159
|
key?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -19,18 +19,6 @@ interface RateLimitConfig {
|
|
|
19
19
|
period: number;
|
|
20
20
|
/** Tokens granted per `period`. */
|
|
21
21
|
rate: number;
|
|
22
|
-
/**
|
|
23
|
-
* Split a hot limit across N independent sub-buckets to avoid a single
|
|
24
|
-
* contended key/Durable Object. Each shard enforces `rate / shards` (and
|
|
25
|
-
* `capacity / shards`); a request is routed to a shard via a deterministic
|
|
26
|
-
* hash of `(name, key)`, so the same key always lands on the same shard and
|
|
27
|
-
* a single key's effective throughput is exactly `rate / shards`. Aggregate
|
|
28
|
-
* throughput across many distinct keys approaches `rate` as keys spread
|
|
29
|
-
* uniformly across shards. Reserve it for high-volume limits where
|
|
30
|
-
* contention bites; leave unset (one bucket) otherwise. Must be a positive
|
|
31
|
-
* integer — `1` is equivalent to unset.
|
|
32
|
-
*/
|
|
33
|
-
shards?: number;
|
|
34
22
|
/**
|
|
35
23
|
* Phase offset in epoch milliseconds for windowed algorithms — windows
|
|
36
24
|
* align to `start + n * period`. Ignored by token buckets. Defaults to `0`.
|
|
@@ -135,8 +123,7 @@ interface RateLimiterOptions<Names extends string> {
|
|
|
135
123
|
denyList?: Iterable<string>;
|
|
136
124
|
/**
|
|
137
125
|
* Optional key normalizer applied to every incoming `args.key` (the
|
|
138
|
-
* deny-list check
|
|
139
|
-
* the normalized form). Use for case-folding, trimming, or canonicalizing
|
|
126
|
+
* deny-list check and the storage key both see the normalized form). Use for case-folding, trimming, or canonicalizing
|
|
140
127
|
* IPs/emails so equivalent inputs share a single bucket. The deny-list
|
|
141
128
|
* itself is consulted as-is; normalize the deny-list entries up front to
|
|
142
129
|
* match.
|
|
@@ -166,9 +153,7 @@ declare class RateLimiter<Names extends string = string> {
|
|
|
166
153
|
* Read the current config and the units admittable right now for a
|
|
167
154
|
* `(name, key)` pair. The value is projected forward to the current clock
|
|
168
155
|
* (token-bucket refill, fixed-window rollover, sliding-window decay), not
|
|
169
|
-
* the last persisted figure.
|
|
170
|
-
* shard `limit()`/`run()` would route this key to — the sibling shards are
|
|
171
|
-
* never touched by this key, so summing them would over-report.
|
|
156
|
+
* the last persisted figure.
|
|
172
157
|
*/
|
|
173
158
|
getValue(name: Names, args?: {
|
|
174
159
|
key?: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{availableAt as o,evaluate as a}from"./packem_shared/availableAt-
|
|
1
|
+
import{availableAt as o,evaluate as a}from"./packem_shared/availableAt-BSKYrouo.mjs";import{default as i}from"./packem_shared/dbRateLimit-rlAL4c2M.mjs";import{default as l}from"./packem_shared/RateLimitError-9Y5OLxpl.mjs";import{rateLimit as x}from"./packem_shared/rateLimit-CWZur6XE.mjs";import{ratelimitPlugin as c}from"./packem_shared/ratelimitPlugin-CFnDs9rj.mjs";import{RateLimiter as u}from"./packem_shared/RateLimiter-DKXPLL9z.mjs";import{createDbStore as b,createMemoryStore as n,createReadOnlyDbStore as L,createSqlStore as s}from"./packem_shared/createDbStore-ve7GsO2i.mjs";import{tokenBudget as v}from"./packem_shared/tokenBudget-BRlWn1im.mjs";const e="0.0.0";export{l as RateLimitError,u as RateLimiter,e as VERSION,o as availableAt,b as createDbStore,n as createMemoryStore,L as createReadOnlyDbStore,s as createSqlStore,i as dbRateLimit,a as evaluate,x as rateLimit,c as ratelimitPlugin,v as tokenBudget};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as o}from"@lunora/errors";import{availableAt as w,evaluate as y}from"./availableAt-BSKYrouo.mjs";import f from"./RateLimitError-9Y5OLxpl.mjs";import{createMemoryStore as v}from"./createDbStore-ve7GsO2i.mjs";const l=(n,e)=>e===void 0?encodeURIComponent(n):`${encodeURIComponent(n)}:${encodeURIComponent(e)}`,p="@lunora/ratelimit: new RateLimiter() was built with no explicit `store`, so it falls back to `createMemoryStore()` — an in-process Map. That Map is durable and correctly shared only for calls that land on the same Durable Object instance; it is NOT shared across `.shardBy(...)` shards or `.global()` replicas, and it resets when the DO instance is evicted/restarted. For a limit that must hold across any of those, pass a durable `store` (`createDbStore` or `createSqlStore`) instead. Pass `store: createMemoryStore()` explicitly once you've confirmed the in-memory default is correct here, to silence this warning.";class L{config;denyList;normalize;now;store;constructor(e){this.config=e.config,this.normalize=e.normalize??(t=>t),this.denyList=new Set([...e.denyList??[]].flatMap(t=>[t,this.normalize(t)])),this.now=e.now??Date.now,e.store===void 0&&console.warn(p),this.store=e.store??v();for(const[t,r]of Object.entries(this.config)){if(Object.hasOwn(r,"shards"))throw new o("INTERNAL",`rate limit "${t}": \`shards\` is no longer supported — remove it; the limit now enforces its full \`rate\` (lower \`rate\` if you raised it to offset the split)`);if(!Number.isFinite(r.period)||r.period<=0)throw new o("INTERNAL",`rate limit "${t}": period must be a positive number`);if(!Number.isFinite(r.rate)||r.rate<=0)throw new o("INTERNAL",`rate limit "${t}": rate must be a positive number`);if(r.capacity!==void 0&&(!Number.isFinite(r.capacity)||r.capacity<0))throw new o("INTERNAL",`rate limit "${t}": capacity must be a non-negative number`)}}async check(e,t={}){return this.run(e,t,!1)}async getValue(e,t={}){const r=this.resolve(e),a=this.now(),s=l(e,this.normalizeKey(t.key)),i=w(r,await this.store.get(s),a);return{config:r,ts:i.ts,value:i.value}}async limit(e,t={}){return this.run(e,t,!0)}async reset(e,t={}){this.resolve(e),await Promise.resolve(this.store.delete(l(e,this.normalizeKey(t.key))))}normalizeKey(e){return e===void 0?void 0:this.normalize(e)}resolve(e){const t=this.config[e];if(!t)throw new o("INTERNAL",`rate limit "${e}" is not configured`);return t}async run(e,t,r){const a=this.resolve(e),s=this.normalizeKey(t.key);if(s!==void 0&&(this.denyList.has(s)||this.denyList.has(t.key))){const u={ok:!1,reason:"deny",retryAfter:Number.POSITIVE_INFINITY};if(t.throws)throw new f(u);return u}const i=t.count??1;if(!Number.isInteger(i)||i<=0)throw new o("INTERNAL",`rate limit "${e}": count must be a positive integer`);const h=l(e,s),d=await this.store.get(h),{status:c,value:m}=y(a,d,{consume:r,count:i,now:this.now(),reserve:t.reserve??!1});if(m!==void 0&&await this.store.set(h,m),!c.ok&&t.throws)throw new f(c);return c}}export{L as RateLimiter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as b}from"@lunora/errors";const o=e=>e.capacity??e.rate,E=e=>e.kind==="sliding window"?e.rate:o(e),i=(e,r)=>{throw new b("INTERNAL",`@lunora/ratelimit: requested count ${String(e)} exceeds the limiter capacity ${String(r)}`)},M=(e,r,t)=>{const u=o(e),a=e.rate/e.period,s=r??{ts:t,value:u},c=Math.max(0,t-s.ts);return{available:Math.min(u,s.value+c*a),capacity:u,ratePerMs:a}},w=(e,r,t)=>{const u=e.start??0,a=u+Math.floor((t-u)/e.period)*e.period;if(!r)return{ts:a,value:o(e)};if(r.ts<a){let s=0,c=1;return(r.value<0||e.capacity!==void 0)&&(s=r.value,c=Math.max(1,Math.ceil((a-r.ts)/e.period))),{ts:a,value:Math.min(o(e),s+c*e.rate)}}return{ts:r.ts,value:r.value}},x=(e,r,t)=>{const u=e.start??0,a=u+Math.floor((t-u)/e.period)*e.period,s=t-a,c=(e.period-s)/e.period;let n=0,l=0;return r?.ts===a?(n=r.prev??0,l=r.value):r?.ts===a-e.period&&(n=r.value),{currentCount:l,elapsed:s,previousCount:n,weight:c,windowStart:a}},S=(e,r,t)=>{const{available:u,capacity:a,ratePerMs:s}=M(e,r,t.now);if(u>=t.count){const l={ts:t.now,value:u-t.count};return{status:{ok:!0,retryAfter:0},value:t.consume?l:void 0}}const c=t.count-u,n=Math.ceil(c/s);return t.consume&&t.reserve&&t.count<=a?{status:{ok:!0,retryAfter:n},value:{ts:t.now,value:u-t.count}}:(t.count>a&&i(t.count,a),{status:{ok:!1,reason:"rate",retryAfter:n},value:void 0})},y=(e,r,t,u,a,s)=>{const c=d=>w(e,r,t+d*e.period).value>=a;let l=u<0||e.capacity!==void 0?Math.max(1,Math.ceil((a-u)/e.rate)):1;for(let d=0;d<4&&l>1&&c(l-1);d+=1)l-=1;for(let d=0;d<4&&!c(l);d+=1)l+=1;return t+l*e.period-s},W=(e,r,t)=>{const u=o(e),a=w(e,r,t.now);if(a.value>=t.count){const s={ts:a.ts,value:a.value-t.count};return{status:{ok:!0,retryAfter:0},value:t.consume?s:void 0}}if(t.consume&&t.reserve&&t.count<=u){const s=a.value-t.count;return{status:{ok:!0,retryAfter:y(e,{ts:a.ts,value:s},a.ts,s,0,t.now)},value:{ts:a.ts,value:s}}}return t.count>u&&i(t.count,u),{status:{ok:!1,reason:"rate",retryAfter:y(e,r,a.ts,a.value,t.count,t.now)},value:void 0}},N=(e,r,t)=>{const u=e.rate,{period:a}=e,{currentCount:s,elapsed:c,previousCount:n,weight:l,windowStart:d}=x(e,r,t.now),m=n*l+s+t.count<=u,h=v=>{const k=u-v-t.count;if(n>0&&k>=0)return Math.ceil(a-c-k*a/n);const A=u-t.count,C=v>0?Math.max(0,a-A*a/v):0;return Math.ceil(a-c+C)};if(m||t.consume&&t.reserve&&t.count<=u){const v={prev:n,ts:d,value:s+t.count};return{status:{ok:!0,retryAfter:m?0:h(v.value)},value:t.consume?v:void 0}}return t.count>u&&i(t.count,u),{status:{ok:!1,reason:"rate",retryAfter:h(s)},value:void 0}},B=(e,r,t)=>{if(e.kind==="token bucket")return{ts:t,value:M(e,r,t).available};if(e.kind==="sliding window"){const{currentCount:u,previousCount:a,weight:s,windowStart:c}=x(e,r,t);return{ts:c,value:Math.max(0,e.rate-(a*s+u))}}return w(e,r,t)},L=(e,r,t)=>e.kind==="token bucket"?S(e,r,t):e.kind==="sliding window"?N(e,r,t):W(e,r,t);export{B as availableAt,E as enforcedCapacity,L as evaluate};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{rateLimit as o}from"./rateLimit-CWZur6XE.mjs";import{RateLimiter as m}from"./RateLimiter-
|
|
1
|
+
import{rateLimit as o}from"./rateLimit-CWZur6XE.mjs";import{RateLimiter as m}from"./RateLimiter-DKXPLL9z.mjs";import{createDbStore as i}from"./createDbStore-ve7GsO2i.mjs";const f=(e,r,t={})=>o(a=>new m({config:e,store:i({db:a.db,...t.store})}),r,t);export{f as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{enforcedCapacity as n}from"./availableAt-BSKYrouo.mjs";const h=(t,r)=>({check:async c=>t.check(r,{key:c}),record:async(c,a)=>{if(!Number.isFinite(a)||a<=0)return t.check(r,{key:c});const{config:e}=await t.getValue(r,{key:c}),o=Math.max(1,Math.floor(n(e)));return t.limit(r,{count:Math.min(Math.ceil(a),o),key:c,reserve:!0})}});export{h as tokenBudget};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/ratelimit",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"description": "Rate limiting: token-bucket / fixed-window / sliding-window algorithms, deny list,
|
|
3
|
+
"version": "1.0.0-alpha.82",
|
|
4
|
+
"description": "Rate limiting: token-bucket / fixed-window / sliding-window algorithms, deny list, pluggable stores, and procedure middleware",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
7
7
|
"durable-objects",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as a}from"@lunora/errors";import{availableAt as p,evaluate as v}from"./availableAt-BzjRBFIT.mjs";import u from"./RateLimitError-9Y5OLxpl.mjs";import{createMemoryStore as N}from"./createDbStore-ve7GsO2i.mjs";const f=(o,e)=>e===void 0?encodeURIComponent(o):`${encodeURIComponent(o)}:${encodeURIComponent(e)}`,g=(o,e)=>{let t=0;for(let r=0;r<o.length;r+=1)t=t*31+o.charCodeAt(r)|0;return Math.abs(t)%e},I="@lunora/ratelimit: new RateLimiter() was built with no explicit `store`, so it falls back to `createMemoryStore()` — an in-process Map. That Map is durable and correctly shared only for calls that land on the same Durable Object instance; it is NOT shared across `.shardBy(...)` shards or `.global()` replicas, and it resets when the DO instance is evicted/restarted. For a limit that must hold across any of those, pass a durable `store` (`createDbStore` or `createSqlStore`) instead. Pass `store: createMemoryStore()` explicitly once you've confirmed the in-memory default is correct here, to silence this warning.",y=(o,e)=>e>1?{...o,capacity:(o.capacity??o.rate)/e,rate:o.rate/e}:o,L=(o,e,t)=>{const r=f(o,e);return t>1?Array.from({length:t},(i,s)=>`${r}#${String(s)}`):[r]},w=(o,e,t)=>{const r=f(o,e);return t>1?`${r}#${String(g(r,t))}`:r};class ${config;denyList;normalize;now;store;constructor(e){this.config=e.config,this.normalize=e.normalize??(t=>t),this.denyList=new Set([...e.denyList??[]].flatMap(t=>[t,this.normalize(t)])),this.now=e.now??Date.now,e.store===void 0&&console.warn(I),this.store=e.store??N();for(const[t,r]of Object.entries(this.config)){if(r.shards!==void 0&&(!Number.isInteger(r.shards)||r.shards<1))throw new a("INTERNAL",`rate limit "${t}": shards must be a positive integer`);if(!Number.isFinite(r.period)||r.period<=0)throw new a("INTERNAL",`rate limit "${t}": period must be a positive number`);if(!Number.isFinite(r.rate)||r.rate<=0)throw new a("INTERNAL",`rate limit "${t}": rate must be a positive number`);if(r.capacity!==void 0&&(!Number.isFinite(r.capacity)||r.capacity<0))throw new a("INTERNAL",`rate limit "${t}": capacity must be a non-negative number`)}}async check(e,t={}){return this.run(e,t,!1)}async getValue(e,t={}){const r=this.resolve(e),i=r.shards??1,s=this.now(),n=this.normalizeKey(t.key),h=w(e,n,i),c=p(y(r,i),await this.store.get(h),s);return{config:r,ts:c.ts,value:c.value}}async limit(e,t={}){return this.run(e,t,!0)}async reset(e,t={}){const r=this.resolve(e).shards??1,i=this.normalizeKey(t.key);await Promise.all(L(e,i,r).map(s=>Promise.resolve(this.store.delete(s))))}normalizeKey(e){return e===void 0?void 0:this.normalize(e)}resolve(e){const t=this.config[e];if(!t)throw new a("INTERNAL",`rate limit "${e}" is not configured`);return t}async run(e,t,r){const i=this.resolve(e),s=this.normalizeKey(t.key);if(s!==void 0&&(this.denyList.has(s)||this.denyList.has(t.key))){const d={ok:!1,reason:"deny",retryAfter:Number.POSITIVE_INFINITY};if(t.throws)throw new u(d);return d}const n=t.count??1;if(!Number.isInteger(n)||n<=0)throw new a("INTERNAL",`rate limit "${e}": count must be a positive integer`);const h=i.shards??1,c=w(e,s,h),b=await this.store.get(c),{status:l,value:m}=v(y(i,h),b,{consume:r,count:n,now:this.now(),reserve:t.reserve??!1});if(m!==void 0&&await this.store.set(c,m),!l.ok&&t.throws)throw new u(l);return l}}export{$ as RateLimiter};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as C}from"@lunora/errors";const v=e=>e.capacity??e.rate,j=e=>e.kind==="sliding window"?e.rate:v(e),o=(e,a)=>{throw new C("INTERNAL",`@lunora/ratelimit: requested count ${String(e)} exceeds the limiter capacity ${String(a)}`)},k=(e,a,t)=>{const u=v(e),r=e.rate/e.period,s=a??{ts:t,value:u},c=Math.max(0,t-s.ts);return{available:Math.min(u,s.value+c*r),capacity:u,ratePerMs:r}},h=(e,a,t)=>{const u=e.start??0,r=u+Math.floor((t-u)/e.period)*e.period;if(!a||a.ts<r){let s=0,c=1;return a&&(a.value<0||e.capacity!==void 0)&&(s=a.value,c=Math.max(1,Math.ceil((r-a.ts)/e.period))),{ts:r,value:Math.min(v(e),s+c*e.rate)}}return{ts:a.ts,value:a.value}},y=(e,a,t)=>{const u=e.start??0,r=u+Math.floor((t-u)/e.period)*e.period,s=t-r,c=(e.period-s)/e.period;let n=0,l=0;return a?.ts===r?(n=a.prev??0,l=a.value):a?.ts===r-e.period&&(n=a.value),{currentCount:l,elapsed:s,previousCount:n,weight:c,windowStart:r}},b=(e,a,t)=>{const{available:u,capacity:r,ratePerMs:s}=k(e,a,t.now);if(u>=t.count){const l={ts:t.now,value:u-t.count};return{status:{ok:!0,retryAfter:0},value:t.consume?l:void 0}}const c=t.count-u,n=Math.ceil(c/s);return t.consume&&t.reserve&&t.count<=r?{status:{ok:!0,retryAfter:n},value:{ts:t.now,value:u-t.count}}:(t.count>r&&o(t.count,r),{status:{ok:!1,reason:"rate",retryAfter:n},value:void 0})},S=(e,a,t)=>{const u=v(e),r=h(e,a,t.now);if(r.value>=t.count){const c={ts:r.ts,value:r.value-t.count};return{status:{ok:!0,retryAfter:0},value:t.consume?c:void 0}}const s=r.ts+e.period-t.now;return t.consume&&t.reserve&&t.count<=u?{status:{ok:!0,retryAfter:s},value:{ts:r.ts,value:r.value-t.count}}:(t.count>u&&o(t.count,u),{status:{ok:!1,reason:"rate",retryAfter:s},value:void 0})},f=(e,a,t)=>{const u=e.rate,{period:r}=e,{currentCount:s,elapsed:c,previousCount:n,weight:l,windowStart:M}=y(e,a,t.now),i=n*l+s+t.count<=u,w=d=>{const m=u-d-t.count;if(n>0&&m>=0)return Math.ceil(r-c-m*r/n);const x=u-t.count,A=d>0?Math.max(0,r-x*r/d):0;return Math.ceil(r-c+A)};if(i||t.consume&&t.reserve&&t.count<=u){const d={prev:n,ts:M,value:s+t.count};return{status:{ok:!0,retryAfter:i?0:w(d.value)},value:t.consume?d:void 0}}return t.count>u&&o(t.count,u),{status:{ok:!1,reason:"rate",retryAfter:w(s)},value:void 0}},E=(e,a,t)=>{if(e.kind==="token bucket")return{ts:t,value:k(e,a,t).available};if(e.kind==="sliding window"){const{currentCount:u,previousCount:r,weight:s,windowStart:c}=y(e,a,t);return{ts:c,value:Math.max(0,e.rate-(r*s+u))}}return h(e,a,t)},B=(e,a,t)=>e.kind==="token bucket"?b(e,a,t):e.kind==="sliding window"?f(e,a,t):S(e,a,t);export{E as availableAt,j as enforcedCapacity,B as evaluate};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{enforcedCapacity as n}from"./availableAt-BzjRBFIT.mjs";const u=(t,r)=>({check:async c=>t.check(r,{key:c}),record:async(c,a)=>{if(!Number.isFinite(a)||a<=0)return t.check(r,{key:c});const{config:e}=await t.getValue(r,{key:c}),o=Math.max(1,Math.floor(n(e)/(e.shards??1)));return t.limit(r,{count:Math.min(Math.ceil(a),o),key:c,reserve:!0})}});export{u as tokenBudget};
|