@tryghost/limit-service 1.1.2 → 1.2.1
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 +28 -9
- package/lib/config.js +11 -11
- package/lib/limit-service.js +31 -13
- package/lib/limit.js +30 -13
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ or
|
|
|
17
17
|
Below is a sample code to wire up limit service and perform few common limit checks:
|
|
18
18
|
|
|
19
19
|
```js
|
|
20
|
+
const knex = require('knex');
|
|
20
21
|
const errors = require('@tryghost/errors');
|
|
21
22
|
const LimitService = require('@tryghost/limit-service');
|
|
22
23
|
|
|
@@ -80,15 +81,17 @@ const subscription = {
|
|
|
80
81
|
const helpLink = 'https://ghost.org/help/';
|
|
81
82
|
|
|
82
83
|
// initialize knex db connection for the limit service to use when running query checks
|
|
83
|
-
const db =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
84
|
+
const db = {
|
|
85
|
+
knex: knex({
|
|
86
|
+
client: 'mysql',
|
|
87
|
+
connection: {
|
|
88
|
+
user: 'root',
|
|
89
|
+
password: 'toor',
|
|
90
|
+
host: 'localhost',
|
|
91
|
+
database: 'ghost',
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
92
95
|
|
|
93
96
|
// finish initializing the limits service
|
|
94
97
|
limitService.loadLimits({limits, subscription, db, helpLink, errors});
|
|
@@ -133,6 +136,22 @@ if (limitService.checkIfAnyOverLimit()) {
|
|
|
133
136
|
}
|
|
134
137
|
```
|
|
135
138
|
|
|
139
|
+
### Transactions
|
|
140
|
+
|
|
141
|
+
Some limit types (`max` or `maxPeriodic`) need to fetch the current count from the database. Sometimes you need those checks to also run in a transaction. To fix that, you can pass the `transacting` option to all the available checks.
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
db.transaction((transacting) => {
|
|
145
|
+
const options = {transacting};
|
|
146
|
+
|
|
147
|
+
await limitService.errorIfWouldGoOverLimit('newsletters', options);
|
|
148
|
+
await limitService.errorIfIsOverLimit('newsletters', options);
|
|
149
|
+
const a = await limitService.checkIsOverLimit('newsletters', options);
|
|
150
|
+
const b = await limitService.checkWouldGoOverLimit('newsletters', options);
|
|
151
|
+
const c = await limitService.checkIfAnyOverLimit(options);
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
136
155
|
### Types of limits
|
|
137
156
|
At the moment there are four different types of limits that limit service allows to define. These types are:
|
|
138
157
|
1. `flag` - is an "on/off" switch for certain feature. Example usecase: "disable all emails". It's identified by a `disabled: true` property in the "limits" configuration.
|
package/lib/config.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
// 2. MaxLimit should contain a `currentCountQuery` function which would count the resources under limit
|
|
5
5
|
module.exports = {
|
|
6
6
|
members: {
|
|
7
|
-
currentCountQuery: async (
|
|
8
|
-
let result = await
|
|
7
|
+
currentCountQuery: async (knex) => {
|
|
8
|
+
let result = await knex('members').count('id', {as: 'count'}).first();
|
|
9
9
|
return result.count;
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
newsletters: {
|
|
13
|
-
currentCountQuery: async (
|
|
14
|
-
let result = await
|
|
13
|
+
currentCountQuery: async (knex) => {
|
|
14
|
+
let result = await knex('newsletters')
|
|
15
15
|
.count('id', {as: 'count'})
|
|
16
16
|
.where('status', '=', 'active')
|
|
17
17
|
.first();
|
|
@@ -20,8 +20,8 @@ module.exports = {
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
emails: {
|
|
23
|
-
currentCountQuery: async (
|
|
24
|
-
let result = await
|
|
23
|
+
currentCountQuery: async (knex, startDate) => {
|
|
24
|
+
let result = await knex('emails')
|
|
25
25
|
.sum('email_count', {as: 'count'})
|
|
26
26
|
.where('created_at', '>=', startDate)
|
|
27
27
|
.first();
|
|
@@ -30,13 +30,13 @@ module.exports = {
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
staff: {
|
|
33
|
-
currentCountQuery: async (
|
|
34
|
-
let result = await
|
|
33
|
+
currentCountQuery: async (knex) => {
|
|
34
|
+
let result = await knex('users')
|
|
35
35
|
.select('users.id')
|
|
36
36
|
.leftJoin('roles_users', 'users.id', 'roles_users.user_id')
|
|
37
37
|
.leftJoin('roles', 'roles_users.role_id', 'roles.id')
|
|
38
38
|
.whereNot('roles.name', 'Contributor').andWhereNot('users.status', 'inactive').union([
|
|
39
|
-
|
|
39
|
+
knex('invites')
|
|
40
40
|
.select('invites.id')
|
|
41
41
|
.leftJoin('roles', 'invites.role_id', 'roles.id')
|
|
42
42
|
.whereNot('roles.name', 'Contributor')
|
|
@@ -46,8 +46,8 @@ module.exports = {
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
customIntegrations: {
|
|
49
|
-
currentCountQuery: async (
|
|
50
|
-
let result = await
|
|
49
|
+
currentCountQuery: async (knex) => {
|
|
50
|
+
let result = await knex('integrations')
|
|
51
51
|
.count('id', {as: 'count'})
|
|
52
52
|
.whereNotIn('type', ['internal', 'builtin'])
|
|
53
53
|
.first();
|
package/lib/limit-service.js
CHANGED
|
@@ -67,13 +67,20 @@ class LimitService {
|
|
|
67
67
|
return !!this.limits[_.camelCase(limitName)];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param {String} limitName - name of the configured limit
|
|
73
|
+
* @param {Object} [options] - limit parameters
|
|
74
|
+
* @param {Object} [options.transacting] Transaction to run the count query on (if required for the chosen limit)
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
async checkIsOverLimit(limitName, options = {}) {
|
|
71
78
|
if (!this.isLimited(limitName)) {
|
|
72
79
|
return;
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
try {
|
|
76
|
-
await this.limits[limitName].errorIfIsOverLimit();
|
|
83
|
+
await this.limits[limitName].errorIfIsOverLimit(options);
|
|
77
84
|
return false;
|
|
78
85
|
} catch (error) {
|
|
79
86
|
if (error instanceof this.errors.HostLimitError) {
|
|
@@ -84,13 +91,20 @@ class LimitService {
|
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {String} limitName - name of the configured limit
|
|
97
|
+
* @param {Object} [options] - limit parameters
|
|
98
|
+
* @param {Object} [options.transacting] Transaction to run the count query on (if required for the chosen limit)
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
async checkWouldGoOverLimit(limitName, options = {}) {
|
|
88
102
|
if (!this.isLimited(limitName)) {
|
|
89
103
|
return;
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
try {
|
|
93
|
-
await this.limits[limitName].errorIfWouldGoOverLimit(
|
|
107
|
+
await this.limits[limitName].errorIfWouldGoOverLimit(options);
|
|
94
108
|
return false;
|
|
95
109
|
} catch (error) {
|
|
96
110
|
if (error instanceof this.errors.HostLimitError) {
|
|
@@ -104,39 +118,43 @@ class LimitService {
|
|
|
104
118
|
/**
|
|
105
119
|
*
|
|
106
120
|
* @param {String} limitName - name of the configured limit
|
|
107
|
-
* @param {Object}
|
|
121
|
+
* @param {Object} [options] - limit parameters
|
|
122
|
+
* @param {Object} [options.transacting] Transaction to run the count query on (if required for the chosen limit)
|
|
108
123
|
* @returns
|
|
109
124
|
*/
|
|
110
|
-
async errorIfIsOverLimit(limitName,
|
|
125
|
+
async errorIfIsOverLimit(limitName, options = {}) {
|
|
111
126
|
if (!this.isLimited(limitName)) {
|
|
112
127
|
return;
|
|
113
128
|
}
|
|
114
129
|
|
|
115
|
-
await this.limits[limitName].errorIfIsOverLimit(
|
|
130
|
+
await this.limits[limitName].errorIfIsOverLimit(options);
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
/**
|
|
119
134
|
*
|
|
120
135
|
* @param {String} limitName - name of the configured limit
|
|
121
|
-
* @param {Object}
|
|
136
|
+
* @param {Object} [options] - limit parameters
|
|
137
|
+
* @param {Object} [options.transacting] Transaction to run the count query on (if required for the chosen limit)
|
|
122
138
|
* @returns
|
|
123
139
|
*/
|
|
124
|
-
async errorIfWouldGoOverLimit(limitName,
|
|
140
|
+
async errorIfWouldGoOverLimit(limitName, options = {}) {
|
|
125
141
|
if (!this.isLimited(limitName)) {
|
|
126
142
|
return;
|
|
127
143
|
}
|
|
128
144
|
|
|
129
|
-
await this.limits[limitName].errorIfWouldGoOverLimit(
|
|
145
|
+
await this.limits[limitName].errorIfWouldGoOverLimit(options);
|
|
130
146
|
}
|
|
131
147
|
|
|
132
148
|
/**
|
|
133
149
|
* Checks if any of the configured limits acceded
|
|
134
|
-
*
|
|
150
|
+
*
|
|
151
|
+
* @param {Object} [options] - limit parameters
|
|
152
|
+
* @param {Object} [options.transacting] Transaction to run the count queries on (if required for the chosen limit)
|
|
135
153
|
* @returns {Promise<boolean>}
|
|
136
154
|
*/
|
|
137
|
-
async checkIfAnyOverLimit() {
|
|
155
|
+
async checkIfAnyOverLimit(options = {}) {
|
|
138
156
|
for (const limit in this.limits) {
|
|
139
|
-
if (await this.checkIsOverLimit(limit)) {
|
|
157
|
+
if (await this.checkIsOverLimit(limit, options)) {
|
|
140
158
|
return true;
|
|
141
159
|
}
|
|
142
160
|
}
|
package/lib/limit.js
CHANGED
|
@@ -98,8 +98,13 @@ class MaxLimit extends Limit {
|
|
|
98
98
|
return new this.errors.HostLimitError(errorObj);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
/**
|
|
102
|
+
* @param {Object} [options]
|
|
103
|
+
* @param {Object} [options.transacting] Transaction to run the count query on
|
|
104
|
+
* @returns
|
|
105
|
+
*/
|
|
106
|
+
async currentCountQuery(options = {}) {
|
|
107
|
+
return await this.currentCountQueryFn(options.transacting ?? this.db?.knex);
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
/**
|
|
@@ -108,9 +113,11 @@ class MaxLimit extends Limit {
|
|
|
108
113
|
* @param {Object} options
|
|
109
114
|
* @param {Number} [options.max] - overrides configured default max value to perform checks against
|
|
110
115
|
* @param {Number} [options.addedCount] - number of items to add to the currentCount during the check
|
|
116
|
+
* @param {Object} [options.transacting] Transaction to run the count query on
|
|
111
117
|
*/
|
|
112
|
-
async errorIfWouldGoOverLimit(
|
|
113
|
-
|
|
118
|
+
async errorIfWouldGoOverLimit(options = {}) {
|
|
119
|
+
const {max, addedCount = 1} = options;
|
|
120
|
+
let currentCount = await this.currentCountQuery(options);
|
|
114
121
|
|
|
115
122
|
if ((currentCount + addedCount) > (max || this.max)) {
|
|
116
123
|
throw this.generateError(currentCount);
|
|
@@ -123,11 +130,12 @@ class MaxLimit extends Limit {
|
|
|
123
130
|
* @param {Object} options
|
|
124
131
|
* @param {Number} [options.max] - overrides configured default max value to perform checks against
|
|
125
132
|
* @param {Number} [options.currentCount] - overrides currentCountQuery to perform checks against
|
|
133
|
+
* @param {Object} [options.transacting] Transaction to run the count query on
|
|
126
134
|
*/
|
|
127
|
-
async errorIfIsOverLimit(
|
|
128
|
-
currentCount = currentCount || await this.currentCountQuery();
|
|
135
|
+
async errorIfIsOverLimit(options = {}) {
|
|
136
|
+
const currentCount = options.currentCount || await this.currentCountQuery(options);
|
|
129
137
|
|
|
130
|
-
if (currentCount > (max || this.max)) {
|
|
138
|
+
if (currentCount > (options.max || this.max)) {
|
|
131
139
|
throw this.generateError(currentCount);
|
|
132
140
|
}
|
|
133
141
|
}
|
|
@@ -202,10 +210,15 @@ class MaxPeriodicLimit extends Limit {
|
|
|
202
210
|
return new this.errors.HostLimitError(errorObj);
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
|
|
213
|
+
/**
|
|
214
|
+
* @param {Object} [options]
|
|
215
|
+
* @param {Object} [options.transacting] Transaction to run the count query on
|
|
216
|
+
* @returns
|
|
217
|
+
*/
|
|
218
|
+
async currentCountQuery(options = {}) {
|
|
206
219
|
const lastPeriodStartDate = lastPeriodStart(this.startDate, this.interval);
|
|
207
220
|
|
|
208
|
-
return await this.currentCountQueryFn(this.db, lastPeriodStartDate);
|
|
221
|
+
return await this.currentCountQueryFn(options.transacting ? options.transacting : (this.db ? this.db.knex : undefined), lastPeriodStartDate);
|
|
209
222
|
}
|
|
210
223
|
|
|
211
224
|
/**
|
|
@@ -214,9 +227,11 @@ class MaxPeriodicLimit extends Limit {
|
|
|
214
227
|
* @param {Object} options
|
|
215
228
|
* @param {Number} [options.max] - overrides configured default maxPeriodic value to perform checks against
|
|
216
229
|
* @param {Number} [options.addedCount] - number of items to add to the currentCount during the check
|
|
230
|
+
* @param {Object} [options.transacting] Transaction to run the count query on
|
|
217
231
|
*/
|
|
218
|
-
async errorIfWouldGoOverLimit(
|
|
219
|
-
|
|
232
|
+
async errorIfWouldGoOverLimit(options = {}) {
|
|
233
|
+
const {max, addedCount = 1} = options;
|
|
234
|
+
let currentCount = await this.currentCountQuery(options);
|
|
220
235
|
|
|
221
236
|
if ((currentCount + addedCount) > (max || this.maxPeriodic)) {
|
|
222
237
|
throw this.generateError(currentCount);
|
|
@@ -228,9 +243,11 @@ class MaxPeriodicLimit extends Limit {
|
|
|
228
243
|
*
|
|
229
244
|
* @param {Object} options
|
|
230
245
|
* @param {Number} [options.max] - overrides configured default maxPeriodic value to perform checks against
|
|
246
|
+
* @param {Object} [options.transacting] Transaction to run the count query on
|
|
231
247
|
*/
|
|
232
|
-
async errorIfIsOverLimit(
|
|
233
|
-
|
|
248
|
+
async errorIfIsOverLimit(options = {}) {
|
|
249
|
+
const {max} = options;
|
|
250
|
+
let currentCount = await this.currentCountQuery(options);
|
|
234
251
|
|
|
235
252
|
if (currentCount > (max || this.maxPeriodic)) {
|
|
236
253
|
throw this.generateError(currentCount);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryghost/limit-service",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"repository": "https://github.com/TryGhost/Utils/tree/main/packages/limit-service",
|
|
5
5
|
"author": "Ghost Foundation",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"c8": "7.11.
|
|
23
|
+
"c8": "7.11.3",
|
|
24
24
|
"mocha": "10.0.0",
|
|
25
25
|
"should": "13.2.3",
|
|
26
|
-
"sinon": "
|
|
26
|
+
"sinon": "14.0.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@tryghost/errors": "^1.2.1",
|
|
30
30
|
"lodash": "^4.17.21",
|
|
31
31
|
"luxon": "^1.26.0"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "ee7a05a733c50a4eb7c7b972d0f52a7b3d58751e"
|
|
34
34
|
}
|