express-adaptive-rate-limiter 1.0.0 → 2.0.0

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.
Files changed (51) hide show
  1. package/README.md +250 -16
  2. package/dist/algorithms/baseAlgorithm.js +1 -1
  3. package/dist/algorithms/fixedWindow.js +1 -1
  4. package/dist/algorithms/leakyBucket.js +1 -1
  5. package/dist/algorithms/slidingWindowCounter.js +1 -1
  6. package/dist/algorithms/slidingWindowLog.js +1 -1
  7. package/dist/algorithms/tokenBucket.js +1 -1
  8. package/dist/core/blockedKeys.js +1 -0
  9. package/dist/core/coreMiddleware.js +1 -0
  10. package/dist/core/limiters/clusterLimiter.js +16 -0
  11. package/dist/core/limiters/dynamoDbLimiter.js +1 -0
  12. package/dist/core/limiters/etcdLimiter.js +1 -0
  13. package/dist/core/limiters/memcachedLimiter.js +1 -0
  14. package/dist/core/limiters/memoryLimiter.js +1 -0
  15. package/dist/core/limiters/mongoLimiter.js +1 -0
  16. package/dist/core/limiters/mongooseLimiter.js +1 -0
  17. package/dist/core/limiters/mySqlLimiter.js +12 -0
  18. package/dist/core/limiters/nodeClusterLimiter.js +1 -0
  19. package/dist/core/limiters/postgresLimiter.js +13 -0
  20. package/dist/core/limiters/prismaLimiter.js +24 -0
  21. package/dist/core/limiters/redisLimiter.js +16 -0
  22. package/dist/core/limiters/sqliteLimiter.js +13 -0
  23. package/dist/core/limiters/valkeyLimiter.js +16 -0
  24. package/dist/core/rateLimiter.js +1 -0
  25. package/dist/core/rateLimiterRes.js +1 -0
  26. package/dist/core/rateLimiterStore.js +1 -0
  27. package/dist/core/wrappers/blackAndWhiteLimiter.js +1 -0
  28. package/dist/core/wrappers/burstyRateLimiter.js +1 -0
  29. package/dist/core/wrappers/rateLimiterQueue.js +1 -0
  30. package/dist/core/wrappers/rateLimiterUnion.js +1 -0
  31. package/dist/core/wrappers/timeoutLimiter.js +1 -0
  32. package/dist/index.js +1 -1
  33. package/dist/middleware/expressMiddleware.js +1 -1
  34. package/dist/middleware/headers.js +1 -1
  35. package/dist/rateLimit.js +1 -1
  36. package/dist/rateLimitManager.js +1 -1
  37. package/dist/stores/baseStore.js +1 -1
  38. package/dist/stores/luaScripts.js +379 -1
  39. package/dist/stores/memoryStore.js +1 -1
  40. package/dist/stores/redisStore.js +1 -1
  41. package/dist/types/index.d.ts +507 -0
  42. package/dist/utils/errors.js +1 -1
  43. package/dist/utils/graphQLParser.js +1 -1
  44. package/dist/utils/ipUtils.js +1 -1
  45. package/dist/utils/keyGenerator.js +1 -1
  46. package/dist/utils/redisClient.js +1 -0
  47. package/dist/utils/retryAfter.js +1 -1
  48. package/dist/utils/skipUtils.js +1 -1
  49. package/dist/utils/time.js +1 -1
  50. package/dist/utils/validation.js +1 -1
  51. package/package.json +67 -4
package/README.md CHANGED
@@ -1,8 +1,25 @@
1
1
  # express-adaptive-rate-limiter
2
2
 
3
- Production-ready, multi-algorithm rate limiting middleware for Express.js.
3
+ Production-ready rate limiting for Node.js.
4
4
 
5
- Supports **Fixed Window**, **Sliding Window Log**, **Sliding Window Counter**, **Token Bucket**, and **Leaky Bucket** algorithms with **Memory** and **Redis** backends. Built-in atomic Lua scripts for Redis, automatic fail-open/fail-closed, configurable RFC-compliant standard headers (IETF draft-6 and draft-7), custom key generators, GraphQL root field detection, dynamic request costs, powerful multi-criteria skip rules, and full TypeScript support.
5
+ The package ships **two tiers**:
6
+
7
+ - **Core tier** — framework-agnostic limiters with a `consume()` / `get()` / `set()` / `block()` API, backed by **Memory, Redis, Mongo, Mongoose, MySQL, Postgres, SQLite, Prisma, Memcached, DynamoDB, Valkey, etcd** and **Redis Cluster**.
8
+ - **Express tier** — middleware built on **Fixed Window, Sliding Window Log, Sliding Window Counter, Token Bucket** and **Leaky Bucket** algorithms, with atomic Redis Lua scripts, fail-open/fail-closed handling, RFC-compliant standard headers, GraphQL root field detection, dynamic request costs, multi-criteria skip rules and full TypeScript support.
9
+
10
+ Every backend in the core tier is atomic: the counter increment and the window creation happen in a single round trip, so several processes can share one limit safely.
11
+
12
+ ---
13
+
14
+ ## What's new in 2.0.0
15
+
16
+ **New: the framework-agnostic core tier.** 13 storage backends (Memory, Redis, Valkey, Redis Cluster, Mongo, Mongoose, DynamoDB, MySQL, Postgres, SQLite, Prisma, Memcached, etcd) with a unified points-based API, plus the `Queue`, `Union`, `Bursty`, `BlackAndWhite` and `Timeout` wrappers. See [Core Tier](#core-tier-framework-agnostic-limiters).
17
+
18
+ **Behavior change: the fixed window is anchored on the first request of a key.** In 1.0.0 the window was aligned to the wall clock, so a client that straddled a minute boundary could consume two full windows in a few milliseconds and exceed its configured limit. The window now starts with the first request of a key and lasts `windowMs`. `RateLimit-Reset` now reports the real time until the window ends instead of the next clock boundary.
19
+
20
+ **Performance.** The memory store evicts the least recently used key in O(1) instead of scanning the whole keyspace, IP whitelists and header-based skip rules are compiled once and cached, and Redis key listing uses `SCAN` instead of the blocking `KEYS`.
21
+
22
+ **Redis client support.** A single adapter now drives `ioredis`, `node-redis` v3 and `node-redis` v4, detected at runtime, so the same code works with every supported client.
6
23
 
7
24
  ---
8
25
 
@@ -14,23 +31,27 @@ npm install express-adaptive-rate-limiter
14
31
 
15
32
  ### Optional Peer Dependencies
16
33
 
17
- Install only what you need:
18
-
19
- - **Redis backend**:
20
- ```bash
21
- npm install ioredis
22
- # OR
23
- npm install redis
24
- ```
25
- - **GraphQL root field key generation**:
26
- ```bash
27
- npm install graphql
28
- ```
34
+ Install only the driver for the backend you use:
35
+
36
+ - **Redis**: `npm install ioredis` or `npm install redis`
37
+ - **MongoDB**: `npm install mongodb`
38
+ - **Mongoose**: `npm install mongoose`
39
+ - **MySQL**: `npm install mysql2`
40
+ - **PostgreSQL**: `npm install pg`
41
+ - **SQLite**: `npm install better-sqlite3` (Node 22.5+ can use the built-in `node:sqlite`)
42
+ - **Prisma**: `npm install @prisma/client`
43
+ - **Memcached**: `npm install memjs` or `npm install memcached`
44
+ - **DynamoDB**: `npm install @aws-sdk/client-dynamodb`
45
+ - **Valkey**: `npm install iovalkey` or `npm install @valkey/valkey-glide`
46
+ - **etcd**: `npm install etcd3`
47
+ - **GraphQL root field key generation**: `npm install graphql`
29
48
 
30
49
  ---
31
50
 
32
51
  ## Quick Start
33
52
 
53
+ ### Express middleware
54
+
34
55
  ```javascript
35
56
  import express from 'express';
36
57
  import { rateLimit } from 'express-adaptive-rate-limiter';
@@ -53,13 +74,42 @@ app.get('/', (req, res) => {
53
74
  app.listen(3000);
54
75
  ```
55
76
 
77
+ ### Framework-agnostic core limiter
78
+
79
+ ```javascript
80
+ import { MemoryLimiter } from 'express-adaptive-rate-limiter';
81
+
82
+ const limiter = new MemoryLimiter({ points: 100, duration: 60 });
83
+
84
+ const res = await limiter.consume('user-42');
85
+ console.log(res.remainingPoints, res.msBeforeNext);
86
+ ```
87
+
88
+ ### Core limiter behind Express
89
+
90
+ Any core limiter can be turned into Express middleware:
91
+
92
+ ```javascript
93
+ import express from 'express';
94
+ import { MongoLimiter, createCoreMiddleware } from 'express-adaptive-rate-limiter';
95
+
96
+ const app = express();
97
+
98
+ app.use(
99
+ createCoreMiddleware(
100
+ new MongoLimiter({ points: 100, duration: 60, dbName: 'my-app' }),
101
+ { standardHeaders: true }
102
+ )
103
+ );
104
+ ```
105
+
56
106
  ---
57
107
 
58
108
  ## Supported Algorithms
59
109
 
60
110
  | Algorithm | Identifier | Best For | Memory | Redis | Atomic |
61
111
  | --------------------------- | ------------------------ | -------------------------------------------- | :----: | :------: | :----: |
62
- | **Fixed Window Counter** | `fixed-window` | Low-overhead, simple reset boundaries | ✅ | ✅ (Lua) | ✅ |
112
+ | **Fixed Window Counter** | `fixed-window` | Low-overhead, window anchored on first hit | ✅ | ✅ (Lua) | ✅ |
63
113
  | **Sliding Window Log** | `sliding-window-log` | 100% strict precision rolling window | ✅ | ✅ (Lua) | ✅ |
64
114
  | **Sliding Window Counter** | `sliding-window-counter` | Smooth rolling limits with O(1) memory | ✅ | ✅ (Lua) | ✅ |
65
115
  | **Token Bucket** | `token-bucket` | Bursts allowed with steady background refill | ✅ | ✅ (Lua) | ✅ |
@@ -68,6 +118,157 @@ app.listen(3000);
68
118
 
69
119
  ---
70
120
 
121
+ ## Core Tier: Framework-Agnostic Limiters
122
+
123
+ The core tier is a high-performance points-based limiter. A limiter holds a counter per key; the counter is reset when its window expires.
124
+
125
+ ### The API
126
+
127
+ Every core limiter exposes the same surface:
128
+
129
+ | Method | Description |
130
+ | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
131
+ | `consume(key, points?, options?)` | Consume points. Resolves with a `RateLimiterRes`, or **rejects** with one when the limit is exceeded. |
132
+ | `penalty(key, points?, options?)` | Increase the consumed points without moving the window. |
133
+ | `reward(key, points?, options?)` | Decrease the consumed points without moving the window. |
134
+ | `get(key, options?)` | Read the current state, or `null` when the key is unknown. |
135
+ | `set(key, points, secDuration?, options?)` | Overwrite the counter and restart the window. |
136
+ | `block(key, secDuration, options?)` | Block a key for a number of seconds. |
137
+ | `delete(key, options?)` | Reset a key in the store and clear in-memory blocks. Returns whether it existed. |
138
+ | `deleteInMemoryBlocked(key)` | Remove only the local in-process block for a specific key. |
139
+ | `deleteInMemoryBlockedAll()` | Remove all active in-process blocks. |
140
+ | `getKey(key)` / `parseKey(rlKey)` | Prefix handling for the storage key. |
141
+
142
+ `RateLimiterRes` carries the following fields:
143
+
144
+ ```javascript
145
+ {
146
+ remainingPoints: 99, // points still available
147
+ msBeforeNext: 59832, // ms until the window resets (-1 = never)
148
+ consumedPoints: 1, // points consumed in this window
149
+ isFirstInDuration: true // whether this call opened the window
150
+ }
151
+ ```
152
+
153
+ ### Common options
154
+
155
+ | Option | Type | Default | Description |
156
+ | ------------------------ | ----------------- | ------- | ------------------------------------------------------------------------- |
157
+ | `points` | `number` | `60` | Maximum points consumable within the window. `limit` is an alias. |
158
+ | `duration` | `number` | `60` | Window length in **seconds**. `0` means points are never reset. |
159
+ | `windowMs` | `number` | — | Alias of `duration`, in milliseconds. |
160
+ | `blockDuration` | `number` | `0` | Seconds a key stays blocked once it exceeds its limit. |
161
+ | `execEvenly` | `boolean` | `false` | Spread allowed requests evenly across the window. |
162
+ | `execEvenlyMinDelayMs` | `number` | derived | Minimum delay between two evenly spread executions. |
163
+ | `keyPrefix` | `string` | `'rl:'` | Prefix applied to every stored key. |
164
+ | `insuranceLimiter` | `RateLimiter` | `null` | Fallback limiter used when the primary store fails. |
165
+ | `inMemoryBlockOnConsumed`| `number` | `0` | Block a key in process memory once this many points are consumed. |
166
+ | `inMemoryBlockDuration` | `number` | `0` | Seconds the in-memory block lasts. |
167
+ | `clearExpiredByTimeout` | `boolean` | `false` | Automatically purge expired rows from SQL/Mongo tables periodically. |
168
+ | `clearExpiredTimeoutMs` | `number` | `300000`| Interval for expired key purge timer (default 5 minutes). |
169
+
170
+ ### Backends & Class Aliases
171
+
172
+ Both modern descriptive names and classic class aliases are exported for convenience:
173
+
174
+ | Descriptive Limiter | Class Alias | Driver | Atomic mechanism |
175
+ | ------------------- | ----------------------------- | ----------------------------------------- | ------------------------------------ |
176
+ | `MemoryLimiter` | `RateLimiterMemory` | none | in-process Map with O(1) eviction |
177
+ | `RedisLimiter` | `RateLimiterRedis` | `ioredis` or `redis` | Lua script |
178
+ | `RateLimiterCluster`| `RateLimiterCluster` | `node:cluster` IPC | Master-Worker IPC messaging |
179
+ | `RedisClusterLimiter`| `ClusterLimiter` | ioredis Cluster / node-redis cluster | Lua script, hash-tagged `{slot}` |
180
+ | `ValkeyLimiter` | `RateLimiterValkey` | `iovalkey` or `@valkey/valkey-glide` | Lua script |
181
+ | `MongoLimiter` | `RateLimiterMongo` | `mongodb` | `findOneAndUpdate` with `$inc` |
182
+ | `MongooseLimiter` | `RateLimiterMongoose` | `mongoose` | `findOneAndUpdate` with `$inc` |
183
+ | `DynamoDbLimiter` | `RateLimiterDynamo` / `DynamoDb` | `@aws-sdk/client-dynamodb` | `UpdateItem` with `ADD` |
184
+ | `MySqlLimiter` | `RateLimiterMySQL` | `mysql2` | `INSERT ... ON DUPLICATE KEY UPDATE` |
185
+ | `PostgresLimiter` | `RateLimiterPostgres` | `pg` | `INSERT ... ON CONFLICT DO UPDATE` |
186
+ | `SqliteLimiter` | `RateLimiterSQLite` | `better-sqlite3` or built-in `node:sqlite`| `INSERT ... ON CONFLICT DO UPDATE` |
187
+ | `PrismaLimiter` | `RateLimiterPrisma` | `@prisma/client` | raw upsert, Postgres or MySQL |
188
+ | `MemcachedLimiter` | `RateLimiterMemcache` / `Memcached` | `memjs` / `memcached` | `add` for creation, `incr`/`decr` |
189
+ | `EtcdLimiter` | `RateLimiterEtcd` | `etcd3` | compare-and-swap transaction |
190
+
191
+ Each backend accepts its driver through `storeClient` (with `client` as an alias).
192
+
193
+ ### Node.js Cluster Rate Limiting (`RateLimiterCluster` & `RateLimiterClusterMaster`)
194
+
195
+ Rate-limit across multiple CPU cores without Redis using Node's native Inter-Process Communication (IPC):
196
+
197
+ ```javascript
198
+ import cluster from 'node:cluster';
199
+ import { RateLimiterClusterMaster, RateLimiterCluster } from 'express-adaptive-rate-limiter';
200
+
201
+ if (cluster.isPrimary || cluster.isMaster) {
202
+ // Master process initializes the central coordinator
203
+ new RateLimiterClusterMaster();
204
+
205
+ // Fork worker processes
206
+ for (let i = 0; i < 4; i++) {
207
+ cluster.fork();
208
+ }
209
+ } else {
210
+ // Worker process rate limiter
211
+ const limiter = new RateLimiterCluster({
212
+ keyPrefix: 'cluster_rl',
213
+ points: 10,
214
+ duration: 1,
215
+ timeoutMs: 3000 // failsafe timeout
216
+ });
217
+
218
+ const res = await limiter.consume('user-ip');
219
+ console.log(res.remainingPoints);
220
+ }
221
+ ```
222
+
223
+ ### In-Memory State Dump & Restore (`dump()` & `restore()`)
224
+
225
+ Save in-memory state on shutdown for zero-downtime restarts:
226
+
227
+ ```javascript
228
+ import fs from 'node:fs';
229
+ import { RateLimiterMemory } from 'express-adaptive-rate-limiter';
230
+
231
+ const limiter = new RateLimiterMemory({ points: 10, duration: 60 });
232
+
233
+ // On graceful shutdown
234
+ const snapshot = limiter.dump();
235
+ fs.writeFileSync('./rate-limit-state.json', JSON.stringify(snapshot));
236
+
237
+ // On application startup
238
+ if (fs.existsSync('./rate-limit-state.json')) {
239
+ const data = JSON.parse(fs.readFileSync('./rate-limit-state.json', 'utf8'));
240
+ const report = limiter.restore(data);
241
+ console.log(`Restored ${report.restored} keys, ${report.expired} expired`);
242
+ }
243
+ ```
244
+
245
+ ### Wrappers
246
+
247
+ | Wrapper | Purpose |
248
+ | ---------------------------------------- | ---------------------------------------------------------------- |
249
+ | `RateLimiterQueue` | Queues requests instead of rejecting them when the limit is full.|
250
+ | `RateLimiterUnion` | Requires every wrapped limiter to allow the request. |
251
+ | `BurstyRateLimiter` | A steady limiter with a burst fallback. |
252
+ | `BlackAndWhiteLimiter` / `RateLimiterBlackAndWhite` | Black and white lists in front of a limiter. |
253
+ | `TimeoutLimiter` | Fails a call that takes longer than `timeoutMs`. |
254
+
255
+ ```javascript
256
+ import { MemoryLimiter, RateLimiterUnion, BlackAndWhiteLimiter } from 'express-adaptive-rate-limiter';
257
+
258
+ const perIp = new MemoryLimiter({ points: 100, duration: 60 });
259
+ const perAccount = new MemoryLimiter({ points: 10, duration: 60, keyPrefix: 'acct' });
260
+
261
+ const both = new RateLimiterUnion(perIp, perAccount);
262
+
263
+ const guarded = new BlackAndWhiteLimiter({
264
+ limiter: both,
265
+ whiteList: ['10.0.0.0/8'],
266
+ blackList: ['203.0.113.5']
267
+ });
268
+ ```
269
+
270
+ ---
271
+
71
272
  ## Configuration Reference
72
273
 
73
274
  ### 1. Common Options
@@ -107,7 +308,7 @@ All options below can be passed to `rateLimit(options)`:
107
308
 
108
309
  #### Fixed Window (`algorithm: 'fixed-window'`)
109
310
 
110
- Divides time into fixed discrete windows (e.g. 1 minute). Resets count at each window boundary.
311
+ Counts requests in a window that starts with the **first request of a key** and lasts `windowMs`. The window never slides, so a client can never consume two windows worth of quota in quick succession.
111
312
 
112
313
  | Option | Type | Default | Description |
113
314
  | --------------- | -------- | ------- | ------------------------------------------- |
@@ -229,6 +430,8 @@ rateLimit({
229
430
 
230
431
  ### 4. Storage Backends
231
432
 
433
+ The Express tier uses the algorithm stores (`MemoryStore`, `RedisStore`) described below. The **core tier** ships its own set of backends — see [Core Tier](#core-tier-framework-agnostic-limiters) for the full list and their options.
434
+
232
435
  #### MemoryStore Options
233
436
 
234
437
  ```javascript
@@ -604,6 +807,8 @@ Retry-After: 45
604
807
  | `validateIpRangeV4` | `(ip, whitelist?) => boolean` | Tests IP against whitelist or `Platform.WHITELIST_IPS`. |
605
808
  | `validateSecondaryIps` | `(req, whitelist?) => boolean` | Validates `x-real-ip` or `x-forwarded-for` when `x-xcld: true`. |
606
809
  | `evaluateSkipRule` | `(rule, req, res, context?) => Promise<boolean>` | Evaluates skip condition across headers, origins, IPs, keys. |
810
+ | `createCoreMiddleware` | `(limiter, options?) => middleware` | Turns a core limiter into Express middleware. |
811
+ | `detectRedisClientKind` | `(client) => string` | Reports which Redis client family is in use. |
607
812
 
608
813
  ---
609
814
 
@@ -616,6 +821,7 @@ All error classes inherit from `RateLimiterError`:
616
821
  - `RedisConnectionError`: Thrown when Redis connection, command, or script execution fails.
617
822
  - `QueueFullError`: Thrown in Leaky Bucket shaping mode when `maxQueueSize` is exceeded.
618
823
  - `QueueTimeoutError`: Thrown in Leaky Bucket shaping mode when a queued request times out (`queueTimeoutMs`).
824
+ - `RateLimiterQueueError`: Thrown by `RateLimiterQueue` when the queue is full or a queued request expires.
619
825
 
620
826
  ---
621
827
 
@@ -651,6 +857,34 @@ app.get('/info', (req, res) => {
651
857
  });
652
858
  ```
653
859
 
860
+ ## Performance Benchmarks
861
+
862
+ Run the benchmark suite with `npm run bench`:
863
+
864
+ ```
865
+ ===============================================================================================
866
+ Express Adaptive Rate Limiter - Performance Benchmark Suite
867
+ ===============================================================================================
868
+ | Algorithm & Store | Reqs | Throughput | Mean | p50 | p95 | p99 |
869
+ -----------------------------------------------------------------------------------------------
870
+ | Fixed Window (Memory) | 10000 | 254,535 req/s | 0.078 ms | 0.049 ms | 0.172 ms | 0.654 ms |
871
+ | Sliding Window Counter (Memory) | 10000 | 264,659 req/s | 0.075 ms | 0.060 ms | 0.116 ms | 0.786 ms |
872
+ | Token Bucket (Memory) | 10000 | 321,866 req/s | 0.061 ms | 0.047 ms | 0.087 ms | 0.555 ms |
873
+ | Leaky Bucket Policing (Memory) | 10000 | 188,326 req/s | 0.105 ms | 0.079 ms | 0.246 ms | 0.749 ms |
874
+ | Sliding Window Log (Memory) | 10000 | 121,287 req/s | 0.164 ms | 0.127 ms | 0.227 ms | 0.826 ms |
875
+ | Core Points Limiter (Memory) | 10000 | 205,303 req/s | 0.095 ms | 0.070 ms | 0.190 ms | 0.660 ms |
876
+ | Core Points Limiter (Redis) | 1000 | 1,200+ req/s| 0.810 ms | 0.720 ms | 1.450 ms | 2.100 ms |
877
+ ===============================================================================================
878
+ ```
879
+
880
+ Key performance highlights:
881
+ - **Zero-overhead memory tier**: 250,000–320,000 requests/sec with p50 latency under 0.06 ms.
882
+ - **Atomic Redis scripts**: Single round-trip Lua scripts for atomic increments and TTL sets without race conditions.
883
+ - **In-memory blocking shield**: Protects Redis or SQL databases against high-frequency brute-force/DoS attacks by caching blocks in local process memory (`inMemoryBlockOnConsumed`).
884
+ - **O(1) LRU eviction**: High memory ceilings without garbage collection pauses.
885
+
886
+ ---
887
+
654
888
  ## License
655
889
 
656
890
  MIT © Express Adaptive Rate Limiter Team
@@ -1 +1 @@
1
- function _0x47a5(_0x51c212,_0x165fdc){_0x51c212=_0x51c212-301;const _0x1095cc=_0x1095();let _0x47a50a=_0x1095cc[_0x51c212];if(_0x47a5["ycBEGA"]===undefined){var _0x5450e5=function(_0x15ad9d){const _0x26983d="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x429b31="",_0x3ba478="";for(let _0x5b6f0d=0,_0x4a0a50,_0x31250c,_0x51469f=0;_0x31250c=_0x15ad9d["charAt"](_0x51469f++);~_0x31250c&&(_0x4a0a50=_0x5b6f0d%4?_0x4a0a50*64+_0x31250c:_0x31250c,_0x5b6f0d++%4)?_0x429b31+=String["fromCharCode"](255&_0x4a0a50>>(-2*_0x5b6f0d&6)):0){_0x31250c=_0x26983d["indexOf"](_0x31250c)}for(let _0x3a614b=0,_0x10e72a=_0x429b31["length"];_0x3a614b<_0x10e72a;_0x3a614b++){_0x3ba478+="%"+("00"+_0x429b31["charCodeAt"](_0x3a614b)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x3ba478)};_0x47a5["SFwwOi"]=_0x5450e5,_0x47a5["byooEB"]={},_0x47a5["ycBEGA"]=!![]}const _0x26a2e1=_0x1095cc[0];_0x47a5["ohdXjQ"]!==_0x26a2e1&&(_0x47a5["byooEB"]={},_0x47a5["ohdXjQ"]=_0x26a2e1);const _0xe7b0f6=_0x47a5["byooEB"][_0x51c212];return _0xe7b0f6===undefined?(_0x47a50a=_0x47a5["SFwwOi"](_0x47a50a),_0x47a5["byooEB"][_0x51c212]=_0x47a50a):_0x47a50a=_0xe7b0f6,_0x47a50a}function _0x1095(){const _0x34fc2b=["mte2v3zPwufh","ntG1nxDmqLnIwG","y2XVC2u","mJyXntDcsMzoyxC","C3rVCMu","zNvUy3rPB24","ndGZodyWtvHPD0rw","Bwf4","z2v0","Bg9JA3m","nJbJre13u0C","mJy1mJq0tKDWzuLx","y2XLyxi","CNvUu2nYAxb0","B3b0Aw9UCW","qwXNB3jPDgHTlMnVBNn1BwuOksbTDxn0igjLigLTCgXLBwvUDgvK","CMvZB2X2zq","ntzfDvDjswy","CMvZzxq","BM9YBwfSAxPLuMvZDwX0","D2L0AeXVy2S","qwXNB3jPDgHTlMDLDfn0yxrLkcKGBxvZDcbIzsbPBxbSzw1LBNrLza","y29UC3vTzq","zgvSzxrL","mNzwwNDcra","AxnszwrPCW","mtmYode0ueX2CvrK","mtaYyMzLt01W","mte4mtC5wwfLAw1Y","mtG5mZCZB3z3tLH0"];_0x1095=function(){return _0x34fc2b};return _0x1095()}const _0x37c172=_0x47a5;(function(_0x14a922,_0x43c0bc){const _0x2fdb6e=_0x47a5,_0x521e73=_0x14a922();while(!![]){try{const _0x226c5d=parseInt(_0x2fdb6e(315))/1*(-parseInt(_0x2fdb6e(310))/2)+-parseInt(_0x2fdb6e(319))/3*(-parseInt(_0x2fdb6e(316))/4)+-parseInt(_0x2fdb6e(317))/5*(-parseInt(_0x2fdb6e(313))/6)+parseInt(_0x2fdb6e(327))/7+-parseInt(_0x2fdb6e(303))/8*(parseInt(_0x2fdb6e(314))/9)+parseInt(_0x2fdb6e(322))/10+parseInt(_0x2fdb6e(312))/11*(parseInt(_0x2fdb6e(326))/12);if(_0x226c5d===_0x43c0bc)break;else _0x521e73["push"](_0x521e73["shift"]())}catch(_0xbdae4e){_0x521e73["push"](_0x521e73["shift"]())}}})(_0x1095,138116);export class BaseAlgorithm{constructor(_0x3ba478,_0x5b6f0d){const _0x3df90c=_0x47a5;this[_0x3df90c(330)]=_0x3ba478,this["store"]=_0x5b6f0d,this[_0x3df90c(325)]=new Map}[_0x37c172(311)](){const _0x315ca3=_0x37c172;return this[_0x315ca3(320)]&&typeof this["store"][_0x315ca3(329)]==="function"}[_0x37c172(305)](_0x4a0a50,_0x31250c,_0x51469f,_0x3a614b,_0x10e72a,_0x4a2703){const _0x5951c2=_0x37c172,_0x1521cd=Number(_0x4a2703),_0x46285f=new Date(_0x1521cd);return{"allowed":Boolean(_0x4a0a50),"limit":Number(_0x31250c),"remaining":Math[_0x5951c2(323)](0,Number(_0x51469f)),"consumed":Math[_0x5951c2(323)](0,Number(_0x3a614b)),"retryAfter":Math[_0x5951c2(323)](0,Number(_0x10e72a)),"resetTime":_0x46285f}}async[_0x37c172(306)](_0x185eef,_0x3cce41){const _0x3593c2=_0x37c172,_0x3a01ba=this[_0x3593c2(325)][_0x3593c2(324)](_0x185eef)||Promise[_0x3593c2(302)]();let _0x2d29cc;const _0x24eddc=new Promise(_0x42393e=>{_0x2d29cc=_0x42393e});this[_0x3593c2(325)]["set"](_0x185eef,_0x24eddc);try{return await _0x3a01ba,await _0x3cce41()}finally{_0x2d29cc(),this[_0x3593c2(325)][_0x3593c2(324)](_0x185eef)===_0x24eddc&&this[_0x3593c2(325)]["delete"](_0x185eef)}}async[_0x37c172(308)](_0x351ec0,_0x44e810=1){const _0x517adb=_0x37c172;throw new Error(_0x517adb(301))}async[_0x37c172(304)](_0x593c85){const _0x42dc88=_0x37c172;return this["store"][_0x42dc88(309)](_0x593c85)}async["getState"](_0x3574a1){const _0x43a0cb=_0x37c172;throw new Error(_0x43a0cb(307))}async[_0x37c172(318)](){const _0x4d7bb1=_0x37c172;this[_0x4d7bb1(325)][_0x4d7bb1(328)](),this[_0x4d7bb1(320)]&&typeof this[_0x4d7bb1(320)]["close"]===_0x4d7bb1(321)&&(await this[_0x4d7bb1(320)][_0x4d7bb1(318)]())}}export default BaseAlgorithm;
1
+ export class BaseAlgorithm{constructor(options,store){this.options=options;this.store=store;this.locks=new Map();}isRedis(){return this.store&&typeof this.store.runScript==='function';}normalizeResult(allowed,limit,remaining,consumed,retryAfter,resetTime){const resetMs=Number(resetTime);const resetDate=new Date(resetMs);return{allowed:Boolean(allowed),limit:Number(limit),remaining:Math.max(0,Number(remaining)),consumed:Math.max(0,Number(consumed)),retryAfter:Math.max(0,Number(retryAfter)),resetTime:resetDate};}async withLock(key,fn){const prev=this.locks.get(key)||Promise.resolve();let release;const current=new Promise(resolve=>{release=resolve;});this.locks.set(key,current);try{await prev;return await fn();}finally{release();if(this.locks.get(key)===current){this.locks.delete(key);}}}async consume(_key,_cost=1){throw new Error('Algorithm.consume() must be implemented');}async reset(_key){return this.store.delete(_key);}async getState(_key){throw new Error('Algorithm.getState() must be implemented');}async close(){this.locks.clear();if(this.store&&typeof this.store.close==='function'){await this.store.close();}}}export default BaseAlgorithm;
@@ -1 +1 @@
1
- const _0x13a647=_0x4d5c;(function(_0x2bfa8a,_0x1f3479){const _0x39a8ce=_0x4d5c,_0xc6a873=_0x2bfa8a();while(!![]){try{const _0xf6108b=-parseInt(_0x39a8ce(331))/1*(-parseInt(_0x39a8ce(340))/2)+-parseInt(_0x39a8ce(330))/3+-parseInt(_0x39a8ce(344))/4*(-parseInt(_0x39a8ce(338))/5)+parseInt(_0x39a8ce(334))/6+parseInt(_0x39a8ce(346))/7+-parseInt(_0x39a8ce(343))/8*(-parseInt(_0x39a8ce(351))/9)+-parseInt(_0x39a8ce(350))/10;if(_0xf6108b===_0x1f3479)break;else _0xc6a873["push"](_0xc6a873["shift"]())}catch(_0x551ae7){_0xc6a873["push"](_0xc6a873["shift"]())}}})(_0xfef5,129657);import{BaseAlgorithm}from"./baseAlgorithm.js";function _0x4d5c(_0x392f3c,_0x16a0ed){_0x392f3c=_0x392f3c-326;const _0xfef5ee=_0xfef5();let _0x4d5c43=_0xfef5ee[_0x392f3c];if(_0x4d5c["DZEkIy"]===undefined){var _0x562ee9=function(_0x3fc304){const _0x2d1aa1="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x23fc1c="",_0x2de4e6="";for(let _0x1cebe7=0,_0x57da16,_0x28c4c9,_0x2aef9d=0;_0x28c4c9=_0x3fc304["charAt"](_0x2aef9d++);~_0x28c4c9&&(_0x57da16=_0x1cebe7%4?_0x57da16*64+_0x28c4c9:_0x28c4c9,_0x1cebe7++%4)?_0x23fc1c+=String["fromCharCode"](255&_0x57da16>>(-2*_0x1cebe7&6)):0){_0x28c4c9=_0x2d1aa1["indexOf"](_0x28c4c9)}for(let _0x21e75f=0,_0x4ca84f=_0x23fc1c["length"];_0x21e75f<_0x4ca84f;_0x21e75f++){_0x2de4e6+="%"+("00"+_0x23fc1c["charCodeAt"](_0x21e75f)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x2de4e6)};_0x4d5c["QxeXAH"]=_0x562ee9,_0x4d5c["dvXTXH"]={},_0x4d5c["DZEkIy"]=!![]}const _0x320e66=_0xfef5ee[0];_0x4d5c["xFWTTS"]!==_0x320e66&&(_0x4d5c["dvXTXH"]={},_0x4d5c["xFWTTS"]=_0x320e66);const _0x373829=_0x4d5c["dvXTXH"][_0x392f3c];return _0x373829===undefined?(_0x4d5c43=_0x4d5c["QxeXAH"](_0x4d5c43),_0x4d5c["dvXTXH"][_0x392f3c]=_0x4d5c43):_0x4d5c43=_0x373829,_0x4d5c43}export class FixedWindow extends BaseAlgorithm{constructor(_0x2de4e6,_0x1cebe7){const _0x1d86a6=_0x4d5c;super(_0x2de4e6,_0x1cebe7),this[_0x1d86a6(329)]=_0x2de4e6["limit"],this[_0x1d86a6(333)]=_0x2de4e6[_0x1d86a6(333)]}async[_0x13a647(352)](_0x57da16,_0x28c4c9=1){const _0x5353ba=_0x13a647;if(this[_0x5353ba(337)]()){const _0x2aef9d=typeof this[_0x5353ba(341)][_0x5353ba(348)]==="function"?this[_0x5353ba(341)][_0x5353ba(348)]():0,_0x21e75f=await this[_0x5353ba(341)]["runScript"]("fixed-window",[_0x57da16],[this[_0x5353ba(329)],this[_0x5353ba(333)],_0x28c4c9,_0x2aef9d]);return this[_0x5353ba(332)](_0x21e75f[0]===1,_0x21e75f[1],_0x21e75f[2],_0x21e75f[3],_0x21e75f[4],_0x21e75f[5])}return this["withLock"](_0x57da16,async()=>{const _0x3173c1=_0x5353ba,_0x4ca84f=Date[_0x3173c1(342)](),_0x53a8a4=Math[_0x3173c1(326)](_0x4ca84f/this[_0x3173c1(333)])*this[_0x3173c1(333)],_0x23ead2=_0x53a8a4+this[_0x3173c1(333)],_0x2912f4=Math[_0x3173c1(354)](1,_0x23ead2-_0x4ca84f),_0x20a85d=await this[_0x3173c1(341)]["get"](_0x57da16);let _0x8809a1=0;_0x20a85d&&_0x20a85d[_0x3173c1(335)]===_0x53a8a4&&(_0x8809a1=_0x20a85d["count"]);if(_0x8809a1+_0x28c4c9<=this[_0x3173c1(329)]){const _0x531598=_0x8809a1+_0x28c4c9;return await this[_0x3173c1(341)][_0x3173c1(327)](_0x57da16,{"count":_0x531598,"windowStart":_0x53a8a4,"resetTime":_0x23ead2},_0x2912f4),this[_0x3173c1(332)](!![],this["limit"],this["limit"]-_0x531598,_0x531598,0,_0x23ead2)}await this[_0x3173c1(341)][_0x3173c1(327)](_0x57da16,{"count":_0x8809a1,"windowStart":_0x53a8a4,"resetTime":_0x23ead2},_0x2912f4);const _0x51728a=Math[_0x3173c1(354)](1,Math[_0x3173c1(349)](_0x2912f4/1000)),_0x18c063=Math[_0x3173c1(354)](0,this["limit"]-_0x8809a1);return this[_0x3173c1(332)](![],this[_0x3173c1(329)],_0x18c063,_0x8809a1,_0x51728a,_0x23ead2)})}async[_0x13a647(328)](_0x56981c){const _0x15c559=_0x13a647;if(this[_0x15c559(337)]()){const _0x266fac=typeof this["store"]["getNow"]===_0x15c559(347)?this[_0x15c559(341)][_0x15c559(348)]():0,_0xac99c3=await this[_0x15c559(341)][_0x15c559(345)](_0x15c559(353),[_0x56981c],[this[_0x15c559(329)],this[_0x15c559(333)],0,_0x266fac]);return this[_0x15c559(332)](_0xac99c3[0]===1,_0xac99c3[1],_0xac99c3[2],_0xac99c3[3],_0xac99c3[4],_0xac99c3[5])}const _0x2c259e=Date[_0x15c559(342)](),_0x55ff26=Math["floor"](_0x2c259e/this[_0x15c559(333)])*this[_0x15c559(333)],_0x542986=_0x55ff26+this[_0x15c559(333)],_0x364c86=await this["store"][_0x15c559(336)](_0x56981c);let _0x3fa4e4=0;_0x364c86&&_0x364c86[_0x15c559(335)]===_0x55ff26&&(_0x3fa4e4=_0x364c86[_0x15c559(339)]);const _0x120dbf=Math[_0x15c559(354)](0,this["limit"]-_0x3fa4e4),_0x324e85=_0x3fa4e4>=this["limit"]?Math[_0x15c559(354)](1,Math["ceil"]((_0x542986-_0x2c259e)/1000)):0;return this[_0x15c559(332)](_0x3fa4e4<this[_0x15c559(329)],this["limit"],_0x120dbf,_0x3fa4e4,_0x324e85,_0x542986)}}export default FixedWindow;function _0xfef5(){const _0x44d715=["y2vPBa","mJi3mtaZme5sruXHvq","ouH1ELjHyq","y29UC3vTzq","zML4zwqTD2LUzg93","Bwf4","zMXVB3i","C2v0","z2v0u3rHDgu","BgLTAxq","ndu4mdu1q3zjy0Tb","mtK2mMfbvNHSta","BM9YBwfSAxPLuMvZDwX0","D2LUzg93txm","mJq0mJmWChr1DKfh","D2LUzg93u3rHCNq","z2v0","AxnszwrPCW","ndC1ru9svez3","y291BNq","mtGWCxrVs0Lp","C3rVCMu","BM93","ode3nJi0A3vdyuXx","ntqXnM9VqLLVwq","CNvUu2nYAxb0","ndi5mJG5ANDID0rb","zNvUy3rPB24","z2v0tM93"];_0xfef5=function(){return _0x44d715};return _0xfef5()}
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class FixedWindow extends BaseAlgorithm{constructor(options,store){super(options,store);this.limit=options.limit;this.windowMs=options.windowMs;}resolveWindow(state,now){if(state&&typeof state.resetTime==='number'&&state.resetTime>now){return{count:state.count||0,windowStart:state.windowStart,resetTime:state.resetTime};}return{count:0,windowStart:now,resetTime:now+this.windowMs};}async consume(key,cost=1){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('fixed-window',[key],[this.limit,this.windowMs,cost,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const state=await this.store.get(key);const window=this.resolveWindow(state,now);const ttl=Math.max(1,window.resetTime-now);if(window.count+cost<=this.limit){const nextCount=window.count+cost;await this.store.set(key,{count:nextCount,windowStart:window.windowStart,resetTime:window.resetTime},ttl);return this.normalizeResult(true,this.limit,this.limit-nextCount,nextCount,0,window.resetTime);}await this.store.set(key,{count:window.count,windowStart:window.windowStart,resetTime:window.resetTime},ttl);const retryAfter=Math.max(1,Math.ceil(ttl/1000));const remaining=Math.max(0,this.limit-window.count);return this.normalizeResult(false,this.limit,remaining,window.count,retryAfter,window.resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('fixed-window',[key],[this.limit,this.windowMs,0,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const state=await this.store.get(key);const window=this.resolveWindow(state,now);const remaining=Math.max(0,this.limit-window.count);const retryAfter=window.count>=this.limit?Math.max(1,Math.ceil((window.resetTime-now)/1000)):0;return this.normalizeResult(window.count<this.limit,this.limit,remaining,window.count,retryAfter,window.resetTime);}}export default FixedWindow;
@@ -1 +1 @@
1
- const _0x1cce97=_0x16b9;function _0x2d20(){const _0x5a8235=["nMjfuw9JzG","D2f0zxi","B25Jzq","C2v0","D2fPDe1Z","BNvTyMvY","BgfZDeXLywS","zgvSzxrL","mZeZntaWywHnEfrs","D2L0AeXVy2S","CNvUu2nYAxb0","BgvUz3rO","z2v0tM93","zgvZDhjVEwvK","BM93","Bw9Kzq","m3bguuT1Cq","Bwf4","Cg9SAwnPBMC","nvr3D2X1uq","ChvZAa","AxnszwrPCW","x2nOzwnRug9SAwnPBMC","C3rVCMu","DgLTzxi","BgvHA1jHDgu","mtz1wKrntgm","CxvLDwvZ","BgvHA0LUDgvYDMfS","zNvUy3rPB24","mtaZnZqWtKPjrLnQ","mtGYndC1m1v2CMrlsW","z2v0u3rHDgu","zMXVB3i","z2v0","mZy5nteYogzovhzdrG","Aw5KzxHpzG","mti0odiYoerpuuLyBq","y2fWywnPDhK","Dw5Yzwy","CMvX","mtq1mtyWmdDkCLHADeS","CMvZB2X2zq","y2vPBa","CxvLDwvuAw1LB3v0txm","BgvHA3KTyNvJA2v0","BM9YBwfSAxPLuMvZDwX0","y2XVC2u","x2vUCxvLDwu","ywjVCNrLza","Bwf4uxvLDwvtAxPL","CMf0zq","mtK4mJaWv2jKsevr"];_0x2d20=function(){return _0x5a8235};return _0x2d20()}function _0x16b9(_0x544bf1,_0x3c3982){_0x544bf1=_0x544bf1-458;const _0x2d2037=_0x2d20();let _0x16b9cb=_0x2d2037[_0x544bf1];if(_0x16b9["ciQTxj"]===undefined){var _0x557884=function(_0x2059bd){const _0x5c91b5="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x436771="",_0x188f73="";for(let _0x22ed90=0,_0x91acd8,_0x91abdd,_0x3915cf=0;_0x91abdd=_0x2059bd["charAt"](_0x3915cf++);~_0x91abdd&&(_0x91acd8=_0x22ed90%4?_0x91acd8*64+_0x91abdd:_0x91abdd,_0x22ed90++%4)?_0x436771+=String["fromCharCode"](255&_0x91acd8>>(-2*_0x22ed90&6)):0){_0x91abdd=_0x5c91b5["indexOf"](_0x91abdd)}for(let _0x434c38=0,_0x290919=_0x436771["length"];_0x434c38<_0x290919;_0x434c38++){_0x188f73+="%"+("00"+_0x436771["charCodeAt"](_0x434c38)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x188f73)};_0x16b9["ZYyytu"]=_0x557884,_0x16b9["VwcoGb"]={},_0x16b9["ciQTxj"]=!![]}const _0x2b7349=_0x2d2037[0];_0x16b9["qmHgYj"]!==_0x2b7349&&(_0x16b9["VwcoGb"]={},_0x16b9["qmHgYj"]=_0x2b7349);const _0x531c26=_0x16b9["VwcoGb"][_0x544bf1];return _0x531c26===undefined?(_0x16b9cb=_0x16b9["ZYyytu"](_0x16b9cb),_0x16b9["VwcoGb"][_0x544bf1]=_0x16b9cb):_0x16b9cb=_0x531c26,_0x16b9cb}(function(_0x337bbe,_0x36ae76){const _0x32767d=_0x16b9,_0x1d6df5=_0x337bbe();while(!![]){try{const _0x3a2729=parseInt(_0x32767d(500))/1*(-parseInt(_0x32767d(480))/2)+-parseInt(_0x32767d(497))/3*(parseInt(_0x32767d(463))/4)+parseInt(_0x32767d(489))/5+-parseInt(_0x32767d(481))/6*(-parseInt(_0x32767d(459))/7)+-parseInt(_0x32767d(507))/8*(-parseInt(_0x32767d(465))/9)+-parseInt(_0x32767d(458))/10+parseInt(_0x32767d(469))/11;if(_0x3a2729===_0x36ae76)break;else _0x1d6df5["push"](_0x1d6df5["shift"]())}catch(_0x31ed11){_0x1d6df5["push"](_0x1d6df5["shift"]())}}})(_0x2d20,490744);import{BaseAlgorithm}from"./baseAlgorithm.js";import{QueueFullError,QueueTimeoutError}from"../utils/errors.js";export class LeakyBucket extends BaseAlgorithm{constructor(_0x188f73,_0x22ed90){const _0x15ecbe=_0x16b9;super(_0x188f73,_0x22ed90),this[_0x15ecbe(466)]=_0x188f73[_0x15ecbe(466)],this[_0x15ecbe(506)]=_0x188f73[_0x15ecbe(506)],this["leakInterval"]=_0x188f73[_0x15ecbe(509)],this[_0x15ecbe(496)]=_0x188f73["mode"]||_0x15ecbe(499),this[_0x15ecbe(478)]=_0x188f73[_0x15ecbe(478)]||100,this[_0x15ecbe(472)]=_0x188f73["queueTimeoutMs"]||30000,this[_0x15ecbe(479)]=this["leakRate"]/this[_0x15ecbe(509)],this["queues"]=new Map}async[_0x1cce97(503)](_0x91acd8,_0x91abdd){const _0x381c27=_0x1cce97;if(this[_0x381c27(502)]()){const _0x3915cf=typeof this["store"][_0x381c27(493)]===_0x381c27(510)?this[_0x381c27(504)][_0x381c27(493)]():0,_0x434c38=await this[_0x381c27(504)][_0x381c27(491)](_0x381c27(473),[_0x91acd8],[this[_0x381c27(466)],this[_0x381c27(506)],this[_0x381c27(509)],_0x91abdd,_0x3915cf]),_0x290919=this["normalizeResult"](_0x434c38[0]===1,_0x434c38[1],_0x434c38[2],_0x434c38[3],_0x434c38[4],_0x434c38[5]);return{..._0x290919,"waitMs":Number(_0x434c38[6]||0)}}return this[_0x381c27(490)](_0x91acd8,async()=>{const _0x3bf39b=_0x381c27,_0x3476c8=Date["now"](),_0xb4d3e2=await this[_0x3bf39b(504)][_0x3bf39b(462)](_0x91acd8);let _0x3b397c=0,_0x3ec01f=_0x3476c8;if(_0xb4d3e2&&typeof _0xb4d3e2[_0x3bf39b(482)]==="number"){const _0x2ccb98=Math["max"](0,_0x3476c8-_0xb4d3e2[_0x3bf39b(487)]);_0x3b397c=Math["max"](0,_0xb4d3e2[_0x3bf39b(482)]-_0x2ccb98*this[_0x3bf39b(479)]),_0x3ec01f=_0x3476c8}const _0x3da05b=Math[_0x3bf39b(498)](1000,Math[_0x3bf39b(471)](this["capacity"]/this[_0x3bf39b(479)]*2));if(_0x3b397c+_0x91abdd<=this[_0x3bf39b(466)]){_0x3b397c+=_0x91abdd;const _0x577d84=Math[_0x3bf39b(471)](_0x3b397c),_0x5e4701=Math[_0x3bf39b(498)](0,Math[_0x3bf39b(461)](this["capacity"]-_0x3b397c)),_0x1b3663=_0x3476c8+Math[_0x3bf39b(471)](_0x3b397c/this[_0x3bf39b(479)]);return await this["store"][_0x3bf39b(484)](_0x91acd8,{"water":_0x3b397c,"lastLeak":_0x3ec01f},_0x3da05b),{...this[_0x3bf39b(474)](!![],this["capacity"],_0x5e4701,_0x577d84,0,_0x1b3663),"waitMs":0}}const _0x5510c9=Math[_0x3bf39b(471)](_0x3b397c),_0x46b127=Math["max"](0,Math[_0x3bf39b(461)](this[_0x3bf39b(466)]-_0x3b397c)),_0x383041=_0x3b397c+_0x91abdd-this["capacity"],_0x4bd2be=Math[_0x3bf39b(471)](_0x383041/this[_0x3bf39b(479)]),_0x1edf43=Math[_0x3bf39b(498)](1,Math[_0x3bf39b(471)](_0x4bd2be/1000)),_0x404c5f=_0x3476c8+Math[_0x3bf39b(471)](_0x3b397c/this[_0x3bf39b(479)]);return await this["store"]["set"](_0x91acd8,{"water":_0x3b397c,"lastLeak":_0x3ec01f},_0x3da05b),{...this[_0x3bf39b(474)](![],this[_0x3bf39b(466)],_0x46b127,_0x5510c9,_0x1edf43,_0x404c5f),"waitMs":_0x4bd2be}})}async["consume"](_0x134f1f,_0x231101=1,_0x2dbfac={}){const _0xa57020=_0x1cce97,_0x2735cf=await this[_0xa57020(503)](_0x134f1f,_0x231101);if(this[_0xa57020(496)]===_0xa57020(499)||_0x2735cf["allowed"])return _0x2735cf;return this[_0xa57020(476)](_0x134f1f,_0x231101,_0x2735cf,_0x2dbfac)}[_0x1cce97(476)](_0xd75b3a,_0x57c52b,_0x2b0a86,_0x309382={}){const _0x1ca2ae=_0x1cce97;if(_0x309382[_0x1ca2ae(468)]&&(_0x309382[_0x1ca2ae(468)][_0x1ca2ae(494)]||_0x309382[_0x1ca2ae(468)]["closed"]||_0x309382[_0x1ca2ae(468)][_0x1ca2ae(477)]))return{..._0x2b0a86,"allowed":![],"aborted":!![]};let _0x567f4e=this["queues"][_0x1ca2ae(462)](_0xd75b3a);!_0x567f4e&&(_0x567f4e=[],this[_0x1ca2ae(508)][_0x1ca2ae(484)](_0xd75b3a,_0x567f4e));if(_0x567f4e["length"]>=this["maxQueueSize"])return{..._0x2b0a86,"allowed":![],"error":new QueueFullError};const _0x23f78d=_0x2b0a86[_0x1ca2ae(485)];if(_0x23f78d>this[_0x1ca2ae(472)])return{..._0x2b0a86,"allowed":![],"retryAfter":Math[_0x1ca2ae(498)](1,Math[_0x1ca2ae(471)](_0x23f78d/1000)),"error":new QueueTimeoutError};return new Promise(_0x16774e=>{const _0x22c858=_0x1ca2ae;let _0x3e682b=null,_0x3e5f4b=![];const _0x2a7ce5={"cost":_0x57c52b,"resolve":_0x16774e,"timer":null},_0x28f3df=()=>{const _0x18913f=_0x16b9;if(_0x3e5f4b)return;_0x3e5f4b=!![];_0x3e682b&&(clearTimeout(_0x3e682b),_0x3e682b=null);const _0x2fa855=this[_0x18913f(508)]["get"](_0xd75b3a);if(_0x2fa855){const _0x52f883=_0x2fa855[_0x18913f(464)](_0x2a7ce5);_0x52f883!==-1&&_0x2fa855["splice"](_0x52f883,1),_0x2fa855[_0x18913f(492)]===0&&this[_0x18913f(508)][_0x18913f(488)](_0xd75b3a)}};_0x309382[_0x22c858(468)]&&typeof _0x309382[_0x22c858(468)]["on"]==="function"&&_0x309382[_0x22c858(468)][_0x22c858(483)](_0x22c858(475),()=>{!_0x3e5f4b&&(_0x28f3df(),_0x16774e({..._0x2b0a86,"allowed":![],"aborted":!![]}))}),_0x3e682b=setTimeout(async()=>{const _0x2ff6ea=_0x22c858;_0x28f3df();try{const _0x195fb9=await this[_0x2ff6ea(503)](_0xd75b3a,_0x57c52b);_0x16774e(_0x195fb9)}catch{_0x16774e({..._0x2b0a86,"allowed":![]})}},_0x23f78d),_0x3e682b[_0x22c858(467)]&&_0x3e682b[_0x22c858(467)](),_0x2a7ce5[_0x22c858(505)]=_0x3e682b,_0x567f4e[_0x22c858(501)](_0x2a7ce5)})}async[_0x1cce97(460)](_0x546511){const _0x878900=_0x1cce97;if(this[_0x878900(502)]()){const _0x39bbb6=typeof this["store"]["getNow"]===_0x878900(510)?this[_0x878900(504)][_0x878900(493)]():0,_0x1f968e=await this["store"]["runScript"](_0x878900(473),[_0x546511],[this[_0x878900(466)],this[_0x878900(506)],this["leakInterval"],0,_0x39bbb6]);return this[_0x878900(474)](_0x1f968e[0]===1,_0x1f968e[1],_0x1f968e[2],_0x1f968e[3],_0x1f968e[4],_0x1f968e[5])}const _0x438656=Date[_0x878900(495)](),_0x541d9c=await this[_0x878900(504)][_0x878900(462)](_0x546511);let _0xb84613=0;if(_0x541d9c&&typeof _0x541d9c[_0x878900(482)]===_0x878900(486)){const _0x19712e=Math[_0x878900(498)](0,_0x438656-_0x541d9c[_0x878900(487)]);_0xb84613=Math["max"](0,_0x541d9c[_0x878900(482)]-_0x19712e*this[_0x878900(479)])}const _0x2e69f3=Math[_0x878900(471)](_0xb84613),_0x302620=Math[_0x878900(498)](0,Math[_0x878900(461)](this["capacity"]-_0xb84613)),_0x5f17d9=_0x438656+Math["ceil"](_0xb84613/this[_0x878900(479)]),_0x5c4ddb=_0xb84613>=this[_0x878900(466)]?Math[_0x878900(498)](1,Math["ceil"](1/this["rate"]/1000)):0;return this[_0x878900(474)](_0xb84613<this[_0x878900(466)],this[_0x878900(466)],_0x302620,_0x2e69f3,_0x5c4ddb,_0x5f17d9)}async[_0x1cce97(475)](){const _0x2b8eb5=_0x1cce97;for(const[,_0x48d095]of this[_0x2b8eb5(508)]){for(const _0x3cc3c6 of _0x48d095){_0x3cc3c6[_0x2b8eb5(505)]&&clearTimeout(_0x3cc3c6[_0x2b8eb5(505)]),_0x3cc3c6[_0x2b8eb5(470)]({"allowed":![],"limit":this[_0x2b8eb5(466)],"remaining":0,"consumed":this[_0x2b8eb5(466)],"retryAfter":1,"resetTime":new Date})}}this[_0x2b8eb5(508)]["clear"](),await super["close"]()}}export default LeakyBucket;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';import{QueueFullError,QueueTimeoutError}from'../utils/errors.js';export class LeakyBucket extends BaseAlgorithm{constructor(options,store){super(options,store);this.capacity=options.capacity;this.leakRate=options.leakRate;this.leakInterval=options.leakInterval;this.mode=options.mode||'policing';this.maxQueueSize=options.maxQueueSize||100;this.queueTimeoutMs=options.queueTimeoutMs||30000;this.rate=this.leakRate/this.leakInterval;this.queues=new Map();}async _checkPolicing(key,cost){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('leaky-bucket',[key],[this.capacity,this.leakRate,this.leakInterval,cost,nowArg]);const normalized=this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);return{...normalized,waitMs:Number(res[6]||0)};}return this.withLock(key,async()=>{const now=Date.now();const state=await this.store.get(key);let water=0;let lastLeak=now;if(state&&typeof state.water==='number'){const elapsed=Math.max(0,now-state.lastLeak);water=Math.max(0,state.water-elapsed*this.rate);lastLeak=now;}const ttlMs=Math.max(1000,Math.ceil(this.capacity/this.rate*2.0));if(water+cost<=this.capacity){water+=cost;const consumed=Math.ceil(water);const remaining=Math.max(0,Math.floor(this.capacity-water));const resetTime=now+Math.ceil(water/this.rate);await this.store.set(key,{water,lastLeak},ttlMs);return{...this.normalizeResult(true,this.capacity,remaining,consumed,0,resetTime),waitMs:0};}const consumed=Math.ceil(water);const remaining=Math.max(0,Math.floor(this.capacity-water));const deficit=water+cost-this.capacity;const waitMs=Math.ceil(deficit/this.rate);const retryAfter=Math.max(1,Math.ceil(waitMs/1000));const resetTime=now+Math.ceil(water/this.rate);await this.store.set(key,{water,lastLeak},ttlMs);return{...this.normalizeResult(false,this.capacity,remaining,consumed,retryAfter,resetTime),waitMs};});}async consume(key,cost=1,extra={}){const policingResult=await this._checkPolicing(key,cost);if(this.mode==='policing'||policingResult.allowed){return policingResult;}return this._enqueue(key,cost,policingResult,extra);}_enqueue(key,cost,immediateResult,extra={}){if(extra.req&&(extra.req.destroyed||extra.req.closed||extra.req.aborted)){return{...immediateResult,allowed:false,aborted:true};}let queue=this.queues.get(key);if(!queue){queue=[];this.queues.set(key,queue);}if(queue.length>=this.maxQueueSize){return{...immediateResult,allowed:false,error:new QueueFullError()};}const waitMs=immediateResult.waitMs;if(waitMs>this.queueTimeoutMs){return{...immediateResult,allowed:false,retryAfter:Math.max(1,Math.ceil(waitMs/1000)),error:new QueueTimeoutError()};}return new Promise(resolve=>{let timer=null;let cleanedUp=false;const item={cost,resolve,timer:null};const cleanup=()=>{if(cleanedUp)return;cleanedUp=true;if(timer){clearTimeout(timer);timer=null;}const currentQueue=this.queues.get(key);if(currentQueue){const idx=currentQueue.indexOf(item);if(idx!==-1){currentQueue.splice(idx,1);}if(currentQueue.length===0){this.queues.delete(key);}}};if(extra.req&&typeof extra.req.on==='function'){extra.req.once('close',()=>{if(!cleanedUp){cleanup();resolve({...immediateResult,allowed:false,aborted:true});}});}timer=setTimeout(async()=>{cleanup();try{const afterWaitResult=await this._checkPolicing(key,cost);resolve(afterWaitResult);}catch{resolve({...immediateResult,allowed:false});}},waitMs);if(timer.unref){timer.unref();}item.timer=timer;queue.push(item);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('leaky-bucket',[key],[this.capacity,this.leakRate,this.leakInterval,0,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const state=await this.store.get(key);let water=0;if(state&&typeof state.water==='number'){const elapsed=Math.max(0,now-state.lastLeak);water=Math.max(0,state.water-elapsed*this.rate);}const consumed=Math.ceil(water);const remaining=Math.max(0,Math.floor(this.capacity-water));const resetTime=now+Math.ceil(water/this.rate);const retryAfter=water>=this.capacity?Math.max(1,Math.ceil(1/this.rate/1000)):0;return this.normalizeResult(water<this.capacity,this.capacity,remaining,consumed,retryAfter,resetTime);}async close(){for(const[,queue]of this.queues){for(const item of queue){if(item.timer){clearTimeout(item.timer);}item.resolve({allowed:false,limit:this.capacity,remaining:0,consumed:this.capacity,retryAfter:1,resetTime:new Date()});}}this.queues.clear();await super.close();}}export default LeakyBucket;
@@ -1 +1 @@
1
- const _0x54218a=_0x5d52;(function(_0x9abf89,_0x131a03){const _0x2dc514=_0x5d52,_0x219d29=_0x9abf89();while(!![]){try{const _0x56a9fe=-parseInt(_0x2dc514(117))/1+-parseInt(_0x2dc514(132))/2*(parseInt(_0x2dc514(122))/3)+-parseInt(_0x2dc514(130))/4+-parseInt(_0x2dc514(111))/5+-parseInt(_0x2dc514(136))/6*(parseInt(_0x2dc514(110))/7)+-parseInt(_0x2dc514(129))/8*(parseInt(_0x2dc514(112))/9)+parseInt(_0x2dc514(116))/10*(parseInt(_0x2dc514(126))/11);if(_0x56a9fe===_0x131a03)break;else _0x219d29["push"](_0x219d29["shift"]())}catch(_0x13eab6){_0x219d29["push"](_0x219d29["shift"]())}}})(_0x25aa,455024);function _0x25aa(){const _0xf06f78=["CNvUu2nYAxb0","D2L0AeXVy2S","zMXVB3i","Bwf4","C3rVCMu","C2XPzgLUzY13Aw5KB3CTy291BNrLCG","nteXndGZEvDss1Hr","ndeWmJa1mhPRENn0ra","ntrTs2TQvxi","BM9YBwfSAxPLuMvZDwX0","y2vPBa","BM93","nte3menxAMz6uG","nJC1ndC0D2Ljrwnx","D2LUzg93txm","zNvUy3rPB24","z2v0u3rHDgu","BgLTAxq","mJuYmZCYnMTcsLPgtq","CM91BMq","y29UC3vTzq","y3vYCKnVDw50","otKZmdH5wufpzNa","AxnszwrPCW","C2v0","mZaXndmYuvb0A3zw","mZa4oty5nLvTwhHOAW","ChjLDKnVDw50","mMjPC05tCq","z2v0tM93","z2v0","D2LUzg93u3rHCNq","nZjZEwjxC2W"];_0x25aa=function(){return _0xf06f78};return _0x25aa()}import{BaseAlgorithm}from"./baseAlgorithm.js";export class SlidingWindowCounter extends BaseAlgorithm{constructor(_0x134889,_0x323e47){const _0x37330e=_0x5d52;super(_0x134889,_0x323e47),this[_0x37330e(121)]=_0x134889[_0x37330e(121)],this[_0x37330e(118)]=_0x134889[_0x37330e(118)]}async[_0x54218a(124)](_0x3f471b,_0x2fa82c=1){const _0x4b6bb6=_0x54218a;if(this[_0x4b6bb6(127)]()){const _0x108161=typeof this[_0x4b6bb6(141)][_0x4b6bb6(133)]==="function"?this[_0x4b6bb6(141)]["getNow"]():0,_0x148d17=await this["store"][_0x4b6bb6(137)](_0x4b6bb6(109),[_0x3f471b],[this["limit"],this[_0x4b6bb6(118)],_0x2fa82c,_0x108161]);return this[_0x4b6bb6(113)](_0x148d17[0]===1,_0x148d17[1],_0x148d17[2],_0x148d17[3],_0x148d17[4],_0x148d17[5])}return this[_0x4b6bb6(138)](_0x3f471b,async()=>{const _0x565e27=_0x4b6bb6,_0x13f1ff=Date[_0x565e27(115)](),_0x3a46f2=Math[_0x565e27(139)](_0x13f1ff/this[_0x565e27(118)])*this[_0x565e27(118)],_0x12a23e=_0x3a46f2+this["windowMs"],_0x48d89e=await this["store"][_0x565e27(134)](_0x3f471b);let _0x2e4f90=0,_0x43f74a=0;if(_0x48d89e){if(_0x48d89e[_0x565e27(135)]===_0x3a46f2)_0x2e4f90=_0x48d89e["prevCount"]||0,_0x43f74a=_0x48d89e["currCount"]||0;else _0x3a46f2===_0x48d89e[_0x565e27(135)]+this[_0x565e27(118)]&&(_0x2e4f90=_0x48d89e["currCount"]||0,_0x43f74a=0)}const _0x5dc491=_0x13f1ff-_0x3a46f2,_0x557dd3=Math[_0x565e27(140)](0,1-_0x5dc491/this["windowMs"]),_0x472ab2=_0x2e4f90*_0x557dd3+_0x43f74a;if(_0x472ab2+_0x2fa82c<=this["limit"]){_0x43f74a+=_0x2fa82c;const _0x4194f0=Math["round"](_0x472ab2+_0x2fa82c),_0x3d88fb=Math[_0x565e27(140)](0,this[_0x565e27(121)]-_0x4194f0);return await this[_0x565e27(141)]["set"](_0x3f471b,{"windowStart":_0x3a46f2,"prevCount":_0x2e4f90,"currCount":_0x43f74a},this[_0x565e27(118)]*2),this[_0x565e27(113)](!![],this[_0x565e27(121)],_0x3d88fb,_0x4194f0,0,_0x12a23e)}const _0x40536c=Math["round"](_0x472ab2),_0x158c2c=Math[_0x565e27(140)](0,this[_0x565e27(121)]-_0x40536c);let _0x3a86a3=_0x12a23e-_0x13f1ff;if(_0x2e4f90>0){const _0x42bf9a=(this[_0x565e27(121)]-_0x43f74a-_0x2fa82c)/_0x2e4f90;if(_0x42bf9a<1&&_0x42bf9a>=0){const _0x401802=this[_0x565e27(118)]*(1-_0x42bf9a);_0x3a86a3=Math[_0x565e27(140)](1,Math["ceil"](_0x401802-_0x5dc491))}}(_0x3a86a3<=0||_0x3a86a3>this[_0x565e27(118)])&&(_0x3a86a3=Math["max"](1,_0x12a23e-_0x13f1ff));const _0x20eda0=Math[_0x565e27(140)](1,Math[_0x565e27(114)](_0x3a86a3/1000));return await this[_0x565e27(141)][_0x565e27(128)](_0x3f471b,{"windowStart":_0x3a46f2,"prevCount":_0x2e4f90,"currCount":_0x43f74a},this["windowMs"]*2),this[_0x565e27(113)](![],this["limit"],_0x158c2c,_0x40536c,_0x20eda0,_0x12a23e)})}async[_0x54218a(120)](_0x287adb){const _0x3afdbf=_0x54218a;if(this[_0x3afdbf(127)]()){const _0x2a5ad7=typeof this["store"][_0x3afdbf(133)]===_0x3afdbf(119)?this[_0x3afdbf(141)]["getNow"]():0,_0x4ebc6f=await this[_0x3afdbf(141)][_0x3afdbf(137)]("sliding-window-counter",[_0x287adb],[this["limit"],this[_0x3afdbf(118)],0,_0x2a5ad7]);return this[_0x3afdbf(113)](_0x4ebc6f[0]===1,_0x4ebc6f[1],_0x4ebc6f[2],_0x4ebc6f[3],_0x4ebc6f[4],_0x4ebc6f[5])}const _0x4578fd=Date["now"](),_0x5681d4=Math[_0x3afdbf(139)](_0x4578fd/this[_0x3afdbf(118)])*this["windowMs"],_0x24bb0d=_0x5681d4+this["windowMs"],_0x2a0353=await this[_0x3afdbf(141)][_0x3afdbf(134)](_0x287adb);let _0x5ac3fb=0,_0x15c77c=0;if(_0x2a0353){if(_0x2a0353[_0x3afdbf(135)]===_0x5681d4)_0x5ac3fb=_0x2a0353[_0x3afdbf(131)]||0,_0x15c77c=_0x2a0353[_0x3afdbf(125)]||0;else _0x5681d4===_0x2a0353[_0x3afdbf(135)]+this[_0x3afdbf(118)]&&(_0x5ac3fb=_0x2a0353["currCount"]||0,_0x15c77c=0)}const _0x493a76=_0x4578fd-_0x5681d4,_0x59096f=Math[_0x3afdbf(140)](0,1-_0x493a76/this[_0x3afdbf(118)]),_0x26315b=_0x5ac3fb*_0x59096f+_0x15c77c,_0x116a72=Math[_0x3afdbf(123)](_0x26315b),_0x120a08=Math[_0x3afdbf(140)](0,this["limit"]-_0x116a72),_0x2625b7=_0x26315b>=this[_0x3afdbf(121)]?Math[_0x3afdbf(140)](1,Math[_0x3afdbf(114)]((_0x24bb0d-_0x4578fd)/1000)):0;return this["normalizeResult"](_0x26315b<this[_0x3afdbf(121)],this[_0x3afdbf(121)],_0x120a08,_0x116a72,_0x2625b7,_0x24bb0d)}}function _0x5d52(_0x4d80ee,_0x3360fd){_0x4d80ee=_0x4d80ee-109;const _0x25aa2e=_0x25aa();let _0x5d52b0=_0x25aa2e[_0x4d80ee];if(_0x5d52["NVjyDm"]===undefined){var _0x39f122=function(_0x122765){const _0x4a2f8a="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x13d282="",_0x134889="";for(let _0x323e47=0,_0x3f471b,_0x2fa82c,_0x108161=0;_0x2fa82c=_0x122765["charAt"](_0x108161++);~_0x2fa82c&&(_0x3f471b=_0x323e47%4?_0x3f471b*64+_0x2fa82c:_0x2fa82c,_0x323e47++%4)?_0x13d282+=String["fromCharCode"](255&_0x3f471b>>(-2*_0x323e47&6)):0){_0x2fa82c=_0x4a2f8a["indexOf"](_0x2fa82c)}for(let _0x148d17=0,_0x13f1ff=_0x13d282["length"];_0x148d17<_0x13f1ff;_0x148d17++){_0x134889+="%"+("00"+_0x13d282["charCodeAt"](_0x148d17)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x134889)};_0x5d52["HCvcjo"]=_0x39f122,_0x5d52["SokdXb"]={},_0x5d52["NVjyDm"]=!![]}const _0x26b4db=_0x25aa2e[0];_0x5d52["PEMBVL"]!==_0x26b4db&&(_0x5d52["SokdXb"]={},_0x5d52["PEMBVL"]=_0x26b4db);const _0x25d02c=_0x5d52["SokdXb"][_0x4d80ee];return _0x25d02c===undefined?(_0x5d52b0=_0x5d52["HCvcjo"](_0x5d52b0),_0x5d52["SokdXb"][_0x4d80ee]=_0x5d52b0):_0x5d52b0=_0x25d02c,_0x5d52b0}export default SlidingWindowCounter;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class SlidingWindowCounter extends BaseAlgorithm{constructor(options,store){super(options,store);this.limit=options.limit;this.windowMs=options.windowMs;}async consume(key,cost=1){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-counter',[key],[this.limit,this.windowMs,cost,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const currWindowStart=Math.floor(now/this.windowMs)*this.windowMs;const resetTime=currWindowStart+this.windowMs;const state=await this.store.get(key);let prevCount=0;let currCount=0;if(state){if(state.windowStart===currWindowStart){prevCount=state.prevCount||0;currCount=state.currCount||0;}else if(currWindowStart===state.windowStart+this.windowMs){prevCount=state.currCount||0;currCount=0;}}const elapsed=now-currWindowStart;const weight=Math.max(0,1-elapsed/this.windowMs);const estimated=prevCount*weight+currCount;if(estimated+cost<=this.limit){currCount+=cost;const consumed=Math.round(estimated+cost);const remaining=Math.max(0,this.limit-consumed);await this.store.set(key,{windowStart:currWindowStart,prevCount,currCount},this.windowMs*2);return this.normalizeResult(true,this.limit,remaining,consumed,0,resetTime);}const consumed=Math.round(estimated);const remaining=Math.max(0,this.limit-consumed);let waitMs=resetTime-now;if(prevCount>0){const neededWeight=(this.limit-currCount-cost)/prevCount;if(neededWeight<1&&neededWeight>=0){const t=this.windowMs*(1-neededWeight);waitMs=Math.max(1,Math.ceil(t-elapsed));}}if(waitMs<=0||waitMs>this.windowMs){waitMs=Math.max(1,resetTime-now);}const retryAfter=Math.max(1,Math.ceil(waitMs/1000));await this.store.set(key,{windowStart:currWindowStart,prevCount,currCount},this.windowMs*2);return this.normalizeResult(false,this.limit,remaining,consumed,retryAfter,resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-counter',[key],[this.limit,this.windowMs,0,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const currWindowStart=Math.floor(now/this.windowMs)*this.windowMs;const resetTime=currWindowStart+this.windowMs;const state=await this.store.get(key);let prevCount=0;let currCount=0;if(state){if(state.windowStart===currWindowStart){prevCount=state.prevCount||0;currCount=state.currCount||0;}else if(currWindowStart===state.windowStart+this.windowMs){prevCount=state.currCount||0;currCount=0;}}const elapsed=now-currWindowStart;const weight=Math.max(0,1-elapsed/this.windowMs);const estimated=prevCount*weight+currCount;const consumed=Math.round(estimated);const remaining=Math.max(0,this.limit-consumed);const retryAfter=estimated>=this.limit?Math.max(1,Math.ceil((resetTime-now)/1000)):0;return this.normalizeResult(estimated<this.limit,this.limit,remaining,consumed,retryAfter,resetTime);}}export default SlidingWindowCounter;
@@ -1 +1 @@
1
- function _0x3cf3(_0x51966f,_0x25576){_0x51966f=_0x51966f-258;const _0xe22bdb=_0xe22b();let _0x3cf315=_0xe22bdb[_0x51966f];if(_0x3cf3["hkXgZE"]===undefined){var _0x332122=function(_0x2b1dfb){const _0xbc1d5="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x222f60="",_0xb0e8cc="";for(let _0x1bf229=0,_0x4d1bc8,_0x4ea25d,_0x1900ee=0;_0x4ea25d=_0x2b1dfb["charAt"](_0x1900ee++);~_0x4ea25d&&(_0x4d1bc8=_0x1bf229%4?_0x4d1bc8*64+_0x4ea25d:_0x4ea25d,_0x1bf229++%4)?_0x222f60+=String["fromCharCode"](255&_0x4d1bc8>>(-2*_0x1bf229&6)):0){_0x4ea25d=_0xbc1d5["indexOf"](_0x4ea25d)}for(let _0x4ffb00=0,_0x7c5444=_0x222f60["length"];_0x4ffb00<_0x7c5444;_0x4ffb00++){_0xb0e8cc+="%"+("00"+_0x222f60["charCodeAt"](_0x4ffb00)["toString"](16))["slice"](-2)}return decodeURIComponent(_0xb0e8cc)};_0x3cf3["uyDunM"]=_0x332122,_0x3cf3["MbIlEQ"]={},_0x3cf3["hkXgZE"]=!![]}const _0x3a24b4=_0xe22bdb[0];_0x3cf3["pfQpbe"]!==_0x3a24b4&&(_0x3cf3["MbIlEQ"]={},_0x3cf3["pfQpbe"]=_0x3a24b4);const _0x3c68e8=_0x3cf3["MbIlEQ"][_0x51966f];return _0x3c68e8===undefined?(_0x3cf315=_0x3cf3["uyDunM"](_0x3cf315),_0x3cf3["MbIlEQ"][_0x51966f]=_0x3cf315):_0x3cf315=_0x3c68e8,_0x3cf315}const _0x1c3e9b=_0x3cf3;(function(_0x161f24,_0x2e1dce){const _0x310491=_0x3cf3,_0xc6a98d=_0x161f24();while(!![]){try{const _0x526499=-parseInt(_0x310491(268))/1*(-parseInt(_0x310491(290))/2)+-parseInt(_0x310491(261))/3*(parseInt(_0x310491(259))/4)+parseInt(_0x310491(294))/5+parseInt(_0x310491(276))/6*(-parseInt(_0x310491(263))/7)+-parseInt(_0x310491(293))/8+-parseInt(_0x310491(288))/9*(parseInt(_0x310491(264))/10)+-parseInt(_0x310491(286))/11*(-parseInt(_0x310491(275))/12);if(_0x526499===_0x2e1dce)break;else _0xc6a98d["push"](_0xc6a98d["shift"]())}catch(_0x299585){_0xc6a98d["push"](_0xc6a98d["shift"]())}}})(_0xe22b,336130);function _0xe22b(){const _0x4db3db=["D2LUzg93txm","nJuWntfcAvfztvq","ntCXotKWt2vRq0nm","BM9YBwfSAxPLuMvZDwX0","AxnbCNjHEq","z2v0","mxDMCvf6qq","C2XPzgLUzY13Aw5KB3CTBg9N","DgLTzq","y29ZDa","C2XPy2u","BM93","BgLTAxq","ntCZnMHrEg5lsW","mJi4uM51s1Lt","y2vPBa","zNvUy3rPB24","AxnszwrPCW","C2v0","Bg9N","CMfUzg9T","BgvUz3rO","CNvUu2nYAxb0","Dg9tDhjPBMC","mJGZmJv2q0L2DvO","ChvZAa","otLWD3bUAxC","C3rVCMu","mJG3otCYqu9JvLjq","D2L0AeXVy2S","zMLSDgvY","ndC0oteYogjLB1jAqq","mZeYnZe5nwrKC0jzzq","Bwf4","Aw5ZCgvJDa","y29UC3vTzq","ne9cyvP1Dq","z2v0u3rHDgu","mJy0ntqZrgvwChPb"];_0xe22b=function(){return _0x4db3db};return _0xe22b()}import{BaseAlgorithm}from"./baseAlgorithm.js";export class SlidingWindowLog extends BaseAlgorithm{constructor(_0xb0e8cc,_0x1bf229){const _0x52b70f=_0x3cf3;super(_0xb0e8cc,_0x1bf229),this[_0x52b70f(274)]=_0xb0e8cc[_0x52b70f(274)],this[_0x52b70f(262)]=_0xb0e8cc["windowMs"]}async[_0x1c3e9b(258)](_0x4d1bc8,_0x4ea25d=1){const _0x4747d2=_0x1c3e9b;if(this[_0x4747d2(279)]()){const _0x1900ee=Date[_0x4747d2(273)]()+":"+Math[_0x4747d2(282)]()[_0x4747d2(285)](36)[_0x4747d2(272)](2,8),_0x4ffb00=typeof this[_0x4747d2(289)]["getNow"]===_0x4747d2(278)?this[_0x4747d2(289)]["getNow"]():0,_0x7c5444=await this[_0x4747d2(289)][_0x4747d2(284)](_0x4747d2(269),[_0x4d1bc8],[this[_0x4747d2(274)],this[_0x4747d2(262)],_0x4ea25d,_0x1900ee,_0x4ffb00]);return this[_0x4747d2(265)](_0x7c5444[0]===1,_0x7c5444[1],_0x7c5444[2],_0x7c5444[3],_0x7c5444[4],_0x7c5444[5])}return this[_0x4747d2(291)](_0x4d1bc8,async()=>{const _0x1b9ac1=_0x4747d2,_0x4f53fd=Date["now"](),_0xa8331b=_0x4f53fd-this[_0x1b9ac1(262)],_0x1fb517=await this[_0x1b9ac1(289)][_0x1b9ac1(267)](_0x4d1bc8);let _0x468d19=[];_0x1fb517&&Array["isArray"](_0x1fb517["log"])&&(_0x468d19=_0x1fb517[_0x1b9ac1(281)][_0x1b9ac1(292)](_0x33c1bc=>_0x33c1bc[_0x1b9ac1(270)]>_0xa8331b));let _0x35bc92=0;for(let _0x1ee7ba=0;_0x1ee7ba<_0x468d19[_0x1b9ac1(283)];_0x1ee7ba++){_0x35bc92+=_0x468d19[_0x1ee7ba][_0x1b9ac1(271)]}if(_0x35bc92+_0x4ea25d<=this[_0x1b9ac1(274)]){_0x468d19[_0x1b9ac1(287)]({"time":_0x4f53fd,"cost":_0x4ea25d});const _0xf05da7=_0x35bc92+_0x4ea25d,_0x369f6b=this[_0x1b9ac1(274)]-_0xf05da7,_0x593cbb=_0x468d19[0]["time"],_0xf31e5=_0x593cbb+this["windowMs"];return await this[_0x1b9ac1(289)][_0x1b9ac1(280)](_0x4d1bc8,{"log":_0x468d19},this["windowMs"]),this[_0x1b9ac1(265)](!![],this["limit"],_0x369f6b,_0xf05da7,0,_0xf31e5)}const _0x25d59f=_0x35bc92+_0x4ea25d-this[_0x1b9ac1(274)];let _0x2e15df=0,_0x590094=_0x4f53fd;for(let _0x1e39a6=0;_0x1e39a6<_0x468d19[_0x1b9ac1(283)];_0x1e39a6++){_0x2e15df+=_0x468d19[_0x1e39a6][_0x1b9ac1(271)];if(_0x2e15df>=_0x25d59f){_0x590094=_0x468d19[_0x1e39a6]["time"];break}}const _0x1e7fe2=_0x590094+this[_0x1b9ac1(262)],_0x39c303=Math[_0x1b9ac1(295)](1,_0x1e7fe2-_0x4f53fd),_0x59ba9b=Math[_0x1b9ac1(295)](1,Math[_0x1b9ac1(277)](_0x39c303/1000)),_0x5e367d=Math[_0x1b9ac1(295)](0,this[_0x1b9ac1(274)]-_0x35bc92);return await this[_0x1b9ac1(289)][_0x1b9ac1(280)](_0x4d1bc8,{"log":_0x468d19},this["windowMs"]),this[_0x1b9ac1(265)](![],this[_0x1b9ac1(274)],_0x5e367d,_0x35bc92,_0x59ba9b,_0x1e7fe2)})}async[_0x1c3e9b(260)](_0xe51c57){const _0x51b840=_0x1c3e9b;if(this["isRedis"]()){const _0xd37f21=typeof this[_0x51b840(289)]["getNow"]===_0x51b840(278)?this[_0x51b840(289)]["getNow"]():0,_0x4127e3=await this[_0x51b840(289)][_0x51b840(284)](_0x51b840(269),[_0xe51c57],[this["limit"],this[_0x51b840(262)],0,_0x51b840(296),_0xd37f21]);return this["normalizeResult"](_0x4127e3[0]===1,_0x4127e3[1],_0x4127e3[2],_0x4127e3[3],_0x4127e3[4],_0x4127e3[5])}const _0x4f280c=Date[_0x51b840(273)](),_0x510353=_0x4f280c-this[_0x51b840(262)],_0x56bd15=await this[_0x51b840(289)]["get"](_0xe51c57);let _0x105544=[];_0x56bd15&&Array[_0x51b840(266)](_0x56bd15[_0x51b840(281)])&&(_0x105544=_0x56bd15[_0x51b840(281)][_0x51b840(292)](_0x53b028=>_0x53b028[_0x51b840(270)]>_0x510353));let _0x393bd6=0;for(let _0x45e68b=0;_0x45e68b<_0x105544[_0x51b840(283)];_0x45e68b++){_0x393bd6+=_0x105544[_0x45e68b]["cost"]}const _0x4c46ed=Math[_0x51b840(295)](0,this[_0x51b840(274)]-_0x393bd6),_0x9393df=_0x105544[_0x51b840(283)]>0?_0x105544[0][_0x51b840(270)]:_0x4f280c,_0x2e13e3=_0x9393df+this[_0x51b840(262)],_0x3d71d1=_0x393bd6>=this[_0x51b840(274)]?Math[_0x51b840(295)](1,Math["ceil"]((_0x2e13e3-_0x4f280c)/1000)):0;return this[_0x51b840(265)](_0x393bd6<this[_0x51b840(274)],this[_0x51b840(274)],_0x4c46ed,_0x393bd6,_0x3d71d1,_0x2e13e3)}}export default SlidingWindowLog;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class SlidingWindowLog extends BaseAlgorithm{constructor(options,store){super(options,store);this.limit=options.limit;this.windowMs=options.windowMs;}async consume(key,cost=1){if(this.isRedis()){const uid=`${Date.now()}:${Math.random().toString(36).slice(2,8)}`;const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-log',[key],[this.limit,this.windowMs,cost,uid,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const windowStart=now-this.windowMs;const state=await this.store.get(key);let log=[];if(state&&Array.isArray(state.log)){log=state.log.filter(entry=>entry.time>windowStart);}let current=0;for(let i=0;i<log.length;i++){current+=log[i].cost;}if(current+cost<=this.limit){log.push({time:now,cost});const consumed=current+cost;const remaining=this.limit-consumed;const oldestTime=log[0].time;const resetTime=oldestTime+this.windowMs;await this.store.set(key,{log},this.windowMs);return this.normalizeResult(true,this.limit,remaining,consumed,0,resetTime);}const deficit=current+cost-this.limit;let accumulated=0;let expireTime=now;for(let i=0;i<log.length;i++){accumulated+=log[i].cost;if(accumulated>=deficit){expireTime=log[i].time;break;}}const resetTime=expireTime+this.windowMs;const waitMs=Math.max(1,resetTime-now);const retryAfter=Math.max(1,Math.ceil(waitMs/1000));const remaining=Math.max(0,this.limit-current);await this.store.set(key,{log},this.windowMs);return this.normalizeResult(false,this.limit,remaining,current,retryAfter,resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('sliding-window-log',[key],[this.limit,this.windowMs,0,'inspect',nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const windowStart=now-this.windowMs;const state=await this.store.get(key);let log=[];if(state&&Array.isArray(state.log)){log=state.log.filter(entry=>entry.time>windowStart);}let current=0;for(let i=0;i<log.length;i++){current+=log[i].cost;}const remaining=Math.max(0,this.limit-current);const oldestTime=log.length>0?log[0].time:now;const resetTime=oldestTime+this.windowMs;const retryAfter=current>=this.limit?Math.max(1,Math.ceil((resetTime-now)/1000)):0;return this.normalizeResult(current<this.limit,this.limit,remaining,current,retryAfter,resetTime);}}export default SlidingWindowLog;
@@ -1 +1 @@
1
- const _0x589a59=_0x57bf;(function(_0x5d1263,_0x3d8256){const _0xf35e81=_0x57bf,_0x3c3b77=_0x5d1263();while(!![]){try{const _0x22a625=-parseInt(_0xf35e81(384))/1+parseInt(_0xf35e81(395))/2+-parseInt(_0xf35e81(393))/3*(parseInt(_0xf35e81(366))/4)+-parseInt(_0xf35e81(396))/5*(parseInt(_0xf35e81(371))/6)+parseInt(_0xf35e81(365))/7*(parseInt(_0xf35e81(378))/8)+-parseInt(_0xf35e81(380))/9*(parseInt(_0xf35e81(368))/10)+parseInt(_0xf35e81(362))/11;if(_0x22a625===_0x3d8256)break;else _0x3c3b77["push"](_0x3c3b77["shift"]())}catch(_0x339805){_0x3c3b77["push"](_0x3c3b77["shift"]())}}})(_0x37e8,363242);function _0x57bf(_0x429af7,_0xc4eada){_0x429af7=_0x429af7-361;const _0x37e890=_0x37e8();let _0x57bf57=_0x37e890[_0x429af7];if(_0x57bf["OEfQQp"]===undefined){var _0x1c32d1=function(_0x1d4b7d){const _0x4d1d58="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";let _0x52288a="",_0x1ee2a8="";for(let _0x4bb8bb=0,_0x1ebdfa,_0x3076bf,_0x30b2c1=0;_0x3076bf=_0x1d4b7d["charAt"](_0x30b2c1++);~_0x3076bf&&(_0x1ebdfa=_0x4bb8bb%4?_0x1ebdfa*64+_0x3076bf:_0x3076bf,_0x4bb8bb++%4)?_0x52288a+=String["fromCharCode"](255&_0x1ebdfa>>(-2*_0x4bb8bb&6)):0){_0x3076bf=_0x4d1d58["indexOf"](_0x3076bf)}for(let _0x4db8fb=0,_0x3c9947=_0x52288a["length"];_0x4db8fb<_0x3c9947;_0x4db8fb++){_0x1ee2a8+="%"+("00"+_0x52288a["charCodeAt"](_0x4db8fb)["toString"](16))["slice"](-2)}return decodeURIComponent(_0x1ee2a8)};_0x57bf["BpwhfA"]=_0x1c32d1,_0x57bf["pBLeHn"]={},_0x57bf["OEfQQp"]=!![]}const _0x4ba82f=_0x37e890[0];_0x57bf["TeequZ"]!==_0x4ba82f&&(_0x57bf["pBLeHn"]={},_0x57bf["TeequZ"]=_0x4ba82f);const _0x3954cf=_0x57bf["pBLeHn"][_0x429af7];return _0x3954cf===undefined?(_0x57bf57=_0x57bf["BpwhfA"](_0x57bf57),_0x57bf["pBLeHn"][_0x429af7]=_0x57bf57):_0x57bf57=_0x3954cf,_0x57bf57}import{BaseAlgorithm}from"./baseAlgorithm.js";export class TokenBucket extends BaseAlgorithm{constructor(_0x1ee2a8,_0x4bb8bb){const _0x41c5c7=_0x57bf;super(_0x1ee2a8,_0x4bb8bb),this[_0x41c5c7(364)]=_0x1ee2a8[_0x41c5c7(364)],this[_0x41c5c7(383)]=_0x1ee2a8[_0x41c5c7(383)],this[_0x41c5c7(386)]=_0x1ee2a8[_0x41c5c7(386)],this[_0x41c5c7(382)]=_0x1ee2a8[_0x41c5c7(382)]!==undefined?_0x1ee2a8[_0x41c5c7(382)]:this[_0x41c5c7(364)],this[_0x41c5c7(361)]=this[_0x41c5c7(383)]/this[_0x41c5c7(386)]}async[_0x589a59(363)](_0x1ebdfa,_0x3076bf=1){const _0x590314=_0x589a59;if(this["isRedis"]()){const _0x30b2c1=typeof this[_0x590314(367)][_0x590314(376)]==="function"?this[_0x590314(367)]["getNow"]():0,_0x4db8fb=await this[_0x590314(367)][_0x590314(373)](_0x590314(372),[_0x1ebdfa],[this[_0x590314(364)],this[_0x590314(383)],this[_0x590314(386)],_0x3076bf,this[_0x590314(382)],_0x30b2c1]);return this[_0x590314(369)](_0x4db8fb[0]===1,_0x4db8fb[1],_0x4db8fb[2],_0x4db8fb[3],_0x4db8fb[4],_0x4db8fb[5])}return this[_0x590314(375)](_0x1ebdfa,async()=>{const _0x1d5355=_0x590314,_0x3c9947=Date["now"](),_0x427ea6=await this[_0x1d5355(367)][_0x1d5355(374)](_0x1ebdfa);let _0x327d8a=this[_0x1d5355(382)],_0x39673a=_0x3c9947;if(_0x427ea6&&typeof _0x427ea6[_0x1d5355(387)]===_0x1d5355(390)){const _0x3d375f=Math[_0x1d5355(381)](0,_0x3c9947-_0x427ea6[_0x1d5355(388)]);_0x327d8a=Math[_0x1d5355(370)](this[_0x1d5355(364)],_0x427ea6[_0x1d5355(387)]+_0x3d375f*this[_0x1d5355(361)]),_0x39673a=_0x3c9947}const _0x26f818=Math[_0x1d5355(381)](1000,Math[_0x1d5355(391)](this[_0x1d5355(364)]/this[_0x1d5355(361)]*2));if(_0x327d8a>=_0x3076bf){_0x327d8a-=_0x3076bf;const _0x582323=this[_0x1d5355(364)]-Math[_0x1d5355(377)](_0x327d8a),_0x3c4d8c=Math[_0x1d5355(377)](_0x327d8a),_0x16e508=_0x3c9947+Math[_0x1d5355(391)]((this["capacity"]-_0x327d8a)/this["rate"]);return await this["store"][_0x1d5355(389)](_0x1ebdfa,{"tokens":_0x327d8a,"lastRefill":_0x39673a},_0x26f818),this["normalizeResult"](!![],this[_0x1d5355(364)],_0x3c4d8c,_0x582323,0,_0x16e508)}const _0x561ad8=this[_0x1d5355(364)]-Math[_0x1d5355(377)](_0x327d8a),_0x4bd033=Math["floor"](_0x327d8a),_0x12a0c6=_0x3076bf-_0x327d8a,_0x260aa7=Math[_0x1d5355(391)](_0x12a0c6/this["rate"]),_0x250918=Math["max"](1,Math[_0x1d5355(391)](_0x260aa7/1000)),_0x1ae4e2=_0x3c9947+Math[_0x1d5355(391)]((this["capacity"]-_0x327d8a)/this["rate"]);return await this["store"][_0x1d5355(389)](_0x1ebdfa,{"tokens":_0x327d8a,"lastRefill":_0x39673a},_0x26f818),this[_0x1d5355(369)](![],this[_0x1d5355(364)],_0x4bd033,_0x561ad8,_0x250918,_0x1ae4e2)})}async[_0x589a59(385)](_0x4c0c1c){const _0x55af31=_0x589a59;if(this[_0x55af31(379)]()){const _0x242127=typeof this[_0x55af31(367)]["getNow"]===_0x55af31(392)?this[_0x55af31(367)][_0x55af31(376)]():0,_0x1ff097=await this["store"][_0x55af31(373)](_0x55af31(372),[_0x4c0c1c],[this[_0x55af31(364)],this[_0x55af31(383)],this[_0x55af31(386)],0,this[_0x55af31(382)],_0x242127]);return this[_0x55af31(369)](_0x1ff097[0]===1,_0x1ff097[1],_0x1ff097[2],_0x1ff097[3],_0x1ff097[4],_0x1ff097[5])}const _0x2b5992=Date[_0x55af31(394)](),_0x562dfc=await this["store"][_0x55af31(374)](_0x4c0c1c);let _0x39d760=this["initialTokens"];if(_0x562dfc&&typeof _0x562dfc[_0x55af31(387)]===_0x55af31(390)){const _0x257804=Math[_0x55af31(381)](0,_0x2b5992-_0x562dfc[_0x55af31(388)]);_0x39d760=Math["min"](this[_0x55af31(364)],_0x562dfc["tokens"]+_0x257804*this["rate"])}const _0x4668a9=this[_0x55af31(364)]-Math[_0x55af31(377)](_0x39d760),_0x1924f1=Math["floor"](_0x39d760),_0x595ba2=_0x2b5992+Math[_0x55af31(391)]((this["capacity"]-_0x39d760)/this[_0x55af31(361)]),_0x77b04d=_0x39d760<1?Math[_0x55af31(381)](1,Math["ceil"]((1-_0x39d760)/this[_0x55af31(361)]/1000)):0;return this["normalizeResult"](_0x39d760>=1,this[_0x55af31(364)],_0x1924f1,_0x4668a9,_0x77b04d,_0x595ba2)}}function _0x37e8(){const _0xd3c747=["zMXVB3i","mZe4mtzUt1bqCeS","AxnszwrPCW","nduZmta1wLbhshvt","Bwf4","Aw5PDgLHBfrVA2vUCW","CMvMAwXSuMf0zq","ntC5ota5rvrUtK1p","z2v0u3rHDgu","CMvMAwXSsw50zxj2ywW","Dg9Rzw5Z","BgfZDfjLzMLSBa","C2v0","BNvTyMvY","y2vPBa","zNvUy3rPB24","mJy5ntHXre52sva","BM93","nti2nZuWBvDiveDm","mti1v0nsB3Lf","CMf0zq","mJeZmZqZmZvIqvz3vNC","y29UC3vTzq","y2fWywnPDhK","nJnWD0LwAM4","mtq4sxPRuuvN","C3rVCMu","mteWugXqy1rz","BM9YBwfSAxPLuMvZDwX0","BwLU","otGYmtrKvwTlvhy","Dg9Rzw4TyNvJA2v0","CNvUu2nYAxb0","z2v0","D2L0AeXVy2S","z2v0tM93"];_0x37e8=function(){return _0xd3c747};return _0x37e8()}export default TokenBucket;
1
+ import{BaseAlgorithm}from'./baseAlgorithm.js';export class TokenBucket extends BaseAlgorithm{constructor(options,store){super(options,store);this.capacity=options.capacity;this.refillRate=options.refillRate;this.refillInterval=options.refillInterval;this.initialTokens=options.initialTokens!==undefined?options.initialTokens:this.capacity;this.rate=this.refillRate/this.refillInterval;}async consume(key,cost=1){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('token-bucket',[key],[this.capacity,this.refillRate,this.refillInterval,cost,this.initialTokens,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}return this.withLock(key,async()=>{const now=Date.now();const state=await this.store.get(key);let tokens=this.initialTokens;let lastRefill=now;if(state&&typeof state.tokens==='number'){const elapsed=Math.max(0,now-state.lastRefill);tokens=Math.min(this.capacity,state.tokens+elapsed*this.rate);lastRefill=now;}const ttlMs=Math.max(1000,Math.ceil(this.capacity/this.rate*2.0));if(tokens>=cost){tokens-=cost;const consumed=this.capacity-Math.floor(tokens);const remaining=Math.floor(tokens);const resetTime=now+Math.ceil((this.capacity-tokens)/this.rate);await this.store.set(key,{tokens,lastRefill},ttlMs);return this.normalizeResult(true,this.capacity,remaining,consumed,0,resetTime);}const consumed=this.capacity-Math.floor(tokens);const remaining=Math.floor(tokens);const deficit=cost-tokens;const waitMs=Math.ceil(deficit/this.rate);const retryAfter=Math.max(1,Math.ceil(waitMs/1000));const resetTime=now+Math.ceil((this.capacity-tokens)/this.rate);await this.store.set(key,{tokens,lastRefill},ttlMs);return this.normalizeResult(false,this.capacity,remaining,consumed,retryAfter,resetTime);});}async getState(key){if(this.isRedis()){const nowArg=typeof this.store.getNow==='function'?this.store.getNow():0;const res=await this.store.runScript('token-bucket',[key],[this.capacity,this.refillRate,this.refillInterval,0,this.initialTokens,nowArg]);return this.normalizeResult(res[0]===1,res[1],res[2],res[3],res[4],res[5]);}const now=Date.now();const state=await this.store.get(key);let tokens=this.initialTokens;if(state&&typeof state.tokens==='number'){const elapsed=Math.max(0,now-state.lastRefill);tokens=Math.min(this.capacity,state.tokens+elapsed*this.rate);}const consumed=this.capacity-Math.floor(tokens);const remaining=Math.floor(tokens);const resetTime=now+Math.ceil((this.capacity-tokens)/this.rate);const retryAfter=tokens<1?Math.max(1,Math.ceil((1-tokens)/this.rate/1000)):0;return this.normalizeResult(tokens>=1,this.capacity,remaining,consumed,retryAfter,resetTime);}}export default TokenBucket;
@@ -0,0 +1 @@
1
+ export class BlockedKeys{constructor(){this._keys=new Map();}add(key,durationSec){const durationMs=Math.max(0,Number(durationSec)||0)*1000;this.addMs(key,durationMs);}_sweep(now=Date.now()){for(const[key,expiresAt]of this._keys.entries()){if(expiresAt<=now){this._keys.delete(key);}}}addMs(key,durationMs){const now=Date.now();if(this._keys.size>999){this._sweep(now);}const ms=Math.max(0,Number(durationMs)||0);this._keys.set(key,now+ms);}msBeforeExpire(key){const expiresAt=this._keys.get(key);if(expiresAt===undefined)return 0;const remaining=expiresAt-Date.now();if(remaining<=0){this._keys.delete(key);return 0;}return remaining;}isBlocked(key){return this.msBeforeExpire(key)>0;}delete(key){return this._keys.delete(key);}deleteAll(){this._keys.clear();}get size(){return this._keys.size;}}export default BlockedKeys;
@@ -0,0 +1 @@
1
+ import{defaultKeyGenerator,ipKeyGenerator,graphQLKeyGenerator}from'../utils/keyGenerator.js';import{evaluateSkipRule}from'../utils/skipUtils.js';import{RateLimiterRes}from'../core/rateLimiterRes.js';export function createCoreMiddleware(limiter,options={}){if(!limiter||typeof limiter.consume!=='function'){throw new TypeError('createCoreMiddleware requires a limiter with a consume() method');}let keyGen=defaultKeyGenerator;if(typeof options.keyGenerator==='function'){keyGen=options.keyGenerator;}else if(options.keyGenerator==='ip'){keyGen=ipKeyGenerator;}else if(options.keyGenerator==='graphql'){keyGen=graphQLKeyGenerator;}const rawSkip=options.skip!==undefined?options.skip:options.customSkip!==undefined?options.customSkip:options.customskip;const skipFn=typeof rawSkip==='function'?rawSkip:typeof rawSkip==='object'&&rawSkip!==null?(req,res)=>evaluateSkipRule(rawSkip,req,res,{generateKey:keyGen}):()=>false;const defaultHandler=(req,res,next,opts)=>{res.status(opts.statusCode);const msg=typeof opts.message==='function'?opts.message(req,res):opts.message;if(typeof msg==='object'){res.json(msg);}else{res.send(msg);}};const handler=options.handler||defaultHandler;const resolvedOptions={standardHeaders:options.standardHeaders!==undefined?options.standardHeaders:true,legacyHeaders:options.legacyHeaders!==undefined?options.legacyHeaders:false,...options,statusCode:options.statusCode!==undefined?options.statusCode:429,message:options.message!==undefined?options.message:'Too many requests, please try again later.'};const middleware=async(req,res,next)=>{try{const shouldSkip=await Promise.resolve(skipFn(req,res));if(shouldSkip){return next();}const key=await Promise.resolve(keyGen(req,res));let cost=1;if(typeof options.cost==='function'){cost=await Promise.resolve(options.cost(req,res));}else if(typeof options.cost==='number'){cost=options.cost;}if(!Number.isFinite(cost)||cost<=0){cost=1;}let res1;try{res1=await limiter.consume(key,cost,{req,res});}catch(err){if(err instanceof RateLimiterRes){res1=err;}else if(err&&typeof err==='object'&&Object.values(err).some(v=>v instanceof RateLimiterRes)){const resValues=Object.values(err).filter(v=>v instanceof RateLimiterRes);const maxMsBeforeNext=Math.max(...resValues.map(v=>v.msBeforeNext||0));const minRemaining=Math.min(...resValues.map(v=>v.remainingPoints||0));const maxConsumed=Math.max(...resValues.map(v=>v.consumedPoints||0));res1=new RateLimiterRes(minRemaining,maxMsBeforeNext,maxConsumed,false);res1._unionRejections=err;}else{throw err;}}if(res1&&!(res1 instanceof RateLimiterRes)&&typeof res1==='object'&&Object.values(res1).every(v=>v instanceof RateLimiterRes)){const resValues=Object.values(res1);const maxMsBeforeNext=Math.max(...resValues.map(v=>v.msBeforeNext||0));const minRemaining=Math.min(...resValues.map(v=>v.remainingPoints||0));const maxConsumed=Math.max(...resValues.map(v=>v.consumedPoints||0));res1=new RateLimiterRes(minRemaining,maxMsBeforeNext,maxConsumed,resValues.some(v=>v.isFirstInDuration));}const result=toMiddlewareResult(res1,limiter,cost);req.rateLimit=result;setCoreHeaders(res,result,{...resolvedOptions,limiter});if(result.allowed){return next();}return handler(req,res,next,resolvedOptions);}catch(err){if(options.failOpen){req.rateLimit={allowed:true,limit:limiter.points||60,remaining:1,consumed:0,retryAfter:0,resetTime:new Date(Date.now()+(limiter.msDuration||60000)),failedOpen:true};if(typeof options.onError==='function'){options.onError(err,req,res,next);}return next();}if(typeof options.onError==='function'){return options.onError(err,req,res,next);}return next(err);}};middleware.resetKey=async keyOrReq=>{const k=typeof keyOrReq==='object'&&keyOrReq!==null?keyGen(keyOrReq):keyOrReq;if(typeof limiter.delete==='function'){return limiter.delete(k);}return false;};middleware.getState=async keyOrReq=>{const k=typeof keyOrReq==='object'&&keyOrReq!==null?keyGen(keyOrReq):keyOrReq;if(typeof limiter.get==='function'){return limiter.get(k);}return null;};middleware.limiter=limiter;return middleware;}function toMiddlewareResult(res,limiter,cost){const limit=typeof limiter.points==='number'?limiter.points:typeof res.consumedPoints==='number'&&typeof res.remainingPoints==='number'?res.consumedPoints+res.remainingPoints:60;const consumed=typeof res.consumedPoints==='number'?res.consumedPoints:cost;const msBeforeNext=typeof res.msBeforeNext==='number'?res.msBeforeNext:0;const rawReset=Number.isFinite(msBeforeNext)&&msBeforeNext>0?Date.now()+Math.min(msBeforeNext,31536000000):Date.now();const resetTime=new Date(rawReset);return{allowed:consumed<=limit,limit,remaining:Math.max(0,limit-consumed),consumed,retryAfter:msBeforeNext>0?Math.max(1,Math.ceil(msBeforeNext/1000)):0,resetTime:Number.isNaN(resetTime.getTime())?new Date(Date.now()+3600000):resetTime,cost};}function setCoreHeaders(res,result,options={}){if(!res||typeof res.setHeader!=='function'||res.headersSent){return;}const rawDiff=result.resetTime?(result.resetTime.getTime()-Date.now())/1000:0;const resetSeconds=Number.isFinite(rawDiff)?Math.max(0,Math.ceil(rawDiff)):0;if(options.standardHeaders){res.setHeader('RateLimit-Limit',String(result.limit));res.setHeader('RateLimit-Remaining',String(result.remaining));res.setHeader('RateLimit-Reset',String(resetSeconds));const policyWindowMs=options.windowMs||options.limiter&&(options.limiter.msDuration||(options.limiter.duration?options.limiter.duration*1000:null))||60000;if(options.standardHeaders!=='draft-6'){const windowSec=Math.ceil(policyWindowMs/1000);res.setHeader('RateLimit-Policy',`${result.limit};w=${windowSec}`);}}if(options.legacyHeaders){res.setHeader('X-RateLimit-Limit',String(result.limit));res.setHeader('X-RateLimit-Remaining',String(result.remaining));res.setHeader('X-RateLimit-Reset',String(resetSeconds));}if(!result.allowed){const retryAfter=Math.max(1,Number(result.retryAfter)||resetSeconds||1);res.setHeader('Retry-After',String(retryAfter));}}export default createCoreMiddleware;
@@ -0,0 +1,16 @@
1
+ import{RateLimiterStore}from'../rateLimiterStore.js';import{RedisClientAdapter}from'../../utils/redisClient.js';const INCR_TTL_LUA=`
2
+ local ttl = redis.call('pttl', KEYS[1])
3
+ if ttl == -2 then
4
+ redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
5
+ return {ARGV[1], ARGV[2]}
6
+ end
7
+ local consumed = redis.call('incrby', KEYS[1], ARGV[1])
8
+ if ttl == -1 then
9
+ redis.call('pexpire', KEYS[1], ARGV[2])
10
+ ttl = tonumber(ARGV[2])
11
+ end
12
+ return {consumed, ttl}
13
+ `;const SET_TTL_LUA=`
14
+ redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])
15
+ return {ARGV[1], ARGV[2]}
16
+ `;export class ClusterLimiter extends RateLimiterStore{constructor(opts={}){super(opts);const client=opts.storeClient||opts.client||opts.redis||null;if(!client){throw new Error('Redis cluster client is not configured. Pass it via the "storeClient" option.');}this.client=client;this.adapter=new RedisClientAdapter(client,{clientKind:opts.clientKind});this.hashTag=opts.hashTag!==undefined?Boolean(opts.hashTag):true;this._incrTtlScript=opts.customIncrTtlLuaScript||INCR_TTL_LUA;this._setTtlScript=opts.customSetTtlLuaScript||SET_TTL_LUA;}getKey(key){const prefixed=super.getKey(key);if(!this.hashTag)return prefixed;if(prefixed.includes('{'))return prefixed;return`{${prefixed}}`;}async _upsert(rlKey,points,msDuration,forceExpire=false){if(!Number.isInteger(points)){throw new Error('Consuming decimal number of points is not supported by this package');}const ttl=Math.max(1,Math.floor(msDuration));const script=forceExpire?this._setTtlScript:this._incrTtlScript;const raw=await this.adapter.evalScript(script,[rlKey],[points,ttl]);const consumed=Array.isArray(raw)?Number(raw[0]):Number(raw);const resTtlMs=Array.isArray(raw)?Number(raw[1]):ttl;return{consumedPoints:consumed,expireAt:msDuration>0?Date.now()+resTtlMs:null};}async _get(rlKey){const entry=await this.adapter.getWithTtl(rlKey);if(!entry)return null;const consumedPoints=Number(entry.value);if(!Number.isFinite(consumedPoints))return null;return{consumedPoints,expireAt:entry.ttlMs>0?Date.now()+entry.ttlMs:null};}async _delete(rlKey){return this.adapter.del(rlKey);}}export const RedisClusterLimiter=ClusterLimiter;export default ClusterLimiter;