-
Notifications
You must be signed in to change notification settings - Fork 2
/
rate-limit.ts
146 lines (128 loc) · 3.3 KB
/
rate-limit.ts
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
export class RefillingTokenBucket<_Key> {
public max: number;
public refillIntervalSeconds: number;
constructor(max: number, refillIntervalSeconds: number) {
this.max = max;
this.refillIntervalSeconds = refillIntervalSeconds;
}
private storage = new Map<_Key, RefillBucket>();
public check(key: _Key, cost: number): boolean {
const bucket = this.storage.get(key) ?? null;
if (bucket === null) {
return true;
}
const now = Date.now();
const refill = Math.floor((now - bucket.refilledAt) / (this.refillIntervalSeconds * 1000));
if (refill > 0) {
return Math.min(bucket.count + refill, this.max) >= cost;
}
return bucket.count >= cost;
}
public consume(key: _Key, cost: number): boolean {
let bucket = this.storage.get(key) ?? null;
const now = Date.now();
if (bucket === null) {
bucket = {
count: this.max - cost,
refilledAt: now
};
this.storage.set(key, bucket);
return true;
}
const refill = Math.floor((now - bucket.refilledAt) / (this.refillIntervalSeconds * 1000));
bucket.count = Math.min(bucket.count + refill, this.max);
bucket.refilledAt = now;
if (bucket.count < cost) {
return false;
}
bucket.count -= cost;
this.storage.set(key, bucket);
return true;
}
}
export class Throttler<_Key> {
public timeoutSeconds: number[];
private storage = new Map<_Key, ThrottlingCounter>();
constructor(timeoutSeconds: number[]) {
this.timeoutSeconds = timeoutSeconds;
}
public consume(key: _Key): boolean {
let counter = this.storage.get(key) ?? null;
const now = Date.now();
if (counter === null) {
counter = {
timeout: 0,
updatedAt: now
};
this.storage.set(key, counter);
return true;
}
const allowed = now - counter.updatedAt >= this.timeoutSeconds[counter.timeout] * 1000;
if (!allowed) {
return false;
}
counter.updatedAt = now;
counter.timeout = Math.min(counter.timeout + 1, this.timeoutSeconds.length - 1);
this.storage.set(key, counter);
return true;
}
public reset(key: _Key): void {
this.storage.delete(key);
}
}
export class ExpiringTokenBucket<_Key> {
public max: number;
public expiresInSeconds: number;
private storage = new Map<_Key, ExpiringBucket>();
constructor(max: number, expiresInSeconds: number) {
this.max = max;
this.expiresInSeconds = expiresInSeconds;
}
public check(key: _Key, cost: number): boolean {
const bucket = this.storage.get(key) ?? null;
const now = Date.now();
if (bucket === null) {
return true;
}
if (now - bucket.createdAt >= this.expiresInSeconds * 1000) {
return true;
}
return bucket.count >= cost;
}
public consume(key: _Key, cost: number): boolean {
let bucket = this.storage.get(key) ?? null;
const now = Date.now();
if (bucket === null) {
bucket = {
count: this.max - cost,
createdAt: now
};
this.storage.set(key, bucket);
return true;
}
if (now - bucket.createdAt >= this.expiresInSeconds * 1000) {
bucket.count = this.max;
}
if (bucket.count < cost) {
return false;
}
bucket.count -= cost;
this.storage.set(key, bucket);
return true;
}
public reset(key: _Key): void {
this.storage.delete(key);
}
}
interface RefillBucket {
count: number;
refilledAt: number;
}
interface ExpiringBucket {
count: number;
createdAt: number;
}
interface ThrottlingCounter {
timeout: number;
updatedAt: number;
}