@withjoy/limiter 0.1.7 → 0.2.0

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
@@ -24,16 +24,16 @@ Your client can use any subset of these Buckets, but when it does, the Best Prac
24
24
  However, for flexibility reasons, this is not strictly enforced.
25
25
 
26
26
  ## Why limitd-redis ?
27
- Although this package is available on npm, it's no longer actively maintained. We've observed bugs in the existing published version, but these issues have already been resolved in the [limitd-redis](https://github.com/auth0/limitd-redis) gitHub repository. To address this, we've imported the latest stable version(v7.8.1) from there github repository and using it locally in the limiter.
27
+ Although this package is available on npm, it's no longer actively maintained. We've observed bugs in the existing published version, but these issues have already been resolved in the [limitd-redis](https://github.com/auth0/limitd-redis) gitHub repository. To address this, we've imported the latest stable version(v7.8.1) from there github repository and using it locally in the `limiter`.
28
28
 
29
29
  In case we need to update in the future, we can replace the "limitd-redis" directory with the latest version of [limitd-redis](https://github.com/auth0/limitd-redis) from their GitHub repository.
30
30
 
31
- Note: We need to add dependency from limitd-redis to limiter package json before publish. We are using limitd-redis as directly not package inside limiter.
31
+ Note: We need to add dependency from `limitd-redis` to `limiter` package json before publish. We are using `limitd-redis` as directly not package inside `limiter`.
32
32
 
33
33
  ## Testing:
34
34
 
35
35
  `npm run test` will run offline tests using a mock.
36
- `npm run sanity-check` will run against a limitd service configured to run with the test buckets.
36
+ `npm run sanity-check` will run against a `limitd` service configured to run with the test buckets.
37
37
 
38
38
 
39
39
  ## Quick Test
package/limiter.js CHANGED
@@ -89,12 +89,36 @@ const limiterRetryConfig = z
89
89
  .strict()
90
90
  .default(LIMITER_RETRY_DEFAULTS);
91
91
 
92
+ // from https://github.com/auth0/limitd-redis#buckets
93
+ const limiterBucketConfig = z.object({
94
+ size: z.number(),
95
+ per_day: z.number().optional(),
96
+ per_hour: z.number().optional(),
97
+ per_minute: z.number().optional(),
98
+ per_second: z.number().optional(),
99
+ // and that's all we care about
100
+ // we need more? implement more of the `limitd-redis` schema
101
+ });
102
+
103
+ const limiterConfig = z
104
+ .object({
105
+ limitdUrl: z.string(),
106
+ // the rest are optional / have defaults
107
+ buckets: z.record(z.string(), limiterBucketConfig).optional(),
108
+ keyPrefix: z.string().default(defaultRedisKeyPrefix),
109
+ circuitbreaker: limiterCircuitBreakerConfig,
110
+ retry: limiterRetryConfig,
111
+ commandTimeout: z.number().default(30),
112
+ })
113
+ .strict();
114
+ ;
115
+
92
116
  class Limiter {
93
117
  // static defaultBuckets;
94
118
  // static pickDefaultBuckets(keys) { ... };
95
119
 
96
120
  constructor(config) {
97
- this._config = config;
121
+ this._config = limiterConfig.parse(config);
98
122
  this._limitdRedisConnectionPromise = this.connect();
99
123
  }
100
124
 
@@ -105,12 +129,12 @@ class Limiter {
105
129
  */
106
130
  connect() {
107
131
  return new Promise((resolve, reject) => {
108
- let buckets = this._config.buckets || {};
132
+ let buckets = this._config.buckets || {};
109
133
  buckets = {
110
134
  ...defaultBuckets,
111
135
  ...buckets,
112
136
  };
113
- const keyPrefix = this._config.keyPrefix || defaultRedisKeyPrefix;
137
+ const keyPrefix = this._config.keyPrefix;
114
138
  if (!connection) {
115
139
  connection = new LimitdRedis({
116
140
  uri: this._config.limitdUrl,
@@ -137,6 +161,9 @@ let buckets = this._config.buckets || {};
137
161
  close() {
138
162
  return new Promise(async (resolve, reject) => {
139
163
  const connectionPromise = await this._limitdRedisConnectionPromise;
164
+ if (!connectionPromise) {
165
+ return resolve(null);
166
+ }
140
167
  connectionPromise.close((err, resp) => {
141
168
  if (err) {
142
169
  this._console.error(err);
@@ -254,7 +281,11 @@ Limiter.pickDefaultBuckets = function pickDefaultBuckets(keyArray) {
254
281
  }
255
282
  return buckets;
256
283
  };
284
+
285
+ // schemas
257
286
  Limiter.limiterCircuitBreakerConfig = limiterCircuitBreakerConfig;
258
287
  Limiter.limiterRetryConfig = limiterRetryConfig;
288
+ Limiter.limiterBucketConfig = limiterBucketConfig;
289
+ Limiter.limiterConfig = limiterConfig;
259
290
 
260
291
  module.exports = Limiter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withjoy/limiter",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "Api Rate limiter",
5
5
  "main": "limiter.js",
6
6
  "scripts": {
@@ -54,7 +54,109 @@ const limiterWithMockService = async (succeeds = true, conforms = true) => {
54
54
  return limiterProcessor;
55
55
  };
56
56
 
57
- test("perfrom limitd-redis testing with test bucket", async (done) => {
57
+ describe("Limiter", () => {
58
+ let limiter;
59
+ beforeEach(() => {
60
+ jest.spyOn(Limiter.prototype, "connect").mockImplementation(async () => Promise.resolve());
61
+ });
62
+ afterEach(async () => {
63
+ await limiter.close();
64
+ jest.restoreAllMocks();
65
+ });
66
+
67
+ describe("constructor", () => {
68
+ it('accepts maximal valid configuration', () => {
69
+ limiter = new Limiter({
70
+ limitdUrl: 'localhost:6379',
71
+ buckets: {
72
+ ...TEST_BUCKETS,
73
+ perDay: { size: 11, per_day: 11 },
74
+ perMinute: { size: 21, per_minute: 21 },
75
+ perSecond: { size: 31, per_second: 31 },
76
+ },
77
+ keyPrefix: 'maximal:',
78
+ circuitbreaker: {
79
+ timeout: '41s',
80
+ maxFailures: 42,
81
+ cooldown: '43s',
82
+ maxCooldown: '44s',
83
+ name: 'maximal',
84
+ },
85
+ retry: {
86
+ retries: 51,
87
+ minTimeout: 52,
88
+ maxTimeout: 53,
89
+ },
90
+ commandTimeout: 61,
91
+ });
92
+ expect(limiter._config.limitdUrl).toBe('localhost:6379');
93
+ expect(limiter._config.buckets.perDay.size).toBe(11);
94
+ expect(limiter._config.keyPrefix).toBe('maximal:');
95
+ expect(limiter._config.circuitbreaker.timeout).toBe('41s');
96
+ expect(limiter._config.retry.retries).toBe(51);
97
+ expect(limiter._config.commandTimeout).toBe(61);
98
+ });
99
+
100
+ it("accepts minimum valid configuration", async () => {
101
+ limiter = new Limiter({
102
+ limitdUrl: 'localhost:6379',
103
+ });
104
+ expect(limiter._config.limitdUrl).toBe('localhost:6379');
105
+ expect(limiter._config.buckets).toBeUndefined();
106
+ expect(limiter._config.keyPrefix).toBe('limitdRedis:');
107
+ expect(limiter._config.circuitbreaker.timeout).toBe('0.50s');
108
+ expect(limiter._config.retry.retries).toBe(3);
109
+ expect(limiter._config.commandTimeout).toBe(30);
110
+ });
111
+
112
+ it("fails on invalid bucket configuration", () => {
113
+ expect(() => {
114
+ limiter = new Limiter({
115
+ limitdUrl: 'localhost:6379',
116
+ buckets: {
117
+ invalidBucket: { size: 'string' }, // Poisoning with an invalid size
118
+ },
119
+ });
120
+ }).toThrow(/"invalidBucket"/);
121
+ });
122
+
123
+ it("cannot fail on invalid circuitbreaker configuration", () => {
124
+ expect(() => {
125
+ limiter = new Limiter({
126
+ limitdUrl: 'localhost:6379',
127
+ circuitbreaker: {
128
+ timeout: 'still-valid',
129
+ },
130
+ });
131
+ }).not.toThrow();
132
+ });
133
+
134
+ it("fails on invalid retry configuration", () => {
135
+ expect(() => {
136
+ limiter = new Limiter({
137
+ limitdUrl: 'localhost:6379',
138
+ retry: {
139
+ retries: 'not-a-number', // Poisoning with an invalid number of retries
140
+ minTimeout: 52,
141
+ maxTimeout: 53,
142
+ },
143
+ });
144
+ }).toThrow(/"retries"/);
145
+ });
146
+
147
+ it("fails when limitdUrl is missing", () => {
148
+ expect(() => {
149
+ limiter = new Limiter({
150
+ buckets: {
151
+ emails: { size: 10 },
152
+ },
153
+ });
154
+ }).toThrow(/"limitdUrl"/);
155
+ });
156
+ });
157
+ });
158
+
159
+ test("perform limitd-redis testing with test bucket", async (done) => {
58
160
  const limiter = new Limiter({
59
161
  limitdUrl: "localhost:6379",
60
162
  buckets: {