@withjoy/limiter 0.1.1 → 0.1.3

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