forked from express-rate-limit/express-rate-limit
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmemory-store.ts
More file actions
156 lines (135 loc) · 3.42 KB
/
memory-store.ts
File metadata and controls
156 lines (135 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// /source/memory-store.ts
// A memory store for hit counts
import type { Store, Options, IncrementResponse } from './types.js'
/**
* Calculates the time when all hit counters will be reset.
*
* @param windowMs {number} - The duration of a window (in milliseconds).
*
* @returns {Date}
*
* @private
*/
const calculateNextResetTime = (windowMs: number): Date => {
const resetTime = new Date()
resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs)
return resetTime
}
/**
* A `Store` that stores the hit count for each client in memory.
*
* @public
*/
export default class MemoryStore implements Store {
/**
* The duration of time before which all hit counts are reset (in milliseconds).
*/
windowMs!: number
/**
* The map that stores the number of hits for each client in memory.
*/
hits!: {
[key: string]: number | undefined
}
/**
* The time at which all hit counts will be reset.
*/
resetTime!: Date
/**
* Reference to the active timer.
*/
interval?: NodeJS.Timer
/**
* Confirmation that the keys incremented in once instance of MemoryStore
* cannot affect other instances.
*/
localKeys = true
/**
* Method that initializes the store.
*
* @param options {Options} - The options used to setup the middleware.
*/
init(options: Options): void {
// Get the duration of a window from the options.
this.windowMs = options.windowMs
// Then calculate the reset time using that.
this.resetTime = calculateNextResetTime(this.windowMs)
// Initialise the hit counter map.
this.hits = {}
// Reset hit counts for ALL clients every `windowMs` - this will also
// re-calculate the `resetTime`
this.interval = setInterval(async () => {
await this.resetAll()
}, this.windowMs)
// Cleaning up the interval will be taken care of by the `shutdown` method.
if (this.interval.unref) this.interval.unref()
}
/**
* Method to increment a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @returns {IncrementResponse} - The number of hits and reset time for that client.
*
* @public
*/
async increment(key: string): Promise<IncrementResponse> {
const totalHits = (this.hits[key] ?? 0) + 1
this.hits[key] = totalHits
return {
totalHits,
resetTime: this.resetTime,
}
}
async incrementBy(key: string, score: number): Promise<IncrementResponse> {
const totalHits = (this.hits[key] ?? 0) + score
this.hits[key] = totalHits
return {
totalHits,
resetTime: this.resetTime,
}
}
/**
* Method to decrement a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async decrement(key: string): Promise<void> {
const current = this.hits[key]
if (current) this.hits[key] = current - 1
}
async decrementBy(key: string, score: number): Promise<void> {
const current = this.hits[key]
if (current) this.hits[key] = current - score
}
/**
* Method to reset a client's hit counter.
*
* @param key {string} - The identifier for a client.
*
* @public
*/
async resetKey(key: string): Promise<void> {
delete this.hits[key]
}
/**
* Method to reset everyone's hit counter.
*
* @public
*/
async resetAll(): Promise<void> {
this.hits = {}
this.resetTime = calculateNextResetTime(this.windowMs)
}
/**
* Method to stop the timer (if currently running) and prevent any memory
* leaks.
*
* @public
*/
shutdown(): void {
clearInterval(this.interval)
}
}