@tryghost/limit-service 1.1.3 → 1.2.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/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 = knex({
84
- client: 'mysql',
85
- connection: {
86
- user: 'root',
87
- password: 'toor',
88
- host: 'localhost',
89
- database: 'ghost',
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 (db) => {
8
- let result = await db.knex('members').count('id', {as: 'count'}).first();
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 (db) => {
14
- let result = await db.knex('newsletters')
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 (db, startDate) => {
24
- let result = await db.knex('emails')
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 (db) => {
34
- let result = await db.knex('users')
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
- db.knex('invites')
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 (db) => {
50
- let result = await db.knex('integrations')
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();
@@ -67,13 +67,20 @@ class LimitService {
67
67
  return !!this.limits[_.camelCase(limitName)];
68
68
  }
69
69
 
70
- async checkIsOverLimit(limitName) {
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
- async checkWouldGoOverLimit(limitName, metadata = {}) {
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(metadata);
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} metadata - limit parameters
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, metadata = {}) {
125
+ async errorIfIsOverLimit(limitName, options = {}) {
111
126
  if (!this.isLimited(limitName)) {
112
127
  return;
113
128
  }
114
129
 
115
- await this.limits[limitName].errorIfIsOverLimit(metadata);
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} metadata - limit parameters
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, metadata = {}) {
140
+ async errorIfWouldGoOverLimit(limitName, options = {}) {
125
141
  if (!this.isLimited(limitName)) {
126
142
  return;
127
143
  }
128
144
 
129
- await this.limits[limitName].errorIfWouldGoOverLimit(metadata);
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
- async currentCountQuery() {
102
- return await this.currentCountQueryFn(this.db);
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({max, addedCount = 1} = {}) {
113
- let currentCount = await this.currentCountQuery();
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({max, currentCount} = {}) {
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
- async currentCountQuery() {
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({max, addedCount = 1} = {}) {
219
- let currentCount = await this.currentCountQuery();
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({max} = {}) {
233
- let currentCount = await this.currentCountQuery();
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",
3
+ "version": "1.2.2",
4
4
  "repository": "https://github.com/TryGhost/Utils/tree/main/packages/limit-service",
5
5
  "author": "Ghost Foundation",
6
6
  "license": "MIT",
@@ -20,7 +20,7 @@
20
20
  "access": "public"
21
21
  },
22
22
  "devDependencies": {
23
- "c8": "7.11.2",
23
+ "c8": "7.12.0",
24
24
  "mocha": "10.0.0",
25
25
  "should": "13.2.3",
26
26
  "sinon": "14.0.0"
@@ -30,5 +30,5 @@
30
30
  "lodash": "^4.17.21",
31
31
  "luxon": "^1.26.0"
32
32
  },
33
- "gitHead": "273e3f58cdce8874bd732ddc5a3fc0aad71e5a96"
33
+ "gitHead": "2ce0a263975cf6e0f34233caf19404fd8ab5e7a5"
34
34
  }