@tryghost/limit-service 1.2.19 → 1.3.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 CHANGED
@@ -26,7 +26,7 @@ const limitService = new LimitService();
26
26
 
27
27
  // setup limit configuration
28
28
  // currently supported limit keys are: staff, members, customThemes, customIntegrations, uploads,
29
- // limitStripeConnect, limitAnalytics, and limitActivityPub
29
+ // limitStripeConnect, limitAnalytics, and limitSocialWeb
30
30
  // all limit configs support custom "error" configuration that is a template string
31
31
  const limits = {
32
32
  // staff and member are "max" type of limits accepting "max" configuration
@@ -64,17 +64,17 @@ const limits = {
64
64
  uploads: {
65
65
  // max key is in bytes
66
66
  max: 5000000,
67
- // formatting of the {{ max }} vairable is in MB, e.g: 5MB
67
+ // formatting of the {{ max }} variable is in MB, e.g: 5MB
68
68
  error: 'Your plan supports uploads of max size up to {{max}}. Please upgrade to reenable uploading.'
69
69
  },
70
70
  limitStripeConnect: {},
71
71
  limitAnalytics: {},
72
- limitActivityPub: {}
72
+ limitSocialWeb: {}
73
73
  };
74
74
 
75
75
  // This information is needed for the limit service to work with "max periodic" limits
76
76
  // The interval value has to be 'month' as that's the only interval that was needed for
77
- // current usecase
77
+ // current use case
78
78
  // The startDate has to be in ISO 8601 format (https://en.wikipedia.org/wiki/ISO_8601)
79
79
  const subscription = {
80
80
  interval: 'month',
@@ -112,7 +112,7 @@ if (limitService.isLimited('staff')) {
112
112
  await limitService.errorIfWouldGoOverLimit('staff', {max: 100});
113
113
  }
114
114
 
115
- // "max" types of limits have currentCountQuery method reguring a number that is currently in use for the limit
115
+ // "max" types of limits have currentCountQuery method requiring a number that is currently in use for the limit
116
116
  // for example it could be 1, 3, 5 or whatever amount of 'staff' is currently in the system
117
117
  const staffCount = await limitService.currentCountQuery('staff');
118
118
 
@@ -158,18 +158,18 @@ db.transaction((transacting) => {
158
158
 
159
159
  ### Types of limits
160
160
  At the moment there are four different types of limits that limit service allows to define. These types are:
161
- 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.
162
- 2. `max` - checks if the maximum amount of the resource has been used up.Example usecase: "disable creating a staff user when maximum of 5 has been reached". To configure this limit add `max: NUMBER` to the configuration. The limits that support max checks are: `members`, and `staff`
163
- 3. `maxPeriodic` - it's a variation of `max` type with a difference that the check is done over certain period of time. Example usecase: "disable sending emails when the sent emails count has acceded a limit for last billing period". To enable this limit define `maxPeriodic: NUMBER` in the limit configuration and provide a subscription configuration when initializing the limit service instance. The subscription object comes as a separate parameter and has to contain two properties: `startDate` and `interval`, where `startDate` is a date in ISO 8601 format and period is `'month'` (other values like `'year'` are not supported yet)
164
- 4. `allowList` - checks if provided value is defined in configured "allowlist". Example usecase: "disable theme activation if it is not an official theme". To configure this limit define ` allowlist: ['VALUE_1', 'VALUE_2', 'VALUE_N']` property in the "limits" parameter.
161
+ 1. `flag` - is an "on/off" switch for certain feature. Example use case: "disable all emails". It's identified by a `disabled: true` property in the "limits" configuration. It is possible to overwrite the limit by providing a `currentCountQuery` for it. This is useful in cases where we introduce new limits to existing plans and customers have already been using the feature affected by the limit. By providing a `currentCountQuery` that detects if the feature is already in use, we won't disable it.
162
+ 2. `max` - checks if the maximum amount of the resource has been used up.Example use case: "disable creating a staff user when maximum of 5 has been reached". To configure this limit add `max: NUMBER` to the configuration. The limits that support max checks are: `members`, and `staff`
163
+ 3. `maxPeriodic` - it's a variation of `max` type with a difference that the check is done over certain period of time. Example use case: "disable sending emails when the sent emails count has acceded a limit for last billing period". To enable this limit define `maxPeriodic: NUMBER` in the limit configuration and provide a subscription configuration when initializing the limit service instance. The subscription object comes as a separate parameter and has to contain two properties: `startDate` and `interval`, where `startDate` is a date in ISO 8601 format and period is `'month'` (other values like `'year'` are not supported yet)
164
+ 4. `allowList` - checks if provided value is defined in configured "allowlist". Example use case: "disable theme activation if it is not an official theme". To configure this limit define ` allowlist: ['VALUE_1', 'VALUE_2', 'VALUE_N']` property in the "limits" parameter.
165
165
 
166
166
  ### Supported limits
167
- There's a limited amount of limits that are supported by limit service. The are defined by "key" property name in the "config" module. List of currently supported limit names: `members`, `staff`, `customIntegrations`, `emails`, `customThemes`, `uploads`, `limitStripeConnect`, `limitAnalytics`, and `limitActivityPub`.
167
+ There's a limited amount of limits that are supported by limit service. The are defined by "key" property name in the "config" module. List of currently supported limit names: `members`, `staff`, `customIntegrations`, `emails`, `customThemes`, `uploads`, `limitStripeConnect`, `limitAnalytics`, and `limitSocialWeb`.
168
168
 
169
169
  All limits can act as `flag` or `allowList` types. Only certain (`members`, `staff`) can have a `max` limit. Only `emails` currently supports the `maxPeriodic` type of limit.
170
170
 
171
171
  ### Frontend usage
172
- In case the limit check is run without direct access to the database you can override `currentCountQuery` functions for each "max" or "maxPeriodic" type of limit. An example usecase would be a frontend client running in a browser. A browser client can check the limit data through HTTP request and then provide that data to the limit service. Example code to do exactly that:
172
+ In case the limit check is run without direct access to the database you can override `currentCountQuery` functions for each "max" or "maxPeriodic" type of limit. An example use case would be a frontend client running in a browser. A browser client can check the limit data through HTTP request and then provide that data to the limit service. Example code to do exactly that:
173
173
 
174
174
  ```js
175
175
  const limitService = new LimitService();
package/lib/config.js CHANGED
@@ -58,5 +58,5 @@ module.exports = {
58
58
  },
59
59
  limitStripeConnect: {},
60
60
  limitAnalytics: {},
61
- limitActivityPub: {}
61
+ limitSocialWeb: {}
62
62
  };
package/lib/limit.js CHANGED
@@ -264,6 +264,7 @@ class FlagLimit extends Limit {
264
264
  * @param {Number} options.config.disabled - disabled/enabled flag for the limit
265
265
  * @param {String} options.config.error - error message to use when limit is reached
266
266
  * @param {String} options.helpLink - URL to the resource explaining how the limit works
267
+ * @param {Function} [options.config.currentCountQuery] - query checking the state that would be compared against the limit
267
268
  * @param {Object} [options.db] - instance of knex db connection that currentCountQuery can use to run state check through
268
269
  * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
269
270
  */
@@ -273,6 +274,7 @@ class FlagLimit extends Limit {
273
274
 
274
275
  this.disabled = config.disabled;
275
276
  this.fallbackMessage = `Your plan does not support ${userFacingLimitName}. Please upgrade to enable ${userFacingLimitName}.`;
277
+ this.currentCountQueryFn = config?.currentCountQuery || null;
276
278
  }
277
279
 
278
280
  generateError() {
@@ -288,20 +290,59 @@ class FlagLimit extends Limit {
288
290
  }
289
291
 
290
292
  /**
291
- * Flag limits are on/off so using a feature is always over the limit
293
+ * @param {Object} [options]
294
+ * @param {Object} [options.transacting] Transaction to run the count query on
295
+ * @returns {Promise<boolean>} - returns the current count of items that would be compared against the limit
292
296
  */
293
- async errorIfWouldGoOverLimit() {
294
- if (this.disabled) {
297
+ async currentCountQuery(options = {}) {
298
+ if (!this.currentCountQueryFn || typeof this.currentCountQueryFn !== 'function') {
299
+ return false;
300
+ }
301
+
302
+ return await this.currentCountQueryFn(options.transacting ?? this.db?.knex);
303
+ }
304
+
305
+ // As Flag limits are on/off, we won't check against max values.
306
+ // `errorIfWouldGoOverLimit` and `errorIfIsOverLimit` end up doing the same thing.
307
+ async _isOrWouldOverLimitError(options = {}) {
308
+ if (!this.disabled) {
309
+ return;
310
+ }
311
+
312
+ // If no currentCountQuery is provided, throw error when disabled
313
+ if (!this.currentCountQueryFn || typeof this.currentCountQueryFn !== 'function') {
295
314
  throw this.generateError();
296
315
  }
316
+
317
+ // If currentCountQuery is provided, check if feature is in use
318
+ const featureInUse = await this.currentCountQuery(options);
319
+
320
+ // Only throw error if feature is NOT in use (allowing grandfathering)
321
+ if (!featureInUse) {
322
+ throw this.generateError();
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Flag limits are usually on/off so using a feature is always over the limit,
328
+ * unless the limit has a currentCountQuery function provided to check if the
329
+ * feature is in use. This is a use case for when we introduce a new limit and
330
+ * customers have already been using this feature. We don't want to take it
331
+ * away from them.
332
+ */
333
+ async errorIfWouldGoOverLimit(options = {}) {
334
+ await this._isOrWouldOverLimitError(options);
297
335
  }
298
336
 
299
337
  /**
300
338
  * Flag limits are on/off. They don't necessarily mean the limit wasn't possible to reach
301
- * NOTE: this method should not be relied on as it's impossible to check the limit was surpassed!
339
+ * Exception: the limit has a currentCountQuery function provided to check if the
340
+ * feature is in use. This is a use case for when we introduce a new limit and
341
+ * customers have already been using this feature. We don't want to take it
342
+ * away from them.
302
343
  */
303
- async errorIfIsOverLimit() {
304
- return;
344
+ async errorIfIsOverLimit(options = {}) {
345
+ await this._isOrWouldOverLimitError(options);
305
346
  }
306
347
  }
307
348
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryghost/limit-service",
3
- "version": "1.2.19",
3
+ "version": "1.3.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/TryGhost/SDK.git",
@@ -34,5 +34,5 @@
34
34
  "lodash": "^4.17.21",
35
35
  "luxon": "^1.26.0"
36
36
  },
37
- "gitHead": "b10773947244536b8829fec0540819990c901987"
37
+ "gitHead": "01a3b9421038ee84b0e0198a7d83740673eade12"
38
38
  }