@prsm/entitle 1.0.0 → 1.1.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 +11 -2
- package/package.json +1 -1
- package/src/entitlements.js +32 -6
- package/types/entitlements.d.ts +13 -4
package/README.md
CHANGED
|
@@ -104,6 +104,15 @@ The **plan catalog** is declared once at construction, the way you declare your
|
|
|
104
104
|
|
|
105
105
|
**Assignments** (which plan a subject is on) and **overrides** (per-subject adjustments) live in postgres and are mutable at runtime. An override shallow-merges over the plan, so you specify only what differs. A subject with no assignment gets `defaultPlan`.
|
|
106
106
|
|
|
107
|
+
Overrides accumulate: each `override()` call merges into the subject's existing override rather than replacing it, so granting more seats and later enabling a feature leaves both in place, and overriding a key again updates just that key. `clearOverride(subject)` removes the whole override; `clearOverride(subject, { limits: ["seats"] })` reverts only the named keys and keeps the rest.
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
await entitlements.override(account.id, { limits: { seats: 50 } })
|
|
111
|
+
await entitlements.override(account.id, { features: { sso: true } }) // seats override stays
|
|
112
|
+
await entitlements.clearOverride(account.id, { limits: ["seats"] }) // seats reverts to plan, sso stays
|
|
113
|
+
await entitlements.clearOverride(account.id) // back to plain plan
|
|
114
|
+
```
|
|
115
|
+
|
|
107
116
|
Resolved entitlements are cached per subject for a short, configurable window (`cacheTtl`, default `"10s"`) and the cache is invalidated immediately on `assign`, `override`, and `clearOverride`, so the instance making a change sees it at once and other instances converge within the TTL. Set `cacheTtl: 0` to read postgres on every call.
|
|
108
117
|
|
|
109
118
|
## API
|
|
@@ -124,9 +133,9 @@ Assigns a subject to a plan. Takes effect immediately.
|
|
|
124
133
|
|
|
125
134
|
Layers a per-subject override on top of the plan. Shallow-merges; pass only what differs.
|
|
126
135
|
|
|
127
|
-
### `entitlements.clearOverride(subject)`
|
|
136
|
+
### `entitlements.clearOverride(subject, keys?)`
|
|
128
137
|
|
|
129
|
-
|
|
138
|
+
With no `keys`, removes the subject's entire override. With `keys` (`{ features?: string[], limits?: string[] }`), removes only those entries and keeps the rest.
|
|
130
139
|
|
|
131
140
|
### `entitlements.can(subject, feature)`
|
|
132
141
|
|
package/package.json
CHANGED
package/src/entitlements.js
CHANGED
|
@@ -156,25 +156,51 @@ export function createEntitlements(options = {}) {
|
|
|
156
156
|
},
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
159
|
+
* Add or adjust a per-subject override, layered on top of the subject's plan.
|
|
160
|
+
* Merges into any existing override for the subject, so repeated calls
|
|
161
|
+
* accumulate: overriding `seats`, then later overriding `sso`, leaves both in
|
|
162
|
+
* place, and overriding a key again updates just that key. Use clearOverride
|
|
163
|
+
* to remove overrides. Takes effect immediately.
|
|
161
164
|
* @param {string} subject
|
|
162
165
|
* @param {Override} data
|
|
163
166
|
*/
|
|
164
167
|
async override(subject, data) {
|
|
165
168
|
if (!subject) throw new Error("override requires a `subject`")
|
|
166
169
|
validateOverride(data)
|
|
167
|
-
await driver.
|
|
170
|
+
const current = (await driver.getState(subject))?.override ?? {}
|
|
171
|
+
await driver.setOverride(subject, {
|
|
172
|
+
features: { ...(current.features ?? {}), ...(data.features ?? {}) },
|
|
173
|
+
limits: { ...(current.limits ?? {}), ...(data.limits ?? {}) },
|
|
174
|
+
})
|
|
168
175
|
cache.invalidate(subject)
|
|
169
176
|
},
|
|
170
177
|
|
|
171
178
|
/**
|
|
172
|
-
* Remove a subject
|
|
179
|
+
* Remove overrides for a subject. With no `keys`, removes the entire override
|
|
180
|
+
* and the subject falls back to plain plan entitlements. With `keys`, removes
|
|
181
|
+
* only those override entries (reverting them to the plan) and keeps the rest.
|
|
173
182
|
* @param {string} subject
|
|
183
|
+
* @param {{ features?: string[], limits?: string[] }} [keys]
|
|
174
184
|
*/
|
|
175
|
-
async clearOverride(subject) {
|
|
185
|
+
async clearOverride(subject, keys) {
|
|
176
186
|
if (!subject) throw new Error("clearOverride requires a `subject`")
|
|
177
|
-
|
|
187
|
+
if (!keys) {
|
|
188
|
+
await driver.clearOverride(subject)
|
|
189
|
+
cache.invalidate(subject)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
const current = (await driver.getState(subject))?.override
|
|
193
|
+
if (current) {
|
|
194
|
+
const features = { ...(current.features ?? {}) }
|
|
195
|
+
const limits = { ...(current.limits ?? {}) }
|
|
196
|
+
for (const k of keys.features ?? []) delete features[k]
|
|
197
|
+
for (const k of keys.limits ?? []) delete limits[k]
|
|
198
|
+
if (Object.keys(features).length === 0 && Object.keys(limits).length === 0) {
|
|
199
|
+
await driver.clearOverride(subject)
|
|
200
|
+
} else {
|
|
201
|
+
await driver.setOverride(subject, { features, limits })
|
|
202
|
+
}
|
|
203
|
+
}
|
|
178
204
|
cache.invalidate(subject)
|
|
179
205
|
},
|
|
180
206
|
|
package/types/entitlements.d.ts
CHANGED
|
@@ -16,17 +16,26 @@ export function createEntitlements(options?: EntitlementsOptions): {
|
|
|
16
16
|
*/
|
|
17
17
|
assign(subject: string, plan: string): Promise<void>;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Add or adjust a per-subject override, layered on top of the subject's plan.
|
|
20
|
+
* Merges into any existing override for the subject, so repeated calls
|
|
21
|
+
* accumulate: overriding `seats`, then later overriding `sso`, leaves both in
|
|
22
|
+
* place, and overriding a key again updates just that key. Use clearOverride
|
|
23
|
+
* to remove overrides. Takes effect immediately.
|
|
21
24
|
* @param {string} subject
|
|
22
25
|
* @param {Override} data
|
|
23
26
|
*/
|
|
24
27
|
override(subject: string, data: Override): Promise<void>;
|
|
25
28
|
/**
|
|
26
|
-
* Remove a subject
|
|
29
|
+
* Remove overrides for a subject. With no `keys`, removes the entire override
|
|
30
|
+
* and the subject falls back to plain plan entitlements. With `keys`, removes
|
|
31
|
+
* only those override entries (reverting them to the plan) and keeps the rest.
|
|
27
32
|
* @param {string} subject
|
|
33
|
+
* @param {{ features?: string[], limits?: string[] }} [keys]
|
|
28
34
|
*/
|
|
29
|
-
clearOverride(subject: string
|
|
35
|
+
clearOverride(subject: string, keys?: {
|
|
36
|
+
features?: string[];
|
|
37
|
+
limits?: string[];
|
|
38
|
+
}): Promise<void>;
|
|
30
39
|
/**
|
|
31
40
|
* The subject's effective plan name (the default plan if unassigned).
|
|
32
41
|
* @param {string} subject
|