@withjoy/limiter 0.1.3 → 0.1.5
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 +6 -0
- package/limitd-redis/LICENSE +21 -0
- package/limitd-redis/README.md +183 -0
- package/limitd-redis/docker-compose.yml +11 -0
- package/limitd-redis/index.js +2 -0
- package/limitd-redis/lib/cb.js +45 -0
- package/limitd-redis/lib/client.js +135 -0
- package/limitd-redis/lib/db.js +501 -0
- package/limitd-redis/lib/db_ping.js +106 -0
- package/limitd-redis/lib/put.lua +31 -0
- package/limitd-redis/lib/take.lua +48 -0
- package/limitd-redis/lib/utils.js +116 -0
- package/limitd-redis/lib/validation.js +64 -0
- package/limitd-redis/opslevel.yml +6 -0
- package/limitd-redis/package-lock.json +3484 -0
- package/limitd-redis/package.json +31 -0
- package/limitd-redis/test/cb.tests.js +124 -0
- package/limitd-redis/test/client.tests.js +194 -0
- package/limitd-redis/test/db.tests.js +1318 -0
- package/limitd-redis/test/validation.tests.js +124 -0
- package/limiter.js +58 -2
- package/package.json +9 -2
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const ms = require('ms');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const LRU = require('lru-cache');
|
|
4
|
+
|
|
5
|
+
const INTERVAL_TO_MS = {
|
|
6
|
+
'per_second': ms('1s'),
|
|
7
|
+
'per_minute': ms('1m'),
|
|
8
|
+
'per_hour': ms('1h'),
|
|
9
|
+
'per_day': ms('1d')
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const INTERVAL_SHORTCUTS = Object.keys(INTERVAL_TO_MS);
|
|
13
|
+
|
|
14
|
+
function normalizeTemporals(params) {
|
|
15
|
+
const type = _.pick(params, [
|
|
16
|
+
'per_interval',
|
|
17
|
+
'interval',
|
|
18
|
+
'size',
|
|
19
|
+
'unlimited',
|
|
20
|
+
'skip_n_calls'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
INTERVAL_SHORTCUTS.forEach(intervalShortcut => {
|
|
24
|
+
if (!params[intervalShortcut]) { return; }
|
|
25
|
+
type.interval = INTERVAL_TO_MS[intervalShortcut];
|
|
26
|
+
type.per_interval = params[intervalShortcut];
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (typeof type.size === 'undefined') {
|
|
30
|
+
type.size = type.per_interval;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (type.per_interval) {
|
|
34
|
+
type.ttl = ((type.size * type.interval) / type.per_interval) / 1000;
|
|
35
|
+
type.ms_per_interval = type.per_interval / type.interval;
|
|
36
|
+
type.drip_interval = type.interval / type.per_interval;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return type;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeType(params) {
|
|
43
|
+
const type = normalizeTemporals(params);
|
|
44
|
+
|
|
45
|
+
type.overridesMatch = {};
|
|
46
|
+
type.overrides = _.reduce(params.overrides || params.override, (result, overrideDef, name) => {
|
|
47
|
+
const override = normalizeTemporals(overrideDef);
|
|
48
|
+
override.name = name;
|
|
49
|
+
if (overrideDef.until && !(overrideDef.until instanceof Date)) {
|
|
50
|
+
overrideDef.until = new Date(overrideDef.until);
|
|
51
|
+
}
|
|
52
|
+
override.until = overrideDef.until;
|
|
53
|
+
if (overrideDef.match) {
|
|
54
|
+
// TODO: Allow more flags
|
|
55
|
+
override.match = new RegExp(overrideDef.match, 'i');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!override.until || override.until >= new Date()) {
|
|
59
|
+
if (override.match) {
|
|
60
|
+
type.overridesMatch[name] = override;
|
|
61
|
+
} else {
|
|
62
|
+
result[name] = override;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
}, {});
|
|
68
|
+
|
|
69
|
+
if (Object.keys(type.overridesMatch).length > 0) {
|
|
70
|
+
type.overridesCache = new LRU({ max: 50 });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return type;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load the buckets configuration.
|
|
78
|
+
*
|
|
79
|
+
* @param {Object.<string, type>} bucketsConfig The buckets configuration.
|
|
80
|
+
* @memberof LimitDB
|
|
81
|
+
*/
|
|
82
|
+
function buildBuckets(bucketsConfig) {
|
|
83
|
+
return _.reduce(bucketsConfig, (result, bucket, name) => {
|
|
84
|
+
result[name] = normalizeType(bucket);
|
|
85
|
+
return result;
|
|
86
|
+
}, {});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildBucket(bucket) {
|
|
90
|
+
return normalizeType(bucket);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function functionOrFalse(fun) {
|
|
94
|
+
return !!(fun && fun.constructor && fun.call && fun.apply)
|
|
95
|
+
? fun
|
|
96
|
+
: false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function randomBetween(min, max) {
|
|
100
|
+
if (min > max) {
|
|
101
|
+
let tmp = max;
|
|
102
|
+
max = min;
|
|
103
|
+
min = tmp;
|
|
104
|
+
}
|
|
105
|
+
return Math.random() * (max-min) + min;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
buildBuckets,
|
|
110
|
+
buildBucket,
|
|
111
|
+
INTERVAL_SHORTCUTS,
|
|
112
|
+
normalizeTemporals,
|
|
113
|
+
normalizeType,
|
|
114
|
+
functionOrFalse,
|
|
115
|
+
randomBetween
|
|
116
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { INTERVAL_SHORTCUTS } = require('./utils');
|
|
2
|
+
|
|
3
|
+
class LimitdRedisValidationError extends Error {
|
|
4
|
+
constructor(msg, extra) {
|
|
5
|
+
super();
|
|
6
|
+
this.name = this.constructor.name;
|
|
7
|
+
this.message = msg;
|
|
8
|
+
Error.captureStackTrace(this, this.constructor);
|
|
9
|
+
if (extra) {
|
|
10
|
+
this.extra = extra;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validateParams(params, buckets) {
|
|
16
|
+
if (typeof params !== 'object') {
|
|
17
|
+
return new LimitdRedisValidationError('params are required', { code: 101 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof params.type !== 'string') {
|
|
21
|
+
return new LimitdRedisValidationError('type is required', { code: 102 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof buckets[params.type] === 'undefined') {
|
|
25
|
+
return new LimitdRedisValidationError(`undefined bucket type ${params.type}`, { code: 103 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof params.key !== 'string') {
|
|
29
|
+
return new LimitdRedisValidationError('key is required', { code: 104 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof params.configOverride !== 'undefined') {
|
|
33
|
+
try {
|
|
34
|
+
validateOverride(params.configOverride);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function validateOverride(configOverride) {
|
|
42
|
+
if (typeof configOverride !== 'object') {
|
|
43
|
+
throw new LimitdRedisValidationError('configuration overrides must be an object', { code: 105 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If size is provided, nothing more is strictly required
|
|
47
|
+
// (as in the case of static bucket configurations)
|
|
48
|
+
if (typeof configOverride.size === 'number') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const interval = Object.keys(configOverride)
|
|
53
|
+
.find(key => INTERVAL_SHORTCUTS.indexOf(key) > -1);
|
|
54
|
+
|
|
55
|
+
// If size is not provided, we *must* have a interval specified
|
|
56
|
+
if (typeof interval === 'undefined') {
|
|
57
|
+
throw new LimitdRedisValidationError('configuration overrides must provide either a size or interval', { code: 106 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
validateParams,
|
|
63
|
+
LimitdRedisValidationError,
|
|
64
|
+
};
|