@prsm/entitle 2.1.0 → 2.1.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/package.json +1 -1
- package/src/entitlements.js +32 -24
- package/types/entitlements.d.ts +26 -14
package/package.json
CHANGED
package/src/entitlements.js
CHANGED
|
@@ -11,8 +11,8 @@ import ms from "@prsm/ms"
|
|
|
11
11
|
* A per-subject override layered on top of the subject's plan. Shallow-merges
|
|
12
12
|
* over the plan, so you only specify what differs (the enterprise customer who
|
|
13
13
|
* negotiated more seats, or got one feature switched on).
|
|
14
|
-
* @property {Record<string, boolean>} [features]
|
|
15
|
-
* @property {Record<string, number|null>} [limits]
|
|
14
|
+
* @property {Record<string, boolean>} [features] - feature flags to override for this subject; merged over the plan's features, so include only the ones that differ
|
|
15
|
+
* @property {Record<string, number|null>} [limits] - limit ceilings to override for this subject; merged over the plan's limits, so include only the ones that differ (`null` means unlimited)
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -29,9 +29,9 @@ import ms from "@prsm/ms"
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @typedef {Object} Effective
|
|
32
|
-
* @property {string} plan - the effective plan name
|
|
33
|
-
* @property {Record<string, boolean>} features
|
|
34
|
-
* @property {Record<string, number|null>} limits
|
|
32
|
+
* @property {string} plan - the effective plan name (the default plan if the subject is unassigned)
|
|
33
|
+
* @property {Record<string, boolean>} features - the resolved feature flags, after applying the subject's override on top of the plan
|
|
34
|
+
* @property {Record<string, number|null>} limits - the resolved limit ceilings, after applying the subject's override on top of the plan (`null` means unlimited)
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -49,9 +49,13 @@ function assertBoolean(scope, key, v) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function assertLimitValue(scope, key, v) {
|
|
52
|
-
if (v
|
|
52
|
+
if (v === null) return
|
|
53
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
53
54
|
throw new Error(`${scope} limit "${key}" must be a finite number or null (null means unlimited)`)
|
|
54
55
|
}
|
|
56
|
+
if (v < 0) {
|
|
57
|
+
throw new Error(`${scope} limit "${key}" must not be negative (use 0 to deny, null for unlimited)`)
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
/**
|
|
@@ -184,6 +188,12 @@ export function createEntitlements(options = {}) {
|
|
|
184
188
|
return effective
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
async function resolveLimit(subject, key) {
|
|
192
|
+
requireLimit(key)
|
|
193
|
+
const eff = await resolve(subject)
|
|
194
|
+
return key in eff.limits ? eff.limits[key] : 0
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
return {
|
|
188
198
|
/** Create the backing tables if they do not exist. Idempotent. */
|
|
189
199
|
setup() {
|
|
@@ -192,7 +202,7 @@ export function createEntitlements(options = {}) {
|
|
|
192
202
|
|
|
193
203
|
/**
|
|
194
204
|
* Assign a subject to a plan. Takes effect immediately.
|
|
195
|
-
* @param {string} subject
|
|
205
|
+
* @param {string} subject - the subject identifier (whatever id you key entitlements by, such as an account or user id)
|
|
196
206
|
* @param {string} plan - a key of the plan catalog
|
|
197
207
|
*/
|
|
198
208
|
async assign(subject, plan) {
|
|
@@ -206,7 +216,7 @@ export function createEntitlements(options = {}) {
|
|
|
206
216
|
* Remove a subject's plan assignment, reverting them to the default plan.
|
|
207
217
|
* Distinct from assigning the default plan explicitly: an unassigned subject
|
|
208
218
|
* follows `defaultPlan` if it later changes.
|
|
209
|
-
* @param {string} subject
|
|
219
|
+
* @param {string} subject - the subject identifier whose assignment is removed
|
|
210
220
|
*/
|
|
211
221
|
async unassign(subject) {
|
|
212
222
|
if (!subject) throw new Error("unassign requires a `subject`")
|
|
@@ -220,8 +230,8 @@ export function createEntitlements(options = {}) {
|
|
|
220
230
|
* accumulate: overriding `seats`, then later overriding `sso`, leaves both in
|
|
221
231
|
* place, and overriding a key again updates just that key. Use clearOverride
|
|
222
232
|
* to remove overrides. Takes effect immediately.
|
|
223
|
-
* @param {string} subject
|
|
224
|
-
* @param {Override} data
|
|
233
|
+
* @param {string} subject - the subject identifier to override
|
|
234
|
+
* @param {Override} data - the features and/or limits to override; merged into any existing override
|
|
225
235
|
*/
|
|
226
236
|
async override(subject, data) {
|
|
227
237
|
if (!subject) throw new Error("override requires a `subject`")
|
|
@@ -234,8 +244,8 @@ export function createEntitlements(options = {}) {
|
|
|
234
244
|
* Remove overrides for a subject. With no `keys`, removes the entire override
|
|
235
245
|
* and the subject falls back to plain plan entitlements. With `keys`, removes
|
|
236
246
|
* only those override entries (reverting them to the plan) and keeps the rest.
|
|
237
|
-
* @param {string} subject
|
|
238
|
-
* @param {{ features?: string[], limits?: string[] }} [keys]
|
|
247
|
+
* @param {string} subject - the subject identifier whose override is cleared
|
|
248
|
+
* @param {{ features?: string[], limits?: string[] }} [keys] - the specific feature and limit keys to revert; omit to clear the entire override
|
|
239
249
|
*/
|
|
240
250
|
async clearOverride(subject, keys) {
|
|
241
251
|
if (!subject) throw new Error("clearOverride requires a `subject`")
|
|
@@ -249,7 +259,7 @@ export function createEntitlements(options = {}) {
|
|
|
249
259
|
|
|
250
260
|
/**
|
|
251
261
|
* The subject's effective plan name (the default plan if unassigned).
|
|
252
|
-
* @param {string} subject
|
|
262
|
+
* @param {string} subject - the subject identifier to resolve
|
|
253
263
|
* @returns {Promise<string>}
|
|
254
264
|
*/
|
|
255
265
|
async plan(subject) {
|
|
@@ -260,8 +270,8 @@ export function createEntitlements(options = {}) {
|
|
|
260
270
|
* Whether a capability flag is granted to the subject. Throws on a feature
|
|
261
271
|
* key outside the declared/derived universe, so typos surface instead of
|
|
262
272
|
* silently returning false.
|
|
263
|
-
* @param {string} subject
|
|
264
|
-
* @param {string} feature
|
|
273
|
+
* @param {string} subject - the subject identifier to resolve
|
|
274
|
+
* @param {string} feature - the feature key to check; must be in the declared/derived feature universe
|
|
265
275
|
* @returns {Promise<boolean>}
|
|
266
276
|
*/
|
|
267
277
|
async can(subject, feature) {
|
|
@@ -278,14 +288,12 @@ export function createEntitlements(options = {}) {
|
|
|
278
288
|
* subject's plan does not grant - never silently unlimited. Throws on a key
|
|
279
289
|
* outside the declared/derived universe. This is the static ceiling; it does
|
|
280
290
|
* not read usage.
|
|
281
|
-
* @param {string} subject
|
|
282
|
-
* @param {string} key
|
|
291
|
+
* @param {string} subject - the subject identifier to resolve
|
|
292
|
+
* @param {string} key - the limit key to resolve; must be in the declared/derived limit universe
|
|
283
293
|
* @returns {Promise<number|null>}
|
|
284
294
|
*/
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const eff = await resolve(subject)
|
|
288
|
-
return key in eff.limits ? eff.limits[key] : 0
|
|
295
|
+
limit(subject, key) {
|
|
296
|
+
return resolveLimit(subject, key)
|
|
289
297
|
},
|
|
290
298
|
|
|
291
299
|
/**
|
|
@@ -293,7 +301,7 @@ export function createEntitlements(options = {}) {
|
|
|
293
301
|
* reads current usage from the meter for the metric of the same name. This is
|
|
294
302
|
* the composition seam: entitle supplies the limit, meter supplies the usage.
|
|
295
303
|
* Requires a `meter` to have been passed to `createEntitlements`.
|
|
296
|
-
* @param {string} subject
|
|
304
|
+
* @param {string} subject - the subject identifier; must match the id usage is recorded under in the meter
|
|
297
305
|
* @param {string} key - a limit key that is also a meter metric
|
|
298
306
|
* @param {{ period?: any, range?: any }} [usageQuery] - forwarded to `meter.usage`
|
|
299
307
|
* @returns {Promise<CheckResult>}
|
|
@@ -303,7 +311,7 @@ export function createEntitlements(options = {}) {
|
|
|
303
311
|
throw new Error("check requires a `meter`; pass it to createEntitlements, or use limit() for the static ceiling")
|
|
304
312
|
}
|
|
305
313
|
return traced(tracer, "entitle.check", { "entitle.subject": subject, "entitle.feature": key }, async () => {
|
|
306
|
-
const limit = await
|
|
314
|
+
const limit = await resolveLimit(subject, key)
|
|
307
315
|
const usage = await meter.usage({ ...usageQuery, subject, metric: key })
|
|
308
316
|
const used = usage.quantity
|
|
309
317
|
const allowed = limit === null || used < limit
|
|
@@ -320,7 +328,7 @@ export function createEntitlements(options = {}) {
|
|
|
320
328
|
|
|
321
329
|
/**
|
|
322
330
|
* The subject's full effective entitlements, for a settings or billing page.
|
|
323
|
-
* @param {string} subject
|
|
331
|
+
* @param {string} subject - the subject identifier to resolve
|
|
324
332
|
* @returns {Promise<Effective>}
|
|
325
333
|
*/
|
|
326
334
|
async describe(subject) {
|
package/types/entitlements.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
11
11
|
setup(): any;
|
|
12
12
|
/**
|
|
13
13
|
* Assign a subject to a plan. Takes effect immediately.
|
|
14
|
-
* @param {string} subject
|
|
14
|
+
* @param {string} subject - the subject identifier (whatever id you key entitlements by, such as an account or user id)
|
|
15
15
|
* @param {string} plan - a key of the plan catalog
|
|
16
16
|
*/
|
|
17
17
|
assign(subject: string, plan: string): Promise<void>;
|
|
@@ -19,7 +19,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
19
19
|
* Remove a subject's plan assignment, reverting them to the default plan.
|
|
20
20
|
* Distinct from assigning the default plan explicitly: an unassigned subject
|
|
21
21
|
* follows `defaultPlan` if it later changes.
|
|
22
|
-
* @param {string} subject
|
|
22
|
+
* @param {string} subject - the subject identifier whose assignment is removed
|
|
23
23
|
*/
|
|
24
24
|
unassign(subject: string): Promise<void>;
|
|
25
25
|
/**
|
|
@@ -28,16 +28,16 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
28
28
|
* accumulate: overriding `seats`, then later overriding `sso`, leaves both in
|
|
29
29
|
* place, and overriding a key again updates just that key. Use clearOverride
|
|
30
30
|
* to remove overrides. Takes effect immediately.
|
|
31
|
-
* @param {string} subject
|
|
32
|
-
* @param {Override} data
|
|
31
|
+
* @param {string} subject - the subject identifier to override
|
|
32
|
+
* @param {Override} data - the features and/or limits to override; merged into any existing override
|
|
33
33
|
*/
|
|
34
34
|
override(subject: string, data: Override): Promise<void>;
|
|
35
35
|
/**
|
|
36
36
|
* Remove overrides for a subject. With no `keys`, removes the entire override
|
|
37
37
|
* and the subject falls back to plain plan entitlements. With `keys`, removes
|
|
38
38
|
* only those override entries (reverting them to the plan) and keeps the rest.
|
|
39
|
-
* @param {string} subject
|
|
40
|
-
* @param {{ features?: string[], limits?: string[] }} [keys]
|
|
39
|
+
* @param {string} subject - the subject identifier whose override is cleared
|
|
40
|
+
* @param {{ features?: string[], limits?: string[] }} [keys] - the specific feature and limit keys to revert; omit to clear the entire override
|
|
41
41
|
*/
|
|
42
42
|
clearOverride(subject: string, keys?: {
|
|
43
43
|
features?: string[];
|
|
@@ -45,7 +45,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
45
45
|
}): Promise<void>;
|
|
46
46
|
/**
|
|
47
47
|
* The subject's effective plan name (the default plan if unassigned).
|
|
48
|
-
* @param {string} subject
|
|
48
|
+
* @param {string} subject - the subject identifier to resolve
|
|
49
49
|
* @returns {Promise<string>}
|
|
50
50
|
*/
|
|
51
51
|
plan(subject: string): Promise<string>;
|
|
@@ -53,8 +53,8 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
53
53
|
* Whether a capability flag is granted to the subject. Throws on a feature
|
|
54
54
|
* key outside the declared/derived universe, so typos surface instead of
|
|
55
55
|
* silently returning false.
|
|
56
|
-
* @param {string} subject
|
|
57
|
-
* @param {string} feature
|
|
56
|
+
* @param {string} subject - the subject identifier to resolve
|
|
57
|
+
* @param {string} feature - the feature key to check; must be in the declared/derived feature universe
|
|
58
58
|
* @returns {Promise<boolean>}
|
|
59
59
|
*/
|
|
60
60
|
can(subject: string, feature: string): Promise<boolean>;
|
|
@@ -64,8 +64,8 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
64
64
|
* subject's plan does not grant - never silently unlimited. Throws on a key
|
|
65
65
|
* outside the declared/derived universe. This is the static ceiling; it does
|
|
66
66
|
* not read usage.
|
|
67
|
-
* @param {string} subject
|
|
68
|
-
* @param {string} key
|
|
67
|
+
* @param {string} subject - the subject identifier to resolve
|
|
68
|
+
* @param {string} key - the limit key to resolve; must be in the declared/derived limit universe
|
|
69
69
|
* @returns {Promise<number|null>}
|
|
70
70
|
*/
|
|
71
71
|
limit(subject: string, key: string): Promise<number | null>;
|
|
@@ -74,7 +74,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
74
74
|
* reads current usage from the meter for the metric of the same name. This is
|
|
75
75
|
* the composition seam: entitle supplies the limit, meter supplies the usage.
|
|
76
76
|
* Requires a `meter` to have been passed to `createEntitlements`.
|
|
77
|
-
* @param {string} subject
|
|
77
|
+
* @param {string} subject - the subject identifier; must match the id usage is recorded under in the meter
|
|
78
78
|
* @param {string} key - a limit key that is also a meter metric
|
|
79
79
|
* @param {{ period?: any, range?: any }} [usageQuery] - forwarded to `meter.usage`
|
|
80
80
|
* @returns {Promise<CheckResult>}
|
|
@@ -85,7 +85,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
85
85
|
}): Promise<CheckResult>;
|
|
86
86
|
/**
|
|
87
87
|
* The subject's full effective entitlements, for a settings or billing page.
|
|
88
|
-
* @param {string} subject
|
|
88
|
+
* @param {string} subject - the subject identifier to resolve
|
|
89
89
|
* @returns {Promise<Effective>}
|
|
90
90
|
*/
|
|
91
91
|
describe(subject: string): Promise<Effective>;
|
|
@@ -108,7 +108,13 @@ export type Plan = {
|
|
|
108
108
|
* negotiated more seats, or got one feature switched on).
|
|
109
109
|
*/
|
|
110
110
|
export type Override = {
|
|
111
|
+
/**
|
|
112
|
+
* - feature flags to override for this subject; merged over the plan's features, so include only the ones that differ
|
|
113
|
+
*/
|
|
111
114
|
features?: Record<string, boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* - limit ceilings to override for this subject; merged over the plan's limits, so include only the ones that differ (`null` means unlimited)
|
|
117
|
+
*/
|
|
112
118
|
limits?: Record<string, number | null>;
|
|
113
119
|
};
|
|
114
120
|
export type EntitlementsOptions = {
|
|
@@ -151,10 +157,16 @@ export type EntitlementsOptions = {
|
|
|
151
157
|
};
|
|
152
158
|
export type Effective = {
|
|
153
159
|
/**
|
|
154
|
-
* - the effective plan name
|
|
160
|
+
* - the effective plan name (the default plan if the subject is unassigned)
|
|
155
161
|
*/
|
|
156
162
|
plan: string;
|
|
163
|
+
/**
|
|
164
|
+
* - the resolved feature flags, after applying the subject's override on top of the plan
|
|
165
|
+
*/
|
|
157
166
|
features: Record<string, boolean>;
|
|
167
|
+
/**
|
|
168
|
+
* - the resolved limit ceilings, after applying the subject's override on top of the plan (`null` means unlimited)
|
|
169
|
+
*/
|
|
158
170
|
limits: Record<string, number | null>;
|
|
159
171
|
};
|
|
160
172
|
export type CheckResult = {
|