@prsm/entitle 2.1.1 → 2.2.0

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
@@ -175,6 +175,10 @@ Returns the subject's effective plan name.
175
175
 
176
176
  Returns the full effective snapshot `{ plan, features, limits }`, for a settings or billing page.
177
177
 
178
+ ### `entitlements.catalog()`
179
+
180
+ Returns the static configuration `{ defaultPlan, plans, features, limits }`: every declared plan, the default plan, and the full feature and limit universes. Where `describe` resolves a single subject, `catalog` exposes the whole offering, for a plan comparison table, an admin dashboard, or documenting what the system grants. Subject-independent and read-only, so it never touches storage. The returned object is a fresh copy.
181
+
178
182
  ### `entitlements.close()`
179
183
 
180
184
  Releases driver resources.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prsm/entitle",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Plan-based entitlements and feature gating, resolved at runtime, backed by postgres",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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,17 @@ 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
+ */
36
+
37
+ /**
38
+ * @typedef {Object} Catalog
39
+ * @property {string} defaultPlan - the plan applied to a subject with no assignment
40
+ * @property {Record<string, Plan>} plans - the declared plan catalog (your pricing tiers)
41
+ * @property {string[]} features - the full feature universe (declared, or derived from the union across plans)
42
+ * @property {string[]} limits - the full limit universe (declared, or derived from the union across plans)
35
43
  */
36
44
 
37
45
  /**
@@ -200,9 +208,32 @@ export function createEntitlements(options = {}) {
200
208
  return driver.setup()
201
209
  },
202
210
 
211
+ /**
212
+ * The static configuration of this resolver: the plan catalog, the default
213
+ * plan, and the full feature and limit universes. Unlike describe(), which
214
+ * resolves a single subject, this exposes every plan and every declared key
215
+ * - for plan comparison tables, admin dashboards, or documenting what the
216
+ * system offers. Subject-independent, so it never touches storage. The
217
+ * returned object is a fresh copy; mutating it does not affect the resolver.
218
+ * @returns {Catalog}
219
+ */
220
+ catalog() {
221
+ return {
222
+ defaultPlan,
223
+ plans: Object.fromEntries(
224
+ Object.entries(catalog).map(([name, p]) => [name, {
225
+ features: { ...(p.features ?? {}) },
226
+ limits: { ...(p.limits ?? {}) },
227
+ }]),
228
+ ),
229
+ features: [...featureUniverse],
230
+ limits: [...limitUniverse],
231
+ }
232
+ },
233
+
203
234
  /**
204
235
  * Assign a subject to a plan. Takes effect immediately.
205
- * @param {string} subject
236
+ * @param {string} subject - the subject identifier (whatever id you key entitlements by, such as an account or user id)
206
237
  * @param {string} plan - a key of the plan catalog
207
238
  */
208
239
  async assign(subject, plan) {
@@ -216,7 +247,7 @@ export function createEntitlements(options = {}) {
216
247
  * Remove a subject's plan assignment, reverting them to the default plan.
217
248
  * Distinct from assigning the default plan explicitly: an unassigned subject
218
249
  * follows `defaultPlan` if it later changes.
219
- * @param {string} subject
250
+ * @param {string} subject - the subject identifier whose assignment is removed
220
251
  */
221
252
  async unassign(subject) {
222
253
  if (!subject) throw new Error("unassign requires a `subject`")
@@ -230,8 +261,8 @@ export function createEntitlements(options = {}) {
230
261
  * accumulate: overriding `seats`, then later overriding `sso`, leaves both in
231
262
  * place, and overriding a key again updates just that key. Use clearOverride
232
263
  * to remove overrides. Takes effect immediately.
233
- * @param {string} subject
234
- * @param {Override} data
264
+ * @param {string} subject - the subject identifier to override
265
+ * @param {Override} data - the features and/or limits to override; merged into any existing override
235
266
  */
236
267
  async override(subject, data) {
237
268
  if (!subject) throw new Error("override requires a `subject`")
@@ -244,8 +275,8 @@ export function createEntitlements(options = {}) {
244
275
  * Remove overrides for a subject. With no `keys`, removes the entire override
245
276
  * and the subject falls back to plain plan entitlements. With `keys`, removes
246
277
  * only those override entries (reverting them to the plan) and keeps the rest.
247
- * @param {string} subject
248
- * @param {{ features?: string[], limits?: string[] }} [keys]
278
+ * @param {string} subject - the subject identifier whose override is cleared
279
+ * @param {{ features?: string[], limits?: string[] }} [keys] - the specific feature and limit keys to revert; omit to clear the entire override
249
280
  */
250
281
  async clearOverride(subject, keys) {
251
282
  if (!subject) throw new Error("clearOverride requires a `subject`")
@@ -259,7 +290,7 @@ export function createEntitlements(options = {}) {
259
290
 
260
291
  /**
261
292
  * The subject's effective plan name (the default plan if unassigned).
262
- * @param {string} subject
293
+ * @param {string} subject - the subject identifier to resolve
263
294
  * @returns {Promise<string>}
264
295
  */
265
296
  async plan(subject) {
@@ -270,8 +301,8 @@ export function createEntitlements(options = {}) {
270
301
  * Whether a capability flag is granted to the subject. Throws on a feature
271
302
  * key outside the declared/derived universe, so typos surface instead of
272
303
  * silently returning false.
273
- * @param {string} subject
274
- * @param {string} feature
304
+ * @param {string} subject - the subject identifier to resolve
305
+ * @param {string} feature - the feature key to check; must be in the declared/derived feature universe
275
306
  * @returns {Promise<boolean>}
276
307
  */
277
308
  async can(subject, feature) {
@@ -288,8 +319,8 @@ export function createEntitlements(options = {}) {
288
319
  * subject's plan does not grant - never silently unlimited. Throws on a key
289
320
  * outside the declared/derived universe. This is the static ceiling; it does
290
321
  * not read usage.
291
- * @param {string} subject
292
- * @param {string} key
322
+ * @param {string} subject - the subject identifier to resolve
323
+ * @param {string} key - the limit key to resolve; must be in the declared/derived limit universe
293
324
  * @returns {Promise<number|null>}
294
325
  */
295
326
  limit(subject, key) {
@@ -301,7 +332,7 @@ export function createEntitlements(options = {}) {
301
332
  * reads current usage from the meter for the metric of the same name. This is
302
333
  * the composition seam: entitle supplies the limit, meter supplies the usage.
303
334
  * Requires a `meter` to have been passed to `createEntitlements`.
304
- * @param {string} subject
335
+ * @param {string} subject - the subject identifier; must match the id usage is recorded under in the meter
305
336
  * @param {string} key - a limit key that is also a meter metric
306
337
  * @param {{ period?: any, range?: any }} [usageQuery] - forwarded to `meter.usage`
307
338
  * @returns {Promise<CheckResult>}
@@ -328,7 +359,7 @@ export function createEntitlements(options = {}) {
328
359
 
329
360
  /**
330
361
  * The subject's full effective entitlements, for a settings or billing page.
331
- * @param {string} subject
362
+ * @param {string} subject - the subject identifier to resolve
332
363
  * @returns {Promise<Effective>}
333
364
  */
334
365
  async describe(subject) {
@@ -9,9 +9,19 @@
9
9
  export function createEntitlements(options?: EntitlementsOptions): {
10
10
  /** Create the backing tables if they do not exist. Idempotent. */
11
11
  setup(): any;
12
+ /**
13
+ * The static configuration of this resolver: the plan catalog, the default
14
+ * plan, and the full feature and limit universes. Unlike describe(), which
15
+ * resolves a single subject, this exposes every plan and every declared key
16
+ * - for plan comparison tables, admin dashboards, or documenting what the
17
+ * system offers. Subject-independent, so it never touches storage. The
18
+ * returned object is a fresh copy; mutating it does not affect the resolver.
19
+ * @returns {Catalog}
20
+ */
21
+ catalog(): Catalog;
12
22
  /**
13
23
  * Assign a subject to a plan. Takes effect immediately.
14
- * @param {string} subject
24
+ * @param {string} subject - the subject identifier (whatever id you key entitlements by, such as an account or user id)
15
25
  * @param {string} plan - a key of the plan catalog
16
26
  */
17
27
  assign(subject: string, plan: string): Promise<void>;
@@ -19,7 +29,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
19
29
  * Remove a subject's plan assignment, reverting them to the default plan.
20
30
  * Distinct from assigning the default plan explicitly: an unassigned subject
21
31
  * follows `defaultPlan` if it later changes.
22
- * @param {string} subject
32
+ * @param {string} subject - the subject identifier whose assignment is removed
23
33
  */
24
34
  unassign(subject: string): Promise<void>;
25
35
  /**
@@ -28,16 +38,16 @@ export function createEntitlements(options?: EntitlementsOptions): {
28
38
  * accumulate: overriding `seats`, then later overriding `sso`, leaves both in
29
39
  * place, and overriding a key again updates just that key. Use clearOverride
30
40
  * to remove overrides. Takes effect immediately.
31
- * @param {string} subject
32
- * @param {Override} data
41
+ * @param {string} subject - the subject identifier to override
42
+ * @param {Override} data - the features and/or limits to override; merged into any existing override
33
43
  */
34
44
  override(subject: string, data: Override): Promise<void>;
35
45
  /**
36
46
  * Remove overrides for a subject. With no `keys`, removes the entire override
37
47
  * and the subject falls back to plain plan entitlements. With `keys`, removes
38
48
  * only those override entries (reverting them to the plan) and keeps the rest.
39
- * @param {string} subject
40
- * @param {{ features?: string[], limits?: string[] }} [keys]
49
+ * @param {string} subject - the subject identifier whose override is cleared
50
+ * @param {{ features?: string[], limits?: string[] }} [keys] - the specific feature and limit keys to revert; omit to clear the entire override
41
51
  */
42
52
  clearOverride(subject: string, keys?: {
43
53
  features?: string[];
@@ -45,7 +55,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
45
55
  }): Promise<void>;
46
56
  /**
47
57
  * The subject's effective plan name (the default plan if unassigned).
48
- * @param {string} subject
58
+ * @param {string} subject - the subject identifier to resolve
49
59
  * @returns {Promise<string>}
50
60
  */
51
61
  plan(subject: string): Promise<string>;
@@ -53,8 +63,8 @@ export function createEntitlements(options?: EntitlementsOptions): {
53
63
  * Whether a capability flag is granted to the subject. Throws on a feature
54
64
  * key outside the declared/derived universe, so typos surface instead of
55
65
  * silently returning false.
56
- * @param {string} subject
57
- * @param {string} feature
66
+ * @param {string} subject - the subject identifier to resolve
67
+ * @param {string} feature - the feature key to check; must be in the declared/derived feature universe
58
68
  * @returns {Promise<boolean>}
59
69
  */
60
70
  can(subject: string, feature: string): Promise<boolean>;
@@ -64,8 +74,8 @@ export function createEntitlements(options?: EntitlementsOptions): {
64
74
  * subject's plan does not grant - never silently unlimited. Throws on a key
65
75
  * outside the declared/derived universe. This is the static ceiling; it does
66
76
  * not read usage.
67
- * @param {string} subject
68
- * @param {string} key
77
+ * @param {string} subject - the subject identifier to resolve
78
+ * @param {string} key - the limit key to resolve; must be in the declared/derived limit universe
69
79
  * @returns {Promise<number|null>}
70
80
  */
71
81
  limit(subject: string, key: string): Promise<number | null>;
@@ -74,7 +84,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
74
84
  * reads current usage from the meter for the metric of the same name. This is
75
85
  * the composition seam: entitle supplies the limit, meter supplies the usage.
76
86
  * Requires a `meter` to have been passed to `createEntitlements`.
77
- * @param {string} subject
87
+ * @param {string} subject - the subject identifier; must match the id usage is recorded under in the meter
78
88
  * @param {string} key - a limit key that is also a meter metric
79
89
  * @param {{ period?: any, range?: any }} [usageQuery] - forwarded to `meter.usage`
80
90
  * @returns {Promise<CheckResult>}
@@ -85,7 +95,7 @@ export function createEntitlements(options?: EntitlementsOptions): {
85
95
  }): Promise<CheckResult>;
86
96
  /**
87
97
  * The subject's full effective entitlements, for a settings or billing page.
88
- * @param {string} subject
98
+ * @param {string} subject - the subject identifier to resolve
89
99
  * @returns {Promise<Effective>}
90
100
  */
91
101
  describe(subject: string): Promise<Effective>;
@@ -108,7 +118,13 @@ export type Plan = {
108
118
  * negotiated more seats, or got one feature switched on).
109
119
  */
110
120
  export type Override = {
121
+ /**
122
+ * - feature flags to override for this subject; merged over the plan's features, so include only the ones that differ
123
+ */
111
124
  features?: Record<string, boolean>;
125
+ /**
126
+ * - limit ceilings to override for this subject; merged over the plan's limits, so include only the ones that differ (`null` means unlimited)
127
+ */
112
128
  limits?: Record<string, number | null>;
113
129
  };
114
130
  export type EntitlementsOptions = {
@@ -151,12 +167,36 @@ export type EntitlementsOptions = {
151
167
  };
152
168
  export type Effective = {
153
169
  /**
154
- * - the effective plan name
170
+ * - the effective plan name (the default plan if the subject is unassigned)
155
171
  */
156
172
  plan: string;
173
+ /**
174
+ * - the resolved feature flags, after applying the subject's override on top of the plan
175
+ */
157
176
  features: Record<string, boolean>;
177
+ /**
178
+ * - the resolved limit ceilings, after applying the subject's override on top of the plan (`null` means unlimited)
179
+ */
158
180
  limits: Record<string, number | null>;
159
181
  };
182
+ export type Catalog = {
183
+ /**
184
+ * - the plan applied to a subject with no assignment
185
+ */
186
+ defaultPlan: string;
187
+ /**
188
+ * - the declared plan catalog (your pricing tiers)
189
+ */
190
+ plans: Record<string, Plan>;
191
+ /**
192
+ * - the full feature universe (declared, or derived from the union across plans)
193
+ */
194
+ features: string[];
195
+ /**
196
+ * - the full limit universe (declared, or derived from the union across plans)
197
+ */
198
+ limits: string[];
199
+ };
160
200
  export type CheckResult = {
161
201
  /**
162
202
  * - whether current usage is below the limit (always true when the limit is unlimited)