@withjoy/limiter 0.1.1 → 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/limiter.js +29 -18
- package/package.json +1 -1
- package/tests/limiter.test.js +77 -78
- package/tests/performTestWithTestPerMinute.js +5 -4
- package/tests/sanityCheck.js +3 -2
package/limiter.js
CHANGED
|
@@ -42,10 +42,8 @@ class Limiter {
|
|
|
42
42
|
// static pickDefaultBuckets(keys) { ... };
|
|
43
43
|
|
|
44
44
|
constructor(config) {
|
|
45
|
-
this._console = config.console;
|
|
46
45
|
this._config = config;
|
|
47
|
-
this.
|
|
48
|
-
this._limitdRedis = this.connect();
|
|
46
|
+
this._limitdRedisConnection = this.connect();
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
/*
|
|
@@ -67,10 +65,35 @@ class Limiter {
|
|
|
67
65
|
keyPrefix,
|
|
68
66
|
});
|
|
69
67
|
}
|
|
70
|
-
this._limitdRedis = connection;
|
|
71
68
|
return connection;
|
|
72
69
|
}
|
|
73
70
|
|
|
71
|
+
getLimiterProcessor(console) {
|
|
72
|
+
return new LimiterProcessor(console);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
close() {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
this._limitdRedisConnection.close((err, resp) => {
|
|
78
|
+
if (err) {
|
|
79
|
+
this._console.error(err);
|
|
80
|
+
return reject(err);
|
|
81
|
+
}
|
|
82
|
+
console.log("Connection closed");
|
|
83
|
+
resolve(resp);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class LimiterProcessor {
|
|
90
|
+
|
|
91
|
+
constructor(console) {
|
|
92
|
+
this._console = console;
|
|
93
|
+
this._operationList = [];
|
|
94
|
+
this._limitdRedis = connection;
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
/*
|
|
75
98
|
Go through the log of things we did and do the opposite
|
|
76
99
|
*/
|
|
@@ -97,18 +120,6 @@ class Limiter {
|
|
|
97
120
|
this._operationList.push(op);
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
close() {
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
this._limitdRedis.close((err, resp) => {
|
|
103
|
-
if (err) {
|
|
104
|
-
this._console.error(err);
|
|
105
|
-
return reject(err);
|
|
106
|
-
}
|
|
107
|
-
console.log("Connection closed");
|
|
108
|
-
resolve(resp);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
123
|
// promise wrapper for limitd lib put
|
|
113
124
|
_put(type, id, amount) {
|
|
114
125
|
return new Promise((resolve, reject) =>
|
|
@@ -173,12 +184,12 @@ Limiter.pickDefaultBuckets = function pickDefaultBuckets(keyArray) {
|
|
|
173
184
|
const buckets = {};
|
|
174
185
|
for (const key of keyArray) {
|
|
175
186
|
const bucket = defaultBuckets[key];
|
|
176
|
-
if (!
|
|
187
|
+
if (!bucket) {
|
|
177
188
|
throw new Error(`Limiter.pickDefaultBuckets: no such bucket; "${key}"`);
|
|
178
189
|
}
|
|
179
190
|
buckets[key] = defaultBuckets[key];
|
|
180
191
|
}
|
|
181
192
|
return buckets;
|
|
182
|
-
}
|
|
193
|
+
};
|
|
183
194
|
|
|
184
195
|
module.exports = Limiter;
|
package/package.json
CHANGED
package/tests/limiter.test.js
CHANGED
|
@@ -19,12 +19,11 @@ const TEST_BUCKETS = {
|
|
|
19
19
|
const limiterWithMockService = (succeeds = true, conforms = true) => {
|
|
20
20
|
let token_pile = 0;
|
|
21
21
|
const limiter = new Limiter({
|
|
22
|
-
console: console,
|
|
23
22
|
limitdUrl: "localhost:6379",
|
|
24
23
|
buckets: TEST_BUCKETS,
|
|
25
24
|
});
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const limiterProcessor = limiter.getLimiterProcessor(console);
|
|
26
|
+
limiterProcessor._limitdRedis = new (class MockLimitd {
|
|
28
27
|
constructor(should_succeed, should_conform) {
|
|
29
28
|
this.should_succeed = should_succeed;
|
|
30
29
|
this.should_conform = should_conform;
|
|
@@ -52,38 +51,38 @@ const limiterWithMockService = (succeeds = true, conforms = true) => {
|
|
|
52
51
|
return token_pile;
|
|
53
52
|
}
|
|
54
53
|
})(succeeds, conforms);
|
|
55
|
-
return
|
|
54
|
+
return limiterProcessor;
|
|
56
55
|
};
|
|
57
56
|
|
|
58
57
|
test("perfrom limitd-redis testing with test bucket", async (done) => {
|
|
59
58
|
const limiter = new Limiter({
|
|
60
59
|
limitdUrl: "localhost:6379",
|
|
61
|
-
console: console,
|
|
62
60
|
buckets: {
|
|
63
61
|
test: { size: 3, per_second: 2 },
|
|
64
62
|
},
|
|
65
|
-
})
|
|
63
|
+
})
|
|
64
|
+
const limiterProcessor = limiter.getLimiterProcessor(console);
|
|
66
65
|
// take 2 # Conformant - Remaining: 1
|
|
67
|
-
const result1 = await
|
|
66
|
+
const result1 = await limiterProcessor.transfer("test", "test-key", 2);
|
|
68
67
|
expect(result1).toMatchObject({
|
|
69
68
|
conformant: true,
|
|
70
69
|
remaining: 1,
|
|
71
70
|
limit: 3,
|
|
72
71
|
delayed: false,
|
|
73
72
|
});
|
|
74
|
-
await expect(
|
|
73
|
+
await expect(limiterProcessor.transfer("test", "test-key", 2)).rejects.toThrow(
|
|
75
74
|
"Non Conformant"
|
|
76
75
|
);
|
|
77
76
|
|
|
78
77
|
// Put 1
|
|
79
|
-
const result3 = await
|
|
78
|
+
const result3 = await limiterProcessor.transfer("test", "test-key", -1);
|
|
80
79
|
expect(result3).toMatchObject({
|
|
81
80
|
remaining: 2,
|
|
82
81
|
limit: 3,
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
//Now Conformant - Remaining: 2
|
|
86
|
-
const result4 = await
|
|
85
|
+
const result4 = await limiterProcessor.transfer("test", "test-key", 2);
|
|
87
86
|
expect(result4).toMatchObject({
|
|
88
87
|
conformant: true,
|
|
89
88
|
remaining: 0,
|
|
@@ -95,176 +94,176 @@ test("perfrom limitd-redis testing with test bucket", async (done) => {
|
|
|
95
94
|
});
|
|
96
95
|
|
|
97
96
|
test("Resolves to null if we aren't doing a real transfer", (done) => {
|
|
98
|
-
let
|
|
99
|
-
|
|
97
|
+
let limiterProcessor = limiterWithMockService();
|
|
98
|
+
limiterProcessor.transfer("a", "b", 0, null).then((result) => {
|
|
100
99
|
expect(result).toBe(null);
|
|
101
|
-
expect(
|
|
100
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
102
101
|
done();
|
|
103
102
|
});
|
|
104
103
|
});
|
|
105
104
|
|
|
106
105
|
test("Adds tokens if provided a negative token amount", (done) => {
|
|
107
|
-
let
|
|
108
|
-
|
|
106
|
+
let limiterProcessor = limiterWithMockService();
|
|
107
|
+
limiterProcessor.transfer("a", "b", -5).then((result) => {
|
|
109
108
|
expect(result).toMatchObject({});
|
|
110
|
-
expect(
|
|
109
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(5);
|
|
111
110
|
done();
|
|
112
111
|
});
|
|
113
112
|
});
|
|
114
113
|
|
|
115
114
|
test("Removes tokens if provided a positive token amount", (done) => {
|
|
116
|
-
let
|
|
117
|
-
|
|
115
|
+
let limiterProcessor = limiterWithMockService();
|
|
116
|
+
limiterProcessor.transfer("a", "b", 5).then((result) => {
|
|
118
117
|
expect(result).toMatchObject({ conformant: true });
|
|
119
|
-
expect(
|
|
118
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(-5);
|
|
120
119
|
done();
|
|
121
120
|
});
|
|
122
121
|
});
|
|
123
122
|
|
|
124
123
|
test("Reverts all taken tokens when revert is called", (done) => {
|
|
125
|
-
let
|
|
126
|
-
|
|
124
|
+
let limiterProcessor = limiterWithMockService();
|
|
125
|
+
limiterProcessor
|
|
127
126
|
.transfer("a", "b", 5)
|
|
128
127
|
.then((result) => {
|
|
129
128
|
expect(result).toMatchObject({ conformant: true });
|
|
130
|
-
expect(
|
|
129
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(-5);
|
|
131
130
|
})
|
|
132
|
-
.then(() =>
|
|
131
|
+
.then(() => limiterProcessor.revert())
|
|
133
132
|
.then(() => {
|
|
134
|
-
expect(
|
|
133
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
135
134
|
done();
|
|
136
135
|
});
|
|
137
136
|
});
|
|
138
137
|
|
|
139
138
|
test("Reverts all given tokens when revert is called", (done) => {
|
|
140
|
-
let
|
|
141
|
-
|
|
139
|
+
let limiterProcessor = limiterWithMockService();
|
|
140
|
+
limiterProcessor
|
|
142
141
|
.transfer("a", "b", -5)
|
|
143
142
|
.then((result) => {
|
|
144
143
|
expect(result).toMatchObject({});
|
|
145
|
-
expect(
|
|
144
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(5);
|
|
146
145
|
})
|
|
147
|
-
.then(() =>
|
|
146
|
+
.then(() => limiterProcessor.revert())
|
|
148
147
|
.then(() => {
|
|
149
|
-
expect(
|
|
148
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
150
149
|
done();
|
|
151
150
|
});
|
|
152
151
|
});
|
|
153
152
|
|
|
154
153
|
test("Reverts a series of actions when revert is called", (done) => {
|
|
155
|
-
let
|
|
156
|
-
|
|
154
|
+
let limiterProcessor = limiterWithMockService();
|
|
155
|
+
limiterProcessor
|
|
157
156
|
.transfer("a", "b", -5)
|
|
158
157
|
.then((result) => {
|
|
159
158
|
expect(result).toMatchObject({});
|
|
160
|
-
expect(
|
|
159
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(5);
|
|
161
160
|
})
|
|
162
|
-
.then(() =>
|
|
161
|
+
.then(() => limiterProcessor.transfer("a", "b", 2))
|
|
163
162
|
.then((result) => {
|
|
164
163
|
expect(result).toMatchObject({ conformant: true });
|
|
165
|
-
expect(
|
|
164
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(3);
|
|
166
165
|
})
|
|
167
|
-
.then(() =>
|
|
166
|
+
.then(() => limiterProcessor.revert())
|
|
168
167
|
.then(() => {
|
|
169
|
-
expect(
|
|
168
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
170
169
|
done();
|
|
171
170
|
});
|
|
172
171
|
});
|
|
173
172
|
|
|
174
173
|
test("Properly executes a transferAll()", (done) => {
|
|
175
|
-
let
|
|
176
|
-
|
|
174
|
+
let limiterProcessor = limiterWithMockService();
|
|
175
|
+
limiterProcessor
|
|
177
176
|
.transferAll([
|
|
178
177
|
["a", "b", -5],
|
|
179
178
|
["a", "b", 2],
|
|
180
179
|
])
|
|
181
180
|
.then((result) => {
|
|
182
181
|
expect(result).toMatchObject([{}, { conformant: true }]);
|
|
183
|
-
expect(
|
|
182
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(3);
|
|
184
183
|
})
|
|
185
|
-
.then(() =>
|
|
184
|
+
.then(() => limiterProcessor.revert())
|
|
186
185
|
.then(() => {
|
|
187
|
-
expect(
|
|
186
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
188
187
|
done();
|
|
189
188
|
});
|
|
190
189
|
});
|
|
191
190
|
|
|
192
191
|
test("Errors from the underlying service are propagated (put)", () => {
|
|
193
|
-
let
|
|
194
|
-
let endsInFailure =
|
|
192
|
+
let limiterProcessor = limiterWithMockService((succeeds = false));
|
|
193
|
+
let endsInFailure = limiterProcessor.transfer("a", "b", -5);
|
|
195
194
|
return expect(endsInFailure).rejects.toThrow(PUT_FAILED);
|
|
196
195
|
});
|
|
197
196
|
|
|
198
197
|
test("Errors from the underlying service are propagated (take)", () => {
|
|
199
|
-
let
|
|
200
|
-
let endsInFailure =
|
|
198
|
+
let limiterProcessor = limiterWithMockService((succeeds = false));
|
|
199
|
+
let endsInFailure = limiterProcessor.transfer("a", "b", 5);
|
|
201
200
|
return expect(endsInFailure).rejects.toThrow(TAKE_FAILED);
|
|
202
201
|
});
|
|
203
202
|
|
|
204
203
|
test("Non-conforming response results in an error", () => {
|
|
205
|
-
let
|
|
206
|
-
let endsInFailure =
|
|
204
|
+
let limiterProcessor = limiterWithMockService((succeeds = true), (conforms = false));
|
|
205
|
+
let endsInFailure = limiterProcessor.transfer("a", "b", 5);
|
|
207
206
|
return expect(endsInFailure).rejects.toThrow("Non Conformant");
|
|
208
207
|
});
|
|
209
208
|
|
|
210
209
|
test("An operation that fails is not logged", (done) => {
|
|
211
|
-
let
|
|
212
|
-
|
|
210
|
+
let limiterProcessor = limiterWithMockService();
|
|
211
|
+
limiterProcessor
|
|
213
212
|
.transfer("emailRate", "b", -5)
|
|
214
213
|
.then((result) => {
|
|
215
214
|
expect(result).toBe(null);
|
|
216
|
-
expect(
|
|
217
|
-
|
|
215
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(5);
|
|
216
|
+
limiterProcessor.succeeds = false; // somebody is playing games
|
|
218
217
|
})
|
|
219
|
-
.then(() =>
|
|
218
|
+
.then(() => limiterProcessor.transfer("emailRate", "b", 2))
|
|
220
219
|
.catch(() => "failed uniquely!") // do nothing!
|
|
221
220
|
.then((result) => {
|
|
222
221
|
expect(result).toBe("failed uniquely!");
|
|
223
|
-
expect(
|
|
224
|
-
|
|
222
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(5);
|
|
223
|
+
limiterProcessor.succeeds = true; // for real this time
|
|
225
224
|
})
|
|
226
|
-
.then(() =>
|
|
225
|
+
.then(() => limiterProcessor.revert())
|
|
227
226
|
.then(() => {
|
|
228
|
-
expect(
|
|
227
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
229
228
|
done();
|
|
230
229
|
});
|
|
231
230
|
});
|
|
232
231
|
|
|
233
232
|
test("Properly handles spamming revert() calls", (done) => {
|
|
234
|
-
let
|
|
235
|
-
|
|
233
|
+
let limiterProcessor = limiterWithMockService();
|
|
234
|
+
limiterProcessor
|
|
236
235
|
.transfer("a", "b", -5)
|
|
237
236
|
.then((result) => {
|
|
238
237
|
expect(result).toMatchObject({});
|
|
239
|
-
expect(
|
|
238
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(5);
|
|
240
239
|
})
|
|
241
|
-
.then(() =>
|
|
240
|
+
.then(() => limiterProcessor.transfer("a", "b", 2))
|
|
242
241
|
.then((result) => {
|
|
243
242
|
expect(result).toMatchObject({ conformant: true });
|
|
244
|
-
expect(
|
|
243
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(3);
|
|
245
244
|
})
|
|
246
245
|
.then(() =>
|
|
247
246
|
Promise.all([
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
247
|
+
limiterProcessor.revert(),
|
|
248
|
+
limiterProcessor.revert(),
|
|
249
|
+
limiterProcessor.revert(),
|
|
250
|
+
limiterProcessor.revert(),
|
|
251
|
+
limiterProcessor.revert(),
|
|
252
|
+
limiterProcessor.revert(),
|
|
253
|
+
limiterProcessor.revert(),
|
|
254
|
+
limiterProcessor.revert(),
|
|
255
|
+
limiterProcessor.revert(),
|
|
256
|
+
limiterProcessor.revert(),
|
|
257
|
+
limiterProcessor.revert(),
|
|
258
|
+
limiterProcessor.revert(),
|
|
259
|
+
limiterProcessor.revert(),
|
|
260
|
+
limiterProcessor.revert(),
|
|
261
|
+
limiterProcessor.revert(),
|
|
262
|
+
limiterProcessor.revert(),
|
|
264
263
|
])
|
|
265
264
|
)
|
|
266
265
|
.then(() => {
|
|
267
|
-
expect(
|
|
266
|
+
expect(limiterProcessor._limitdRedis._num_tokens()).toBe(0);
|
|
268
267
|
done();
|
|
269
268
|
});
|
|
270
269
|
});
|
|
@@ -6,12 +6,13 @@ async function performTestWithTestPerMinute() {
|
|
|
6
6
|
limitdUrl: "localhost:6379",
|
|
7
7
|
console: console,
|
|
8
8
|
});
|
|
9
|
+
const limiterProcessor = limiter.getLimiterProcessor(console);
|
|
9
10
|
// take 2 # Conformant - Remaining: 1
|
|
10
|
-
const result1 = await
|
|
11
|
+
const result1 = await limiterProcessor.transfer("testPerMinute", "key", 2);
|
|
11
12
|
console.log("First Turn Take Result", result1);
|
|
12
13
|
|
|
13
14
|
//Now Conformant - Remaining: 1
|
|
14
|
-
const result2 = await
|
|
15
|
+
const result2 = await limiterProcessor
|
|
15
16
|
.transfer("testPerMinute", "key", 2)
|
|
16
17
|
.catch((err) => {
|
|
17
18
|
console.log("Second Turn Take Error", err.message);
|
|
@@ -19,11 +20,11 @@ async function performTestWithTestPerMinute() {
|
|
|
19
20
|
console.log("Second Turn Take Result", result2);
|
|
20
21
|
|
|
21
22
|
// Put 1
|
|
22
|
-
const result3 = await
|
|
23
|
+
const result3 = await limiterProcessor.transfer("testPerMinute", "key", -1);
|
|
23
24
|
console.log("Third Turn PUT Result", result3);
|
|
24
25
|
|
|
25
26
|
//Now Conformant - Remaining: 2
|
|
26
|
-
const result4 = await
|
|
27
|
+
const result4 = await limiterProcessor.transfer("testPerMinute", "key", 2);
|
|
27
28
|
console.log("Fourth Turn Take Result", result4);
|
|
28
29
|
process.exit(1);
|
|
29
30
|
}
|
package/tests/sanityCheck.js
CHANGED
|
@@ -9,10 +9,11 @@ const buckets = {
|
|
|
9
9
|
|
|
10
10
|
var limiter = new Limiter({
|
|
11
11
|
limitdUrl: "localhost:6379",
|
|
12
|
-
console: console,
|
|
13
12
|
buckets,
|
|
14
13
|
});
|
|
15
14
|
|
|
15
|
+
var limiterProcessor = limiter.getLimiterProcessor(console);
|
|
16
|
+
|
|
16
17
|
var callback = function (err) {
|
|
17
18
|
if (err) {
|
|
18
19
|
console.log(err);
|
|
@@ -29,4 +30,4 @@ var withdrawls = [
|
|
|
29
30
|
["emailRate", process.argv[2], parseInt(process.argv[3])],
|
|
30
31
|
];
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
limiterProcessor.transferAll(withdrawls).then(() => callback(), callback);
|