hono-rate-limiter 0.1.0 → 0.1.2

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 CHANGED
@@ -12,6 +12,9 @@
12
12
  Rate limiting middleware for [Hono](https://hono.dev/). Use to
13
13
  limit repeated requests to public APIs and/or endpoints such as password reset.
14
14
 
15
+ > [!WARNING]
16
+ > The `keyGenerator` function is currently under construction and needs to be defined for `hono-rate-limiter` to work properly in your environment. Please ensure that you define the `keyGenerator` function according to the documentation before using the library.
17
+
15
18
  ## Usage
16
19
 
17
20
  ```ts
@@ -20,7 +23,8 @@ import { rateLimiter } from "hono-rate-limiter";
20
23
  const limiter = rateLimiter({
21
24
  windowMs: 15 * 60 * 1000, // 15 minutes
22
25
  limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
23
- standardHeaders: "draft-7", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
26
+ standardHeaders: "draft-6", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
27
+ keyGenerator: () => "<unique_key>", // Method to generate custom identifiers for clients.
24
28
  // store: ... , // Redis, MemoryStore, etc. See below.
25
29
  });
26
30
 
@@ -30,18 +34,24 @@ app.use(limiter);
30
34
 
31
35
  # Data Stores
32
36
 
33
- Express-rate-limit supports external data stores to sychronize hit counts across multiple processes and servers.
37
+ Express-rate-limit supports external data stores to synchronize hit counts across multiple processes and servers.
34
38
 
35
- By default, `MemoryStore` is used. This one does not synchronize it’s state across instances. It’s simple to deploy, and often sufficient for basic abuse prevention, but will be inconnsistent across reboots or in deployments with multiple process or servers.
39
+ By default, `MemoryStore` is used. This one does not synchronize its state across instances. It’s simple to deploy, and often sufficient for basic abuse prevention, but will be inconnsistent across reboots or in deployments with multiple process or servers.
36
40
 
37
41
  Deployments requiring more consistently enforced rate limits should use an external store.
38
42
 
39
43
  Here is a list of stores:
40
44
 
41
- | Name | Description |
42
- | ----------- | --------------------------------------------------------------------------------------------------- |
43
- | MemoryStore | (default) Simple in-memory option. Does not share state when app has multiple processes or servers. |
44
- | RedisStore | A [Redis](https://redis.io/)-backed store, more suitable for large or demanding deployments. |
45
+ | Name | Description |
46
+ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
+ | MemoryStore | (default) Simple in-memory option. Does not share state when the app has multiple processes or servers. |
48
+ | [`rate-limit-redis`](https://npm.im/rate-limit-redis) | A [Redis](https://redis.io/)-backed store, more suitable for large or demanding deployments. |
49
+ | [`rate-limit-postresql`](https://www.npm.im/@acpr/rate-limit-postgresql) | A [PostgreSQL](https://www.postgresql.org/)-backed store. |
50
+ | [`rate-limit-memecached`](https://npmjs.org/package/rate-limit-memcached) | A [Memcached](https://memcached.org/)-backed store. |
51
+ | [`cluster-memory-store`](https://npm.im/@express-rate-limit/cluster-memory-store) | A memory-store wrapper that shares state across all processes on a single server via the [node:cluster](https://nodejs.org/api/cluster.html) module. Does not share state across multiple servers. |
52
+ | [`precise-memory-rate-limit`](https://www.npm.im/precise-memory-rate-limit) | A memory store similar to the built-in one, except that it stores a distinct timestamp for each key. |
53
+ | [`typeorm-rate-limit-store`](https://www.npmjs.com/package/typeorm-rate-limit-store) | Supports a variety of databases via [TypeORM](https://typeorm.io/): MySQL, MariaDB, CockroachDB, SQLite, Microsoft SQL Server, Oracle, SAP Hana, and more. |
54
+ | [`@rlimit/storage`](<(https://www.npmjs.com/package/@rlimit/storage)>) | A distributed rlimit store, ideal for multi-regional deployments. |
45
55
 
46
56
  Take a look at this [guide](https://express-rate-limit.mintlify.app/guides/creating-a-store) if you wish to create your own store.
47
57
 
package/package.json CHANGED
@@ -1,22 +1,36 @@
1
1
  {
2
2
  "name": "hono-rate-limiter",
3
- "version": "0.1.0",
4
- "type": "commonjs",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "hono",
8
+ "api",
9
+ "middleware",
10
+ "rest-api",
11
+ "rate-limiting",
12
+ "rate-limiter",
13
+ "honojs"
14
+ ],
15
+ "homepage": "https://github.com/rhinobase/hono-rate-limiter",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/rhinobase/hono-rate-limiter.git",
22
+ "directory": "packages/ui"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/rhinobase/hono-rate-limiter/issues"
26
+ },
5
27
  "dependencies": {
6
28
  "@swc/helpers": "~0.5.2"
7
29
  },
8
30
  "peerDependencies": {
9
31
  "hono": "^4.1.1"
10
32
  },
11
- "exports": {
12
- ".": {
13
- "default": "./src/index.js",
14
- "types": "./src/index.d.ts"
15
- },
16
- "./redis": {
17
- "default": "./src/redis/index.js",
18
- "types": "./src/redis/index.d.ts"
19
- }
20
- },
21
- "main": "./src/index.js"
33
+ "main": "./src/index.js",
34
+ "typings": "./src/index.d.ts",
35
+ "module": "./src/index.js"
22
36
  }
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"defaultKeyGenerator",{enumerable:true,get:function(){return defaultKeyGenerator}});const _adapter=require("hono/adapter");function defaultKeyGenerator(c){const runtime=(0,_adapter.getRuntimeKey)();let key=null;switch(runtime){case"workerd":key=c.req.raw.headers.get("CF-Connecting-IP");break;case"fastly":key=c.req.raw.headers.get("Fastly-Client-IP");break;case"other":key=c.req.raw.headers.get("x-real-ip");break;default:break}return key!=null?key:""}
1
+ import{getRuntimeKey}from"hono/adapter";export function defaultKeyGenerator(c){const runtime=getRuntimeKey();let key=null;switch(runtime){case"workerd":key=c.req.raw.headers.get("CF-Connecting-IP");break;case"fastly":key=c.req.raw.headers.get("Fastly-Client-IP");break;case"other":key=c.req.raw.headers.get("x-real-ip");break;default:break}return key??""}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:all[name]})}_export(exports,{setDraft6Headers:function(){return setDraft6Headers},setDraft7Headers:function(){return setDraft7Headers},setRetryAfterHeader:function(){return setRetryAfterHeader}});const getResetSeconds=(resetTime,windowMs)=>{let resetSeconds;if(resetTime){const deltaSeconds=Math.ceil((resetTime.getTime()-Date.now())/1e3);resetSeconds=Math.max(0,deltaSeconds)}else if(windowMs){resetSeconds=Math.ceil(windowMs/1e3)}return resetSeconds};const setDraft6Headers=(context,info,windowMs)=>{if(context.finalized)return;const windowSeconds=Math.ceil(windowMs/1e3);const resetSeconds=getResetSeconds(info.resetTime);context.header("RateLimit-Policy",`${info.limit};w=${windowSeconds}`);context.header("RateLimit-Limit",info.limit.toString());context.header("RateLimit-Remaining",info.remaining.toString());if(resetSeconds)context.header("RateLimit-Reset",resetSeconds.toString())};const setDraft7Headers=(context,info,windowMs)=>{if(context.finalized)return;const windowSeconds=Math.ceil(windowMs/1e3);const resetSeconds=getResetSeconds(info.resetTime,windowMs);context.header("RateLimit-Policy",`${info.limit};w=${windowSeconds}`);context.header("RateLimit",`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`)};const setRetryAfterHeader=(context,info,windowMs)=>{if(context.finalized)return;const resetSeconds=getResetSeconds(info.resetTime,windowMs);context.header("Retry-After",resetSeconds==null?void 0:resetSeconds.toString())};
1
+ const getResetSeconds=(resetTime,windowMs)=>{let resetSeconds;if(resetTime){const deltaSeconds=Math.ceil((resetTime.getTime()-Date.now())/1e3);resetSeconds=Math.max(0,deltaSeconds)}else if(windowMs){resetSeconds=Math.ceil(windowMs/1e3)}return resetSeconds};export const setDraft6Headers=(context,info,windowMs)=>{if(context.finalized)return;const windowSeconds=Math.ceil(windowMs/1e3);const resetSeconds=getResetSeconds(info.resetTime);context.header("RateLimit-Policy",`${info.limit};w=${windowSeconds}`);context.header("RateLimit-Limit",info.limit.toString());context.header("RateLimit-Remaining",info.remaining.toString());if(resetSeconds)context.header("RateLimit-Reset",resetSeconds.toString())};export const setDraft7Headers=(context,info,windowMs)=>{if(context.finalized)return;const windowSeconds=Math.ceil(windowMs/1e3);const resetSeconds=getResetSeconds(info.resetTime,windowMs);context.header("RateLimit-Policy",`${info.limit};w=${windowSeconds}`);context.header("RateLimit",`limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`)};export const setRetryAfterHeader=(context,info,windowMs)=>{if(context.finalized)return;const resetSeconds=getResetSeconds(info.resetTime,windowMs);context.header("Retry-After",resetSeconds?.toString())};
@@ -10,4 +10,4 @@ import type { ConfigType } from "../types";
10
10
  *
11
11
  * @public
12
12
  */
13
- export declare function rateLimiter<E extends Env, P extends string, I extends Input>(config?: Partial<ConfigType<E, P, I>>): import("hono").MiddlewareHandler<E, P, I>;
13
+ export declare function rateLimiter<E extends Env = Env, P extends string = string, I extends Input = Input>(config?: Partial<ConfigType<E, P, I>>): import("hono").MiddlewareHandler<E, P, I>;
package/src/core/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"rateLimiter",{enumerable:true,get:function(){return rateLimiter}});const _interop_require_default=require("@swc/helpers/_/_interop_require_default");const _factory=require("hono/factory");const _memorystore=_interop_require_default._(require("../memory-store"));const _defaultKeyGenerator=require("./defaultKeyGenerator");const _headers=require("./headers");const _validations=require("./validations");function rateLimiter(config){const{windowMs=6e4,limit=5,message="Too many requests, please try again later.",statusCode=429,standardHeaders="draft-6",requestPropertyName="rateLimit",skipFailedRequests=false,skipSuccessfulRequests=false,keyGenerator=_defaultKeyGenerator.defaultKeyGenerator,skip=()=>false,requestWasSuccessful=c=>c.res.status<400,handler=async(c,_next,options)=>{c.status(options.statusCode);const responseMessage=typeof options.message==="function"?await options.message(c):options.message;if(typeof responseMessage==="string")return c.text(responseMessage);return c.json(responseMessage)},store=new _memorystore.default}=config!=null?config:{};const options={windowMs,limit,message,statusCode,standardHeaders,requestPropertyName,skipFailedRequests,skipSuccessfulRequests,keyGenerator,skip,requestWasSuccessful,handler,store};if(!(0,_validations.isValidStore)(store))throw new Error("The store is not correctly implmented!");if(typeof store.init==="function")store.init(options);return(0,_factory.createMiddleware)(async(c,next)=>{const isSkippable=await skip(c);if(isSkippable){await next();return}const key=await keyGenerator(c);const{totalHits,resetTime}=await store.increment(key);const retrieveLimit=typeof limit==="function"?limit(c):limit;const _limit=await retrieveLimit;const info={limit:_limit,used:totalHits,remaining:Math.max(_limit-totalHits,0),resetTime};c.set(requestPropertyName,info);if(standardHeaders&&!c.finalized){if(standardHeaders==="draft-7"){(0,_headers.setDraft7Headers)(c,info,windowMs)}else{(0,_headers.setDraft6Headers)(c,info,windowMs)}}let decremented=false;const decrementKey=async()=>{if(!decremented){await store.decrement(key);decremented=true}};const shouldSkipRequest=async()=>{if(skipFailedRequests||skipSuccessfulRequests){const wasRequestSuccessful=await requestWasSuccessful(c);if(skipFailedRequests&&!wasRequestSuccessful||skipSuccessfulRequests&&wasRequestSuccessful)await decrementKey()}};if(totalHits>_limit){if(standardHeaders){(0,_headers.setRetryAfterHeader)(c,info,windowMs)}await shouldSkipRequest();return handler(c,next,options)}try{await next();await shouldSkipRequest()}catch(error){if(skipFailedRequests)await decrementKey()}finally{if(!c.finalized)await decrementKey()}})}
1
+ import{createMiddleware}from"hono/factory";import MemoryStore from"../memory-store";import{defaultKeyGenerator}from"./defaultKeyGenerator";import{setDraft6Headers,setDraft7Headers,setRetryAfterHeader}from"./headers";import{isValidStore}from"./validations";export function rateLimiter(config){const{windowMs=6e4,limit=5,message="Too many requests, please try again later.",statusCode=429,standardHeaders="draft-6",requestPropertyName="rateLimit",requestStorePropertyName="rateLimitStore",skipFailedRequests=false,skipSuccessfulRequests=false,keyGenerator=defaultKeyGenerator,skip=()=>false,requestWasSuccessful=c=>c.res.status<400,handler=async(c,_next,options)=>{c.status(options.statusCode);const responseMessage=typeof options.message==="function"?await options.message(c):options.message;if(typeof responseMessage==="string")return c.text(responseMessage);return c.json(responseMessage)},store=new MemoryStore}=config??{};const options={windowMs,limit,message,statusCode,standardHeaders,requestPropertyName,requestStorePropertyName,skipFailedRequests,skipSuccessfulRequests,keyGenerator,skip,requestWasSuccessful,handler,store};if(!isValidStore(store))throw new Error("The store is not correctly implmented!");if(typeof store.init==="function")store.init(options);return createMiddleware(async(c,next)=>{const isSkippable=await skip(c);if(isSkippable){await next();return}const key=await keyGenerator(c);const{totalHits,resetTime}=await store.increment(key);const retrieveLimit=typeof limit==="function"?limit(c):limit;const _limit=await retrieveLimit;const info={limit:_limit,used:totalHits,remaining:Math.max(_limit-totalHits,0),resetTime};c.set(requestPropertyName,info);c.set(requestStorePropertyName,{getKey:store.get?.bind(store),resetKey:store.resetKey.bind(store)});if(standardHeaders&&!c.finalized){if(standardHeaders==="draft-7"){setDraft7Headers(c,info,windowMs)}else{setDraft6Headers(c,info,windowMs)}}let decremented=false;const decrementKey=async()=>{if(!decremented){await store.decrement(key);decremented=true}};const shouldSkipRequest=async()=>{if(skipFailedRequests||skipSuccessfulRequests){const wasRequestSuccessful=await requestWasSuccessful(c);if(skipFailedRequests&&!wasRequestSuccessful||skipSuccessfulRequests&&wasRequestSuccessful)await decrementKey()}};if(totalHits>_limit){if(standardHeaders){setRetryAfterHeader(c,info,windowMs)}await shouldSkipRequest();return handler(c,next,options)}try{await next();await shouldSkipRequest()}catch(error){if(skipFailedRequests)await decrementKey()}finally{if(!c.finalized)await decrementKey()}})}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"isValidStore",{enumerable:true,get:function(){return isValidStore}});const isValidStore=value=>!!(value==null?void 0:value.increment);
1
+ export const isValidStore=value=>!!value?.increment;
package/src/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./core";
1
+ export { rateLimiter as default, rateLimiter } from "./core";
2
2
  export type * from "./types";
package/src/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});const _export_star=require("@swc/helpers/_/_export_star");_export_star._(require("./core"),exports);
1
+ export{rateLimiter as default,rateLimiter}from"./core";
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:all[name]})}_export(exports,{MemoryStore:function(){return MemoryStore},default:function(){return _default}});let MemoryStore=class MemoryStore{init(options){this.windowMs=options.windowMs;if(this.interval)clearInterval(this.interval);this.interval=setInterval(()=>{this.clearExpired()},this.windowMs);if(this.interval.unref)this.interval.unref()}async get(key){var _this_current_get;return(_this_current_get=this.current.get(key))!=null?_this_current_get:this.previous.get(key)}async increment(key){const client=this.getClient(key);const now=Date.now();if(client.resetTime.getTime()<=now){this.resetClient(client,now)}client.totalHits++;return client}async decrement(key){const client=this.getClient(key);if(client.totalHits>0)client.totalHits--}async resetKey(key){this.current.delete(key);this.previous.delete(key)}async resetAll(){this.current.clear();this.previous.clear()}shutdown(){clearInterval(this.interval);void this.resetAll()}resetClient(client,now=Date.now()){client.totalHits=0;client.resetTime.setTime(now+this.windowMs);return client}getClient(key){if(this.current.has(key))return this.current.get(key);let client;if(this.previous.has(key)){client=this.previous.get(key);this.previous.delete(key)}else{client={totalHits:0,resetTime:new Date};this.resetClient(client)}this.current.set(key,client);return client}clearExpired(){this.previous=this.current;this.current=new Map}constructor(){this.previous=new Map;this.current=new Map;this.localKeys=true}};const _default=MemoryStore;
1
+ export class MemoryStore{init(options){this.windowMs=options.windowMs;if(this.interval)clearInterval(this.interval);this.interval=setInterval(()=>{this.clearExpired()},this.windowMs);if(this.interval.unref)this.interval.unref()}async get(key){return this.current.get(key)??this.previous.get(key)}async increment(key){const client=this.getClient(key);const now=Date.now();if(client.resetTime.getTime()<=now){this.resetClient(client,now)}client.totalHits++;return client}async decrement(key){const client=this.getClient(key);if(client.totalHits>0)client.totalHits--}async resetKey(key){this.current.delete(key);this.previous.delete(key)}async resetAll(){this.current.clear();this.previous.clear()}shutdown(){clearInterval(this.interval);void this.resetAll()}resetClient(client,now=Date.now()){client.totalHits=0;client.resetTime.setTime(now+this.windowMs);return client}getClient(key){if(this.current.has(key))return this.current.get(key);let client;if(this.previous.has(key)){client=this.previous.get(key);this.previous.delete(key)}else{client={totalHits:0,resetTime:new Date};this.resetClient(client)}this.current.set(key,client);return client}clearExpired(){this.previous=this.current;this.current=new Map}constructor(){this.previous=new Map;this.current=new Map;this.localKeys=true}}export default MemoryStore;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -36,7 +36,7 @@ export type ConfigType<E extends Env = any, P extends string = any, I extends In
36
36
  *
37
37
  * Defaults to `'Too many requests, please try again later.'`
38
38
  */
39
- message: string | JSON | ((c: Context<E, P, I>) => Promisify<string | JSON>);
39
+ message: string | Record<string, unknown> | ((c: Context<E, P, I>) => Promisify<string | Record<string, unknown>>);
40
40
  /**
41
41
  * The HTTP status code to send back when a client is rate limited.
42
42
  *
@@ -50,11 +50,17 @@ export type ConfigType<E extends Env = any, P extends string = any, I extends In
50
50
  */
51
51
  standardHeaders: boolean | "draft-6" | "draft-7";
52
52
  /**
53
- * The name of the property on the request object to store the rate limit info.
53
+ * The name of the property on the context object to store the rate limit info.
54
54
  *
55
55
  * Defaults to `rateLimit`.
56
56
  */
57
57
  requestPropertyName: string;
58
+ /**
59
+ * The name of the property on the context object to store the Data Store instance.
60
+ *
61
+ * Defaults to `rateLimitStore`.
62
+ */
63
+ requestStorePropertyName: string;
58
64
  /**
59
65
  * If `true`, the library will (by default) skip all requests that have a 4XX
60
66
  * or 5XX status.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
1
+ export{};
@@ -1,2 +0,0 @@
1
- export * from "./types.js";
2
- export { default, RedisStore } from "./store.js";
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:all[name]})}_export(exports,{RedisStore:function(){return _store.RedisStore},default:function(){return _store.default}});const _export_star=require("@swc/helpers/_/_export_star");const _interop_require_wildcard=require("@swc/helpers/_/_interop_require_wildcard");_export_star._(require("./types.js"),exports);const _store=_interop_require_wildcard._(require("./store.js"));
@@ -1,9 +0,0 @@
1
- /**
2
- * The lua scripts, used to make consecutive queries on the same key and avoid
3
- * race conditions by doing all the work on the redis server.
4
- */
5
- declare const scripts: {
6
- increment: string;
7
- get: string;
8
- };
9
- export default scripts;
@@ -1,16 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"default",{enumerable:true,get:function(){return _default}});const scripts={increment:`
2
- local totalHits = redis.call("INCR", KEYS[1])
3
- local timeToExpire = redis.call("PTTL", KEYS[1])
4
- if timeToExpire <= 0 or ARGV[1] == "1"
5
- then
6
- redis.call("PEXPIRE", KEYS[1], tonumber(ARGV[2]))
7
- timeToExpire = tonumber(ARGV[2])
8
- end
9
-
10
- return { totalHits, timeToExpire }
11
- `.replaceAll(/^\s+/gm,"").trim(),get:`
12
- local totalHits = redis.call("GET", KEYS[1])
13
- local timeToExpire = redis.call("PTTL", KEYS[1])
14
-
15
- return { totalHits, timeToExpire }
16
- `.replaceAll(/^\s+/gm,"").trim()};const _default=scripts;
@@ -1,92 +0,0 @@
1
- import type { ClientRateLimitInfo, IncrementResponse, ConfigType as RateLimitConfiguration, Store } from "../types";
2
- import type { Options, RedisReply, SendCommandFn } from "./types.js";
3
- /**
4
- * A `Store` for the `express-rate-limit` package that stores hit counts in
5
- * Redis.
6
- */
7
- export declare class RedisStore implements Store {
8
- /**
9
- * The function used to send raw commands to Redis.
10
- */
11
- sendCommand: SendCommandFn;
12
- /**
13
- * The text to prepend to the key in Redis.
14
- */
15
- prefix: string;
16
- /**
17
- * Whether to reset the expiry for a particular key whenever its hit count
18
- * changes.
19
- */
20
- resetExpiryOnChange: boolean;
21
- /**
22
- * Stores the loaded SHA1s of the LUA scripts used for executing the increment
23
- * and get key operations.
24
- */
25
- incrementScriptSha: Promise<string>;
26
- getScriptSha: Promise<string>;
27
- /**
28
- * The number of milliseconds to remember that user's requests.
29
- */
30
- windowMs: number;
31
- /**
32
- * @constructor for `RedisStore`.
33
- *
34
- * @param options {Options} - The configuration options for the store.
35
- */
36
- constructor(options: Options);
37
- /**
38
- * Loads the script used to increment a client's hit count.
39
- */
40
- loadIncrementScript(): Promise<string>;
41
- /**
42
- * Loads the script used to fetch a client's hit count and expiry time.
43
- */
44
- loadGetScript(): Promise<string>;
45
- /**
46
- * Runs the increment command, and retries it if the script is not loaded.
47
- */
48
- retryableIncrement(key: string): Promise<RedisReply>;
49
- /**
50
- * Method to prefix the keys with the given text.
51
- *
52
- * @param key {string} - The key.
53
- *
54
- * @returns {string} - The text + the key.
55
- */
56
- prefixKey(key: string): string;
57
- /**
58
- * Method that actually initializes the store.
59
- *
60
- * @param options {RateLimitConfiguration} - The options used to setup the middleware.
61
- */
62
- init(options: RateLimitConfiguration): void;
63
- /**
64
- * Method to fetch a client's hit count and reset time.
65
- *
66
- * @param key {string} - The identifier for a client.
67
- *
68
- * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
69
- */
70
- get(key: string): Promise<ClientRateLimitInfo | undefined>;
71
- /**
72
- * Method to increment a client's hit counter.
73
- *
74
- * @param key {string} - The identifier for a client
75
- *
76
- * @returns {IncrementResponse} - The number of hits and reset time for that client
77
- */
78
- increment(key: string): Promise<IncrementResponse>;
79
- /**
80
- * Method to decrement a client's hit counter.
81
- *
82
- * @param key {string} - The identifier for a client
83
- */
84
- decrement(key: string): Promise<void>;
85
- /**
86
- * Method to reset a client's hit counter.
87
- *
88
- * @param key {string} - The identifier for a client
89
- */
90
- resetKey(key: string): Promise<void>;
91
- }
92
- export default RedisStore;
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:all[name]})}_export(exports,{RedisStore:function(){return RedisStore},default:function(){return _default}});const _interop_require_default=require("@swc/helpers/_/_interop_require_default");const _scripts=_interop_require_default._(require("./scripts.js"));const toInt=input=>{if(typeof input==="number")return input;return Number.parseInt((input!=null?input:"").toString(),10)};const parseScriptResponse=results=>{if(!Array.isArray(results))throw new TypeError("Expected result to be array of values");if(results.length!==2)throw new Error(`Expected 2 replies, got ${results.length}`);const totalHits=results[0]===false?0:toInt(results[0]);const timeToExpire=toInt(results[1]);const resetTime=new Date(Date.now()+timeToExpire);return{totalHits,resetTime}};let RedisStore=class RedisStore{async loadIncrementScript(){const result=await this.sendCommand("SCRIPT","LOAD",_scripts.default.increment);if(typeof result!=="string"){throw new TypeError("unexpected reply from redis client")}return result}async loadGetScript(){const result=await this.sendCommand("SCRIPT","LOAD",_scripts.default.get);if(typeof result!=="string"){throw new TypeError("unexpected reply from redis client")}return result}async retryableIncrement(key){const evalCommand=async()=>this.sendCommand("EVALSHA",await this.incrementScriptSha,"1",this.prefixKey(key),this.resetExpiryOnChange?"1":"0",this.windowMs.toString());try{const result=await evalCommand();return result}catch(e){this.incrementScriptSha=this.loadIncrementScript();return evalCommand()}}prefixKey(key){return`${this.prefix}${key}`}init(options){this.windowMs=options.windowMs}async get(key){const results=await this.sendCommand("EVALSHA",await this.getScriptSha,"1",this.prefixKey(key));return parseScriptResponse(results)}async increment(key){const results=await this.retryableIncrement(key);return parseScriptResponse(results)}async decrement(key){await this.sendCommand("DECR",this.prefixKey(key))}async resetKey(key){await this.sendCommand("DEL",this.prefixKey(key))}constructor(options){this.sendCommand=options.sendCommand;var _options_prefix;this.prefix=(_options_prefix=options.prefix)!=null?_options_prefix:"rl:";var _options_resetExpiryOnChange;this.resetExpiryOnChange=(_options_resetExpiryOnChange=options.resetExpiryOnChange)!=null?_options_resetExpiryOnChange:false;this.incrementScriptSha=this.loadIncrementScript();this.getScriptSha=this.loadGetScript()}};const _default=RedisStore;
@@ -1,29 +0,0 @@
1
- /**
2
- * The type of data Redis might return to us.
3
- */
4
- type Data = boolean | number | string;
5
- export type RedisReply = Data | Data[];
6
- /**
7
- * The library sends Redis raw commands, so all we need to know are the
8
- * 'raw-command-sending' functions for each redis client.
9
- */
10
- export type SendCommandFn = (...args: string[]) => Promise<RedisReply>;
11
- /**
12
- * The configuration options for the store.
13
- */
14
- export type Options = {
15
- /**
16
- * The function used to send commands to Redis.
17
- */
18
- readonly sendCommand: SendCommandFn;
19
- /**
20
- * The text to prepend to the key in Redis.
21
- */
22
- readonly prefix?: string;
23
- /**
24
- * Whether to reset the expiry for a particular key whenever its hit count
25
- * changes.
26
- */
27
- readonly resetExpiryOnChange?: boolean;
28
- };
29
- export {};
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});