@stigg/entitlements-runtime 2.0.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.
@@ -0,0 +1,6 @@
1
+ export { EntitlementEvaluator } from './services/entitlementEvaluator';
2
+ export { Decision } from './services/entitlementDecisionService';
3
+ export { EntitlementFallbacks } from './services/entitlementFallbacks';
4
+ export { EntitlementEvaluation, FeatureEntitlementKind, ResolvedCreditEntitlement, ResolvedFeatureEntitlement, } from './types/entitlementEvaluation';
5
+ export { BooleanEntitlementFallback, CreditEntitlementFallback, CreditEntitlementsFallback, EntitlementFallbackConfig, EntitlementFallback, EntitlementsFallback, EnumEntitlementFallback, MeteredEntitlementFallback, NumericEntitlementFallback, } from './types/entitlementFallback';
6
+ export { BooleanEntitlementOptions, CreditEntitlementOptions, EntitlementReadOptions, EnumEntitlementOptions, FeatureEntitlementOptions, MeteredEntitlementOptions, NumericEntitlementOptions, } from './types/entitlementOptions';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitlementFallbacks = exports.EntitlementEvaluator = void 0;
4
+ // Evaluator
5
+ var entitlementEvaluator_1 = require("./services/entitlementEvaluator");
6
+ Object.defineProperty(exports, "EntitlementEvaluator", { enumerable: true, get: function () { return entitlementEvaluator_1.EntitlementEvaluator; } });
7
+ var entitlementFallbacks_1 = require("./services/entitlementFallbacks");
8
+ Object.defineProperty(exports, "EntitlementFallbacks", { enumerable: true, get: function () { return entitlementFallbacks_1.EntitlementFallbacks; } });
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsWUFBWTtBQUNaLHdFQUF1RTtBQUE5RCw0SEFBQSxvQkFBb0IsT0FBQTtBQUU3Qix3RUFBdUU7QUFBOUQsNEhBQUEsb0JBQW9CLE9BQUEifQ==
@@ -0,0 +1,19 @@
1
+ import { AccessDeniedReason, CachedEntitlement, EntitlementType, Maybe } from '@stigg/cache-core';
2
+ export interface DecideEntitlementPolicyParams {
3
+ entitlementType: EntitlementType;
4
+ accessDeniedReason: Maybe<AccessDeniedReason>;
5
+ entitlement: Maybe<CachedEntitlement>;
6
+ requestedUsage?: number;
7
+ requestedValues?: string[];
8
+ }
9
+ export declare class EntitlementDecisionService {
10
+ static decideEntitlementPolicy({ entitlementType, accessDeniedReason, entitlement, requestedUsage, requestedValues, }: DecideEntitlementPolicyParams): Decision;
11
+ private static getNotFoundReason;
12
+ private static decideFeatureAccess;
13
+ private static decideCreditAccess;
14
+ private static decideUsageBasedAccess;
15
+ }
16
+ export interface Decision {
17
+ readonly hasAccess: boolean;
18
+ readonly accessDeniedReason?: AccessDeniedReason;
19
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitlementDecisionService = void 0;
4
+ const cache_core_1 = require("@stigg/cache-core");
5
+ const lodash_1 = require("lodash");
6
+ class EntitlementDecisionService {
7
+ static decideEntitlementPolicy({ entitlementType, accessDeniedReason, entitlement, requestedUsage = 0, requestedValues = [], }) {
8
+ // Entitlement-first: if found, evaluate it normally.
9
+ // Fall back to global accessDeniedReason only when no entitlement exists.
10
+ if (!entitlement) {
11
+ if (accessDeniedReason) {
12
+ return { accessDeniedReason, hasAccess: false };
13
+ }
14
+ return { hasAccess: false, accessDeniedReason: this.getNotFoundReason(entitlementType) };
15
+ }
16
+ if (entitlement.calculatedEntitlement.accessDeniedReason) {
17
+ return {
18
+ hasAccess: false,
19
+ accessDeniedReason: entitlement.calculatedEntitlement.accessDeniedReason,
20
+ };
21
+ }
22
+ if ((0, cache_core_1.isCreditEntitlement)(entitlement)) {
23
+ return this.decideCreditAccess(entitlement);
24
+ }
25
+ if ((0, cache_core_1.isFeatureEntitlement)(entitlement)) {
26
+ return this.decideFeatureAccess(entitlement, requestedUsage, requestedValues);
27
+ }
28
+ return { hasAccess: false, accessDeniedReason: cache_core_1.AccessDeniedReason.NoFeatureEntitlementInSubscription };
29
+ }
30
+ static getNotFoundReason(entitlementType) {
31
+ if (entitlementType === cache_core_1.EntitlementType.Feature) {
32
+ // for backward compatability
33
+ return cache_core_1.AccessDeniedReason.NoFeatureEntitlementInSubscription;
34
+ }
35
+ return cache_core_1.AccessDeniedReason.EntitlementNotFound;
36
+ }
37
+ static decideFeatureAccess(entitlement, requestUsage, requestedValues) {
38
+ const { feature } = entitlement.calculatedEntitlement;
39
+ const { featureType, meterType } = feature;
40
+ switch (featureType) {
41
+ case cache_core_1.FeatureType.Boolean:
42
+ return { hasAccess: true };
43
+ case cache_core_1.FeatureType.Enum: {
44
+ const missingValues = (0, lodash_1.difference)(requestedValues, entitlement.calculatedEntitlement.enumValues || []);
45
+ if (!(0, lodash_1.isEmpty)(missingValues)) {
46
+ return {
47
+ hasAccess: false,
48
+ accessDeniedReason: cache_core_1.AccessDeniedReason.RequestedValuesMismatch,
49
+ };
50
+ }
51
+ return { hasAccess: true };
52
+ }
53
+ case cache_core_1.FeatureType.Number: {
54
+ if (meterType === cache_core_1.MeterType.None) {
55
+ return { hasAccess: true };
56
+ }
57
+ if (entitlement.calculatedEntitlement.hasUnlimitedUsage || entitlement.calculatedEntitlement.hasSoftLimit) {
58
+ return { hasAccess: true };
59
+ }
60
+ const usageLimit = entitlement.calculatedEntitlement.usageLimit || 0;
61
+ const currentUsage = entitlement.usageData.currentUsage;
62
+ return this.decideUsageBasedAccess(usageLimit, currentUsage, requestUsage);
63
+ }
64
+ }
65
+ }
66
+ static decideCreditAccess(entitlement) {
67
+ if (entitlement.calculatedEntitlement.hasSoftLimit) {
68
+ return { hasAccess: true };
69
+ }
70
+ const { usageLimit } = entitlement.calculatedEntitlement;
71
+ const { currentUsage } = entitlement.usageData;
72
+ // checking at least 1 credit to deny access when none remaining
73
+ const requestUsage = 1;
74
+ return this.decideUsageBasedAccess(usageLimit, currentUsage, requestUsage);
75
+ }
76
+ static decideUsageBasedAccess(usageLimit, currentUsage, requestUsage) {
77
+ if (currentUsage + requestUsage <= usageLimit) {
78
+ return { hasAccess: true };
79
+ }
80
+ else {
81
+ return { hasAccess: false, accessDeniedReason: cache_core_1.AccessDeniedReason.RequestedUsageExceedingLimit };
82
+ }
83
+ }
84
+ }
85
+ exports.EntitlementDecisionService = EntitlementDecisionService;
86
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXRsZW1lbnREZWNpc2lvblNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvZW50aXRsZW1lbnREZWNpc2lvblNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsa0RBUzJCO0FBQzNCLG1DQUE2QztBQVU3QyxNQUFhLDBCQUEwQjtJQUM5QixNQUFNLENBQUMsdUJBQXVCLENBQUMsRUFDcEMsZUFBZSxFQUNmLGtCQUFrQixFQUNsQixXQUFXLEVBQ1gsY0FBYyxHQUFHLENBQUMsRUFDbEIsZUFBZSxHQUFHLEVBQUUsR0FDVTtRQUM5QixxREFBcUQ7UUFDckQsMEVBQTBFO1FBQzFFLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNqQixJQUFJLGtCQUFrQixFQUFFLENBQUM7Z0JBQ3ZCLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLENBQUM7WUFDbEQsQ0FBQztZQUNELE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLGtCQUFrQixFQUFFLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxlQUFlLENBQUMsRUFBRSxDQUFDO1FBQzNGLENBQUM7UUFFRCxJQUFJLFdBQVcsQ0FBQyxxQkFBcUIsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1lBQ3pELE9BQU87Z0JBQ0wsU0FBUyxFQUFFLEtBQUs7Z0JBQ2hCLGtCQUFrQixFQUFFLFdBQVcsQ0FBQyxxQkFBcUIsQ0FBQyxrQkFBa0I7YUFDekUsQ0FBQztRQUNKLENBQUM7UUFFRCxJQUFJLElBQUEsZ0NBQW1CLEVBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUNyQyxPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUM5QyxDQUFDO1FBRUQsSUFBSSxJQUFBLGlDQUFvQixFQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7WUFDdEMsT0FBTyxJQUFJLENBQUMsbUJBQW1CLENBQUMsV0FBVyxF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
@@ -0,0 +1,24 @@
1
+ import { AccessDeniedReason, CachedEntitlement, EntitlementQuery, EntitlementType, Maybe } from '@stigg/cache-core';
2
+ import { EntitlementFallbacks } from './entitlementFallbacks';
3
+ import { BooleanEntitlementOptions, CreditEntitlementOptions, EntitlementReadOptions, EnumEntitlementOptions, MeteredEntitlementOptions, NumericEntitlementOptions } from '../types/entitlementOptions';
4
+ import { EntitlementEvaluation, FeatureEntitlement, FeatureQuery } from '../types/entitlementEvaluation';
5
+ export declare class EntitlementEvaluator {
6
+ readonly fallbacks: EntitlementFallbacks;
7
+ constructor(fallbacks: EntitlementFallbacks);
8
+ evaluateBoolean(query: FeatureQuery, entitlement: FeatureEntitlement, accessDeniedReason: Maybe<AccessDeniedReason>, options?: BooleanEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
9
+ evaluateNumeric(query: FeatureQuery, entitlement: FeatureEntitlement, accessDeniedReason: Maybe<AccessDeniedReason>, options?: NumericEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
10
+ evaluateMetered(query: FeatureQuery, entitlement: FeatureEntitlement, accessDeniedReason: Maybe<AccessDeniedReason>, options?: MeteredEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
11
+ evaluateEnum(query: FeatureQuery, entitlement: FeatureEntitlement, accessDeniedReason: Maybe<AccessDeniedReason>, options?: EnumEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
12
+ evaluateCredit(query: EntitlementQuery<EntitlementType.Credit>, entitlement: CachedEntitlement<EntitlementType.Credit> | null, accessDeniedReason: Maybe<AccessDeniedReason>, options?: CreditEntitlementOptions): EntitlementEvaluation<EntitlementType.Credit>;
13
+ /**
14
+ * The type-agnostic path, for callers that state no expected kind up front, so the kind is
15
+ * inferred from the options rather than asserted against them.
16
+ *
17
+ * Differs from the typed reads on one point, deliberately: here a miss *does* answer from the
18
+ * fallback. The typed reads treat "not in the subscription" as a real answer and ignore the
19
+ * fallback; `getEntitlement`/`getEntitlementV2` have always fallen back instead, and callers
20
+ * depend on it. Only the precedence between a per-call and a configured fallback is shared.
21
+ */
22
+ evaluate<TType extends EntitlementType>(query: EntitlementQuery<TType>, cached: CachedEntitlement<TType> | null, accessDeniedReason: Maybe<AccessDeniedReason>, options?: EntitlementReadOptions): EntitlementEvaluation<TType>;
23
+ private evaluateFeature;
24
+ }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitlementEvaluator = void 0;
4
+ const cache_core_1 = require("@stigg/cache-core");
5
+ const entitlementDecisionService_1 = require("./entitlementDecisionService");
6
+ const entitlementOptions_1 = require("../types/entitlementOptions");
7
+ const resolveEntitlement_1 = require("./resolveEntitlement");
8
+ const MATCHES_KIND = {
9
+ BOOLEAN: cache_core_1.isBoolean,
10
+ NUMERIC: cache_core_1.isNumeric,
11
+ METERED: cache_core_1.isMetered,
12
+ ENUM: cache_core_1.isEnum,
13
+ };
14
+ class EntitlementEvaluator {
15
+ constructor(fallbacks) {
16
+ this.fallbacks = fallbacks;
17
+ }
18
+ evaluateBoolean(query, entitlement, accessDeniedReason, options) {
19
+ return this.evaluateFeature(query, entitlement, accessDeniedReason, 'BOOLEAN', options);
20
+ }
21
+ evaluateNumeric(query, entitlement, accessDeniedReason, options) {
22
+ return this.evaluateFeature(query, entitlement, accessDeniedReason, 'NUMERIC', options);
23
+ }
24
+ evaluateMetered(query, entitlement, accessDeniedReason, options) {
25
+ return this.evaluateFeature(query, entitlement, accessDeniedReason, 'METERED', options);
26
+ }
27
+ evaluateEnum(query, entitlement, accessDeniedReason, options) {
28
+ return this.evaluateFeature(query, entitlement, accessDeniedReason, 'ENUM', options);
29
+ }
30
+ evaluateCredit(query, entitlement, accessDeniedReason, options) {
31
+ var _a;
32
+ const decision = entitlementDecisionService_1.EntitlementDecisionService.decideEntitlementPolicy({
33
+ entitlementType: cache_core_1.EntitlementType.Credit,
34
+ accessDeniedReason,
35
+ entitlement,
36
+ });
37
+ // Unlike a feature miss, a credit miss does answer from the fallback when one is available.
38
+ const fallback = entitlement ? undefined : this.fallbacks.resolve(query, options === null || options === void 0 ? void 0 : options.fallback);
39
+ const hasAccess = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.hasAccess) !== null && _a !== void 0 ? _a : decision.hasAccess;
40
+ return Object.assign(Object.assign({}, decision), { hasAccess,
41
+ // The reason belongs to the decision that was overridden, so a granted answer must drop it —
42
+ // the same rule the untyped path applies.
43
+ accessDeniedReason: hasAccess ? undefined : decision.accessDeniedReason, query, resolved: (0, resolveEntitlement_1.resolveCredit)(query.id, entitlement, fallback), entitlement: entitlement !== null && entitlement !== void 0 ? entitlement : undefined, isFallback: !!fallback });
44
+ }
45
+ /**
46
+ * The type-agnostic path, for callers that state no expected kind up front, so the kind is
47
+ * inferred from the options rather than asserted against them.
48
+ *
49
+ * Differs from the typed reads on one point, deliberately: here a miss *does* answer from the
50
+ * fallback. The typed reads treat "not in the subscription" as a real answer and ignore the
51
+ * fallback; `getEntitlement`/`getEntitlementV2` have always fallen back instead, and callers
52
+ * depend on it. Only the precedence between a per-call and a configured fallback is shared.
53
+ */
54
+ evaluate(query, cached, accessDeniedReason, options) {
55
+ var _a;
56
+ // A persisted entry is keyed independently of the kind recorded inside it, and `fromJSON`
57
+ // checks neither — so treat a disagreeing entry as absent rather than answering with the kind
58
+ // it claims to be, which would hand a feature read a credit model.
59
+ const entitlement = (cached === null || cached === void 0 ? void 0 : cached.calculatedEntitlement.type) === query.type ? cached : null;
60
+ const { requestedUsage, requestedValues } = options !== null && options !== void 0 ? options : {};
61
+ const decision = entitlementDecisionService_1.EntitlementDecisionService.decideEntitlementPolicy({
62
+ entitlementType: query.type,
63
+ accessDeniedReason,
64
+ entitlement,
65
+ requestedUsage,
66
+ requestedValues,
67
+ });
68
+ const isCredit = query.type === cache_core_1.EntitlementType.Credit;
69
+ const requestedKind = isCredit ? undefined : (0, entitlementOptions_1.featureKindFromOptions)(options);
70
+ if (!entitlement) {
71
+ const fallback = this.fallbacks.resolve(query, options === null || options === void 0 ? void 0 : options.fallback);
72
+ const hasAccess = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.hasAccess) !== null && _a !== void 0 ? _a : decision.hasAccess;
73
+ return {
74
+ hasAccess,
75
+ // A granted answer carries no denial reason, which is what this path has always returned.
76
+ accessDeniedReason: hasAccess ? undefined : decision.accessDeniedReason,
77
+ query,
78
+ resolved: (0, resolveEntitlement_1.resolveCacheMiss)(query, fallback),
79
+ isFallback: true,
80
+ requestedKind,
81
+ requestedUsage,
82
+ requestedValues,
83
+ };
84
+ }
85
+ return Object.assign(Object.assign({}, decision), { query, resolved: (0, resolveEntitlement_1.resolveEntitlement)(entitlement), entitlement, isFallback: false, requestedKind,
86
+ requestedUsage,
87
+ requestedValues });
88
+ }
89
+ evaluateFeature(query, entitlement, accessDeniedReason, requestedKind, options) {
90
+ var _a;
91
+ const { requestedUsage, requestedValues } = options !== null && options !== void 0 ? options : {};
92
+ const decision = entitlementDecisionService_1.EntitlementDecisionService.decideEntitlementPolicy({
93
+ entitlementType: cache_core_1.EntitlementType.Feature,
94
+ accessDeniedReason,
95
+ entitlement,
96
+ requestedUsage,
97
+ requestedValues,
98
+ });
99
+ // The caller asked for a kind this entitlement isn't, so their fallback is the answer. A plain
100
+ // miss is a real answer ("not in the subscription") and deliberately ignores the fallback, which
101
+ // is why the fallback is only resolved here.
102
+ if (entitlement && !MATCHES_KIND[requestedKind](entitlement.calculatedEntitlement.feature)) {
103
+ const fallback = this.fallbacks.resolve(query, options === null || options === void 0 ? void 0 : options.fallback);
104
+ const hasAccess = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.hasAccess) !== null && _a !== void 0 ? _a : false;
105
+ return {
106
+ hasAccess,
107
+ // The mismatch is the reason access was denied, so a fallback that grants it drops the
108
+ // reason — the same rule the credit and untyped paths apply.
109
+ accessDeniedReason: hasAccess ? undefined : cache_core_1.AccessDeniedReason.FeatureTypeMismatch,
110
+ query,
111
+ resolved: (0, resolveEntitlement_1.resolveFeature)(null, fallback),
112
+ isFallback: true,
113
+ requestedKind,
114
+ requestedUsage,
115
+ requestedValues,
116
+ };
117
+ }
118
+ return Object.assign(Object.assign({}, decision), { query, resolved: (0, resolveEntitlement_1.resolveFeature)(entitlement), entitlement: entitlement !== null && entitlement !== void 0 ? entitlement : undefined, isFallback: false, requestedKind,
119
+ requestedUsage,
120
+ requestedValues });
121
+ }
122
+ }
123
+ exports.EntitlementEvaluator = EntitlementEvaluator;
124
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXRsZW1lbnRFdmFsdWF0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvZW50aXRsZW1lbnRFdmFsdWF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsa0RBVTJCO0FBQzNCLDZFQUFvRjtBQUVwRixvRUFTcUM7QUFRckMsNkRBQTJHO0FBRTNHLE1BQU0sWUFBWSxHQUFxRDtJQUNyRSxPQUFPLEVBQUUsc0JBQVM7SUFDbEIsT0FBTyxFQUFFLHNCQUFTO0lBQ2xCLE9BQU8sRUFBRSxzQkFBUztJQUNsQixJQUFJLEVBQUUsbUJBQU07Q0FDYixDQUFDO0FBRUYsTUFBYSxvQkFBb0I7SUFDL0IsWUFBcUIsU0FBK0I7UUFBL0IsY0FBUyxHQUFULFNBQVMsQ0FBc0I7SUFBRyxDQUFDO0lBRXhELGVBQWUsQ0FDYixLQUFtQixFQUNuQixXQUErQixFQUMvQixrQkFBNkMsRUFDN0MsT0FBbUM7UUFFbkMsT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsa0JBQWtCLEVBQUUsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzFGLENBQUM7SUFFRCxlQUFlLENBQ2IsS0FBbUIsRUFDbkIsV0FBK0IsRUFDL0Isa0JBQTZDLEVBQzdDLE9BQW1DO1FBRW5DLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLEVBQUUsV0FBVyxFQUFFLGtCQUFrQixFQUFFLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUMxRixDQUFDO0lBRUQsZUFBZSxDQUNiLEtBQW1CLEVBQ25CLFdBQStCLEVBQy9CLGtCQUE2QyxFQUM3QyxPQUFtQztRQUVuQyxPQUFPLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLFdBQVcsRUFBRSxrQkFBa0IsRUFBRSxTQUFTLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDMUYsQ0FBQztJQUVELFlBQVksQ0FDVixLQUFtQixFQUNuQixXQUErQixFQUMvQixrQkFBNkMsRUFDN0MsT0FBZ0M7UUFFaEMsT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZGLENBQUM7SUFFRCxjQUFjLENBQ1osS0FBK0MsRUFDL0MsV0FBNkQsRUFDN0Qsa0JBQTZDLEVBQzdDLE9BQWtDOztRQUVsQyxNQUFNLFFBQVEsR0FBRyx1REFBMEIsQ0FBQyx1QkFBdUIsQ0FBQztZQUNsRSxlQUFlLEVBQUUsNEJBQWUsQ0FBQyxNQUFNO1lBQ3ZDLGtCQUFrQjtZQUNsQixXQUFXO1NBQ1osQ0FBQyxDQUFDO1FBRUgsNEZBQTRGO1FBQzVGLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzVGLE1BQU0sU0FBUyxHQUFHLE1BQUEsUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLFNBQVMsbUNBQUksUUFBUSxDQUFDLFNBQVMsQ0FBQztRQUM1RCx1Q0FDSyxRQUFRLEtBQ1gsU0FBUztZQUNULDZGQUE2RjtZQUM3RiwwQ0FBMEM7WUFDMUMsa0JBQWtCLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsRUFDdkUsS0FBSyxFQUNMLFFBQVEsRUFBRSxJQUFBLGtDQUFhLEVBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxXQUFXLEVBQUUsUUFBUSxDQUFDLEVBQ3hELFdBQVcsRUFBRSxXQUFXLGFBQVgsV0FBVyxjQUFYLFdBQVcsR0FBSSxTQUFTLEVBQ3JDLFVBQVUsRUFBRSxDQUFDLENBQUMsUUFBUSxJQUN0QjtJQUNKLENBQUM7SUFFRDs7Ozs7Ozs7T0FRRztJQUNILFFBQVEsQ0FDTixLQUE4QixFQUM5QixNQUF1QyxFQUN2QyxrQkFBNkMsRUFDN0MsT0FBZ0M7O1FBRWhDLDBGQUEwRjtRQUMxRiw4RkFBOEY7UUFDOUYsbUVBQW1FO1FBQ25FLE1BQU0sV0FBVyxHQUFHLENBQUEsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLHFCQUFxQixDQUFDLElBQUksTUFBSyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUV0RixNQUFNLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSxHQUFHLE9BQU8sYUFBUCxPQUFPLGNBQVAsT0FBTyxHQUFJLEVBQUUsQ0FBQztRQUMxRCxNQUFNLFFBQVEsR0FBRyx1REFBMEIsQ0FBQyx1QkFBdUIsQ0FBQztZQUNsRSxlQUFlLEVBQUUsS0FBSyxDQUFDLElBQUk7WUFDM0Isa0JBQWtCO1lBQ2xCLFdBQVc7WUFDWCxjQUFjO1lBQ2QsZUFBZTtTQUNoQixDQUFDLENBQUM7UUFFSCxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsSUFBSSxLQUFLLDRCQUFlLENBQUMsTUFBTSxDQUFDO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFBLDJDQUFzQixFQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRTdFLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNqQixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQ2xFLE1BQU0sU0FBUyxHQUFHLE1BQUEsUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLFNBQVMsbUNBQUksUUFBUSxDQUFDLFNBQVMsQ0FBQztZQUM1RCxPQUFPO2dCQUNMLFNBQVM7Z0JBQ1QsMEZBQTBGO2dCQUMxRixrQkFBa0IsRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGtCQUFrQjtnQkFDdkUsS0FBSztnQkFDTCxRQUFRLEVBQUUsSUFBQSxxQ0FBZ0IsRUFBQyxLQUFLLEVBQUUsUUFBUSxDQUFDO2dCQUMzQyxVQUFVLEVBQUUsSUFBSTtnQkFDaEIsYUFBYTtnQkFDYixjQUFjO2dCQUNkLGVBQWU7YUFDaEIsQ0FBQztRQUNKLENBQUM7UUFFRCx1Q0FDSyxRQUFRLEtBQ1gsS0FBSyxFQUNMLFFBQVEsRUFBRSxJQUFBLHVDQUFrQixFQUFDLFdBQVcsQ0FBQyxFQUN6QyxXQUFXLEVBQ1gsVUFBVSxFQUFFLEtBQUssRUFDakIsYUFBYTtZQUNiLGNBQWM7WUFDZCxlQUFlLElBQ2Y7SUFDSixDQUFDO0lBRU8sZUFBZSxDQUNyQixLQUFtQixFQUNuQixXQUErQixFQUMvQixrQkFBNkMsRUFDN0MsYUFBcUMsRUFDckMsT0FBbUM7O1FBRW5DLE1BQU0sRUFBRSxjQUFjLEVBQUUsZUFBZSxFQUFFLEdBQUcsT0FBTyxhQUFQLE9BQU8sY0FBUCxPQUFPLEdBQUksRUFBRSxDQUFDO1FBQzFELE1BQU0sUUFBUSxHQUFhLHVEQUEwQixDQUFDLHVCQUF1QixDQUFDO1lBQzVFLGVBQWUsRUFBRSw0QkFBZSxDQUFDLE9BQU87WUFDeEMsa0JBQWtCO1lBQ2xCLFdBQVc7WUFDWCxjQUFjO1lBQ2QsZUFBZTtTQUNoQixDQUFDLENBQUM7UUFFSCwrRkFBK0Y7UUFDL0YsaUdBQWlHO1FBQ2pHLDZDQUE2QztRQUM3QyxJQUFJLFdBQVcsSUFBSSxDQUFDLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQyxXQUFXLENBQUMscUJBQXFCLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztZQUMzRixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQ2xFLE1BQU0sU0FBUyxHQUFHLE1BQUEsUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLFNBQVMsbUNBQUksS0FBSyxDQUFDO1lBQy9DLE9BQU87Z0JBQ0wsU0FBUztnQkFDVCx1RkFBdUY7Z0JBQ3ZGLDZEQUE2RDtnQkFDN0Qsa0JBQWtCLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLCtCQUFrQixDQUFDLG1CQUFtQjtnQkFDbEYsS0FBSztnQkFDTCxRQUFRLEVBQUUsSUFBQSxtQ0FBYyxFQUFDLElBQUksRUFBRSxRQUFRLENBQUM7Z0JBQ3hDLFVBQVUsRUFBRSxJQUFJO2dCQUNoQixhQUFhO2dCQUNiLGNBQWM7Z0JBQ2QsZUFBZTthQUNoQixDQUFDO1FBQ0osQ0FBQztRQUVELHVDQUNLLFFBQVEsS0FDWCxLQUFLLEVBQ0wsUUFBUSxFQUFFLElBQUEsbUNBQWMsRUFBQyxXQUFXLENBQUMsRUFDckMsV0FBVyxFQUFFLFdBQVcsYUFBWCxXQUFXLGNBQVgsV0FBVyxHQUFJLFNBQVMsRUFDckMsVUFBVSxFQUFFLEtBQUssRUFDakIsYUFBYTtZQUNiLGNBQWM7WUFDZCxlQUFlLElBQ2Y7SUFDSixDQUFDO0NBQ0Y7QUE5S0Qsb0RBOEtDIn0=
@@ -0,0 +1,48 @@
1
+ import { EntitlementQuery, EntitlementType } from '@stigg/cache-core';
2
+ import { EntitlementFallback, EntitlementFallbackConfig } from '../types/entitlementFallback';
3
+ import { BooleanEntitlementOptions, CreditEntitlementOptions, EntitlementReadOptions, EnumEntitlementOptions, MeteredEntitlementOptions, NumericEntitlementOptions } from '../types/entitlementOptions';
4
+ import { EntitlementEvaluation } from '../types/entitlementEvaluation';
5
+ /**
6
+ * Owns what a caller falls back to when an entitlement cannot be answered from cached state: the
7
+ * configured defaults, the precedence between those and a per-call fallback, and the shape of the
8
+ * answer itself. Holds no cache or transport, so a host can build one before — or without — the
9
+ * service that reads entitlements.
10
+ */
11
+ export declare class EntitlementFallbacks {
12
+ private readonly config;
13
+ constructor(config?: EntitlementFallbackConfig);
14
+ /**
15
+ * The answer when cached state was never reached at all — the host is offline, or the read failed.
16
+ * Distinct from a miss, which *is* a real answer ("not in the subscription") and therefore ignores
17
+ * the fallback. The caller picks how to present it, as only it knows which kind it is serving.
18
+ */
19
+ forBoolean(featureId: string, options?: BooleanEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
20
+ forNumeric(featureId: string, options?: NumericEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
21
+ forMetered(featureId: string, options?: MeteredEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
22
+ forEnum(featureId: string, options?: EnumEntitlementOptions): EntitlementEvaluation<EntitlementType.Feature>;
23
+ forCredit(currencyId: string, options?: CreditEntitlementOptions): EntitlementEvaluation<EntitlementType.Credit>;
24
+ /**
25
+ * The untyped equivalent of the five above, for reads that state no kind up front
26
+ * (`getEntitlement`, `getEntitlementV2`) and must infer it from the caller's options.
27
+ *
28
+ * Unlike the typed reads, this family drops the denial reason once the fallback grants access —
29
+ * a granted answer with a reason attached is what it has never returned.
30
+ */
31
+ forQuery<TType extends EntitlementType>(query: EntitlementQuery<TType>, options?: EntitlementReadOptions): EntitlementEvaluation<TType>;
32
+ /**
33
+ * Every configured fallback, as the answers a bulk read gives when it cannot be served. Goes
34
+ * through the same path as a single read, so one configured entry produces the same shape whether
35
+ * the caller asks for it alone or asks for everything.
36
+ */
37
+ forAllConfigured(): EntitlementEvaluation[];
38
+ /**
39
+ * A per-call fallback replaces the configured one wholesale. The configured maps are keyed by id
40
+ * and cannot know which kind the caller asked for, so an entry is taken on trust — the same
41
+ * looseness every caller had when it did this lookup itself.
42
+ */
43
+ resolve<TFallback extends EntitlementFallback>(query: EntitlementQuery, fallback?: Partial<TFallback>): Partial<TFallback> | undefined;
44
+ /** A granted answer carries no denial reason, which `unavailable` cannot know when it sets one. */
45
+ private clearDeniedReasonOnGrant;
46
+ private unavailable;
47
+ private feature;
48
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitlementFallbacks = void 0;
4
+ const cache_core_1 = require("@stigg/cache-core");
5
+ const entitlementOptions_1 = require("../types/entitlementOptions");
6
+ const resolveEntitlement_1 = require("./resolveEntitlement");
7
+ /**
8
+ * Owns what a caller falls back to when an entitlement cannot be answered from cached state: the
9
+ * configured defaults, the precedence between those and a per-call fallback, and the shape of the
10
+ * answer itself. Holds no cache or transport, so a host can build one before — or without — the
11
+ * service that reads entitlements.
12
+ */
13
+ class EntitlementFallbacks {
14
+ constructor(config = {}) {
15
+ this.config = config;
16
+ }
17
+ /**
18
+ * The answer when cached state was never reached at all — the host is offline, or the read failed.
19
+ * Distinct from a miss, which *is* a real answer ("not in the subscription") and therefore ignores
20
+ * the fallback. The caller picks how to present it, as only it knows which kind it is serving.
21
+ */
22
+ forBoolean(featureId, options) {
23
+ return this.unavailable(this.feature(featureId), options === null || options === void 0 ? void 0 : options.fallback, { requestedKind: 'BOOLEAN' });
24
+ }
25
+ forNumeric(featureId, options) {
26
+ return this.unavailable(this.feature(featureId), options === null || options === void 0 ? void 0 : options.fallback, { requestedKind: 'NUMERIC' });
27
+ }
28
+ forMetered(featureId, options) {
29
+ return this.unavailable(this.feature(featureId), options === null || options === void 0 ? void 0 : options.fallback, {
30
+ requestedKind: 'METERED',
31
+ requestedUsage: options === null || options === void 0 ? void 0 : options.requestedUsage,
32
+ });
33
+ }
34
+ forEnum(featureId, options) {
35
+ return this.unavailable(this.feature(featureId), options === null || options === void 0 ? void 0 : options.fallback, {
36
+ requestedKind: 'ENUM',
37
+ requestedValues: options === null || options === void 0 ? void 0 : options.requestedValues,
38
+ });
39
+ }
40
+ forCredit(currencyId, options) {
41
+ return this.unavailable({ type: cache_core_1.EntitlementType.Credit, id: currencyId }, options === null || options === void 0 ? void 0 : options.fallback);
42
+ }
43
+ /**
44
+ * The untyped equivalent of the five above, for reads that state no kind up front
45
+ * (`getEntitlement`, `getEntitlementV2`) and must infer it from the caller's options.
46
+ *
47
+ * Unlike the typed reads, this family drops the denial reason once the fallback grants access —
48
+ * a granted answer with a reason attached is what it has never returned.
49
+ */
50
+ forQuery(query, options) {
51
+ const evaluation = query.type === cache_core_1.EntitlementType.Credit
52
+ ? this.unavailable(query, options === null || options === void 0 ? void 0 : options.fallback)
53
+ : this.unavailable(query, options === null || options === void 0 ? void 0 : options.fallback, {
54
+ requestedKind: (0, entitlementOptions_1.featureKindFromOptions)(options),
55
+ requestedUsage: options === null || options === void 0 ? void 0 : options.requestedUsage,
56
+ requestedValues: options === null || options === void 0 ? void 0 : options.requestedValues,
57
+ });
58
+ return this.clearDeniedReasonOnGrant(evaluation);
59
+ }
60
+ /**
61
+ * Every configured fallback, as the answers a bulk read gives when it cannot be served. Goes
62
+ * through the same path as a single read, so one configured entry produces the same shape whether
63
+ * the caller asks for it alone or asks for everything.
64
+ */
65
+ forAllConfigured() {
66
+ var _a, _b;
67
+ const features = Object.entries((_a = this.config.entitlementsFallback) !== null && _a !== void 0 ? _a : {}).map(([featureId, fallback]) => this.clearDeniedReasonOnGrant(this.unavailable(this.feature(featureId), fallback, { requestedKind: (0, entitlementOptions_1.featureKindFromFallback)(fallback) })));
68
+ const credits = Object.keys((_b = this.config.creditEntitlementsFallback) !== null && _b !== void 0 ? _b : {}).map((currencyId) => this.clearDeniedReasonOnGrant(this.forCredit(currencyId)));
69
+ return [...features, ...credits];
70
+ }
71
+ /**
72
+ * A per-call fallback replaces the configured one wholesale. The configured maps are keyed by id
73
+ * and cannot know which kind the caller asked for, so an entry is taken on trust — the same
74
+ * looseness every caller had when it did this lookup itself.
75
+ */
76
+ resolve(query, fallback) {
77
+ var _a, _b;
78
+ if (fallback) {
79
+ return fallback;
80
+ }
81
+ const configured = query.type === cache_core_1.EntitlementType.Credit
82
+ ? (_a = this.config.creditEntitlementsFallback) === null || _a === void 0 ? void 0 : _a[query.id]
83
+ : (_b = this.config.entitlementsFallback) === null || _b === void 0 ? void 0 : _b[query.id];
84
+ return configured;
85
+ }
86
+ /** A granted answer carries no denial reason, which `unavailable` cannot know when it sets one. */
87
+ clearDeniedReasonOnGrant(evaluation) {
88
+ return evaluation.hasAccess ? Object.assign(Object.assign({}, evaluation), { accessDeniedReason: undefined }) : evaluation;
89
+ }
90
+ unavailable(query, fallback, requested = {}) {
91
+ var _a;
92
+ const resolved = this.resolve(query, fallback);
93
+ return Object.assign({ hasAccess: (_a = resolved === null || resolved === void 0 ? void 0 : resolved.hasAccess) !== null && _a !== void 0 ? _a : false, accessDeniedReason: cache_core_1.AccessDeniedReason.Unknown, query, resolved: (0, resolveEntitlement_1.resolveCacheMiss)(query, resolved), isFallback: true }, requested);
94
+ }
95
+ feature(featureId) {
96
+ return { type: cache_core_1.EntitlementType.Feature, id: featureId };
97
+ }
98
+ }
99
+ exports.EntitlementFallbacks = EntitlementFallbacks;
100
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXRsZW1lbnRGYWxsYmFja3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvZW50aXRsZW1lbnRGYWxsYmFja3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsa0RBQTBGO0FBRTFGLG9FQVNxQztBQUVyQyw2REFBd0Q7QUFFeEQ7Ozs7O0dBS0c7QUFDSCxNQUFhLG9CQUFvQjtJQUMvQixZQUE2QixTQUFvQyxFQUFFO1FBQXRDLFdBQU0sR0FBTixNQUFNLENBQWdDO0lBQUcsQ0FBQztJQUV2RTs7OztPQUlHO0lBQ0gsVUFBVSxDQUFDLFNBQWlCLEVBQUUsT0FBbUM7UUFDL0QsT0FBTyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLFFBQVEsRUFBRSxFQUFFLGFBQWEsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO0lBQ3BHLENBQUM7SUFFRCxVQUFVLENBQUMsU0FBaUIsRUFBRSxPQUFtQztRQUMvRCxPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsRUFBRSxPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsUUFBUSxFQUFFLEVBQUUsYUFBYSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUM7SUFDcEcsQ0FBQztJQUVELFVBQVUsQ0FBQyxTQUFpQixFQUFFLE9BQW1DO1FBQy9ELE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxRQUFRLEVBQUU7WUFDbEUsYUFBYSxFQUFFLFNBQVM7WUFDeEIsY0FBYyxFQUFFLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxjQUFjO1NBQ3hDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFF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
@@ -0,0 +1,18 @@
1
+ import { CachedEntitlement, EntitlementQuery, EntitlementType } from '@stigg/cache-core';
2
+ import { CreditEntitlementFallback, EntitlementFallback, FeatureEntitlementFallback } from '../types/entitlementFallback';
3
+ import { FeatureEntitlement, ResolvedByType, ResolvedCreditEntitlement, ResolvedFeatureEntitlement } from '../types/entitlementEvaluation';
4
+ export declare function resolveFeature(entitlement: FeatureEntitlement, fallback?: FeatureEntitlementFallback): ResolvedFeatureEntitlement;
5
+ export declare function resolveCredit(currencyId: string, entitlement: CachedEntitlement<EntitlementType.Credit> | null, fallback?: Partial<CreditEntitlementFallback>): ResolvedCreditEntitlement;
6
+ /**
7
+ * Answers from a cached entitlement, which carries both its own kind and its own currency — so the
8
+ * query that found it adds nothing here.
9
+ *
10
+ * Declared as an overload because the return type is conditional on `TType`, and a type guard
11
+ * narrows the value rather than the type parameter.
12
+ */
13
+ export declare function resolveEntitlement<TType extends EntitlementType>(entitlement: CachedEntitlement<TType>): ResolvedByType<TType>;
14
+ /**
15
+ * Answers a cache miss, where the query is the only thing left that names the kind — and, for
16
+ * credit, the currency the caller asked about.
17
+ */
18
+ export declare function resolveCacheMiss<TType extends EntitlementType>(query: EntitlementQuery<TType>, fallback?: Partial<EntitlementFallback>): ResolvedByType<TType>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveFeature = resolveFeature;
4
+ exports.resolveCredit = resolveCredit;
5
+ exports.resolveEntitlement = resolveEntitlement;
6
+ exports.resolveCacheMiss = resolveCacheMiss;
7
+ const cache_core_1 = require("@stigg/cache-core");
8
+ function resolveFeature(entitlement, fallback) {
9
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
10
+ if (!entitlement) {
11
+ return {
12
+ usageLimit: (_a = fallback === null || fallback === void 0 ? void 0 : fallback.value) !== null && _a !== void 0 ? _a : fallback === null || fallback === void 0 ? void 0 : fallback.usageLimit,
13
+ enumValues: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.enumValues) !== null && _b !== void 0 ? _b : [],
14
+ isUnlimited: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.isUnlimited) !== null && _c !== void 0 ? _c : false,
15
+ hasSoftLimit: false,
16
+ currentUsage: 0,
17
+ };
18
+ }
19
+ const { calculatedEntitlement: calculated, usageData } = entitlement;
20
+ return {
21
+ feature: calculated.feature,
22
+ usageLimit: (_d = calculated.usageLimit) !== null && _d !== void 0 ? _d : undefined,
23
+ enumValues: (_e = calculated.enumValues) !== null && _e !== void 0 ? _e : [],
24
+ isUnlimited: calculated.hasUnlimitedUsage,
25
+ hasSoftLimit: !!calculated.hasSoftLimit,
26
+ currentUsage: usageData.currentUsage,
27
+ resetPeriod: (_f = calculated.resetPeriod) !== null && _f !== void 0 ? _f : undefined,
28
+ usagePeriodAnchor: (_g = calculated.usagePeriodAnchor) !== null && _g !== void 0 ? _g : undefined,
29
+ usagePeriodStart: (_h = usageData.usagePeriodStart) !== null && _h !== void 0 ? _h : undefined,
30
+ usagePeriodEnd: (_j = usageData.usagePeriodEnd) !== null && _j !== void 0 ? _j : undefined,
31
+ };
32
+ }
33
+ function resolveCredit(currencyId, entitlement, fallback) {
34
+ var _a, _b, _c, _d;
35
+ const usageLimit = (_a = (entitlement ? entitlement.calculatedEntitlement.usageLimit : fallback === null || fallback === void 0 ? void 0 : fallback.usageLimit)) !== null && _a !== void 0 ? _a : 0;
36
+ const currentUsage = (_b = (entitlement ? entitlement.usageData.currentUsage : fallback === null || fallback === void 0 ? void 0 : fallback.currentUsage)) !== null && _b !== void 0 ? _b : 0;
37
+ return {
38
+ currency: entitlement ? entitlement.calculatedEntitlement.currency : { currencyId },
39
+ usageLimit,
40
+ currentUsage,
41
+ remaining: Math.max(usageLimit - currentUsage, 0),
42
+ hasSoftLimit: entitlement ? ((_c = entitlement.calculatedEntitlement.hasSoftLimit) !== null && _c !== void 0 ? _c : false) : false,
43
+ usagePeriodEnd: (_d = (entitlement ? entitlement.usageData.usagePeriodEnd : undefined)) !== null && _d !== void 0 ? _d : undefined,
44
+ };
45
+ }
46
+ function resolveEntitlement(entitlement) {
47
+ if ((0, cache_core_1.isCreditEntitlement)(entitlement)) {
48
+ return resolveCredit(entitlement.calculatedEntitlement.currency.currencyId, entitlement);
49
+ }
50
+ if ((0, cache_core_1.isFeatureEntitlement)(entitlement)) {
51
+ return resolveFeature(entitlement);
52
+ }
53
+ // Unreachable while the two kinds are the only ones, but a guard cannot prove that: negating one
54
+ // leaves `CachedEntitlement<T>` unnarrowed, since it is a generic class rather than a union.
55
+ throw new Error(`Unknown entitlement type: ${entitlement.calculatedEntitlement.type}`);
56
+ }
57
+ function resolveCacheMiss(query, fallback) {
58
+ return query.type === cache_core_1.EntitlementType.Credit
59
+ ? resolveCredit(query.id, null, fallback)
60
+ : resolveFeature(null, fallback);
61
+ }
62
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZUVudGl0bGVtZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL3Jlc29sdmVFbnRpdGxlbWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQW1CQSx3Q0EyQkM7QUFFRCxzQ0FlQztBQVlELGdEQVlDO0FBVUQsNENBT0M7QUF4R0Qsa0RBTTJCO0FBYTNCLFNBQWdCLGNBQWMsQ0FDNUIsV0FBK0IsRUFDL0IsUUFBcUM7O0lBRXJDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNqQixPQUFPO1lBQ0wsVUFBVSxFQUFFLE1BQUEsUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLEtBQUssbUNBQUksUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLFVBQVU7WUFDbkQsVUFBVSxFQUFFLE1BQUEsUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLFVBQVUsbUNBQUksRUFBRTtZQUN0QyxXQUFXLEVBQUUsTUFBQSxRQUFRLGFBQVIsUUFBUSx1QkFBUixRQUFRLENBQUUsV0FBVyxtQ0FBSSxLQUFLO1lBQzNDLFlBQVksRUFBRSxLQUFLO1lBQ25CLFlBQVksRUFBRSxDQUFDO1NBQ2hCLENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxFQUFFLHFCQUFxQixFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsR0FBRyxXQUFXLENBQUM7SUFDckUsT0FBTztRQUNMLE9BQU8sRUFBRSxVQUFVLENBQUMsT0FBTztRQUMzQixVQUFVLEVBQUUsTUFBQSxVQUFVLENBQUMsVUFBVSxtQ0FBSSxTQUFTO1FBQzlDLFVBQVUsRUFBRSxNQUFBLFVBQVUsQ0FBQyxVQUFVLG1DQUFJLEVBQUU7UUFDdkMsV0FBVyxFQUFFLFVBQVUsQ0FBQyxpQkFBaUI7UUFDekMsWUFBWSxFQUFFLENBQUMsQ0FBQyxVQUFVLENBQUMsWUFBWTtRQUN2QyxZQUFZLEVBQUUsU0FBUyxDQUFDLFlBQVk7UUFDcEMsV0FBVyxFQUFFLE1B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
@@ -0,0 +1,61 @@
1
+ import { AccessDeniedReason, CachedEntitlement, CalculatedCreditEntitlement, EntitlementFeature, EntitlementQuery, EntitlementResetPeriod, EntitlementType } from '@stigg/cache-core';
2
+ /**
3
+ * The answer's values, already merged from whichever source won — the cached entitlement, or the
4
+ * caller's fallback. Deliberately *not* a `CachedEntitlement`: that type means "real, fully-mapped
5
+ * cache state" and requires a `feature` with a `featureType`, which is precisely what a miss does not
6
+ * know. Resolving here is what lets every transport translate without a branch.
7
+ */
8
+ export interface ResolvedFeatureEntitlement {
9
+ /** Absent when nothing was found — there is no feature to describe. */
10
+ readonly feature?: EntitlementFeature;
11
+ readonly usageLimit?: number;
12
+ readonly enumValues: string[];
13
+ readonly isUnlimited: boolean;
14
+ readonly hasSoftLimit: boolean;
15
+ readonly currentUsage: number;
16
+ readonly resetPeriod?: EntitlementResetPeriod;
17
+ readonly usagePeriodAnchor?: number;
18
+ readonly usagePeriodStart?: number;
19
+ readonly usagePeriodEnd?: number;
20
+ }
21
+ export interface ResolvedCreditEntitlement {
22
+ readonly currency: CalculatedCreditEntitlement['currency'];
23
+ readonly usageLimit: number;
24
+ readonly currentUsage: number;
25
+ readonly remaining: number;
26
+ readonly hasSoftLimit: boolean;
27
+ readonly usagePeriodEnd?: number;
28
+ }
29
+ export type ResolvedByType<TType extends EntitlementType> = TType extends EntitlementType.Feature ? ResolvedFeatureEntitlement : TType extends EntitlementType.Credit ? ResolvedCreditEntitlement : ResolvedFeatureEntitlement | ResolvedCreditEntitlement;
30
+ /**
31
+ * The outcome of evaluating cached state for one entitlement, in cache vocabulary.
32
+ *
33
+ * This is the shared seam: the access logic runs once in `EntitlementEvaluator`, and each transport
34
+ * translates the result into its own representation — `ModelMapper` into the SDK models today, the
35
+ * REST DTOs later. Keeping it free of API types is what lets both live over the same evaluation.
36
+ */
37
+ export interface EntitlementEvaluation<TType extends EntitlementType = EntitlementType> {
38
+ readonly hasAccess: boolean;
39
+ readonly accessDeniedReason?: AccessDeniedReason;
40
+ /** What was asked for. */
41
+ readonly query: EntitlementQuery<TType>;
42
+ /** The answer's values, whichever source they came from. */
43
+ readonly resolved: ResolvedByType<TType>;
44
+ /** The cached entitlement the decision was made from; absent on a miss or a kind mismatch. */
45
+ readonly entitlement?: CachedEntitlement<TType>;
46
+ /** Whether the answer is the caller's fallback rather than resolved state. */
47
+ readonly isFallback: boolean;
48
+ /**
49
+ * The kind the caller asked for. Authoritative only when there is no `resolved.feature` to read a
50
+ * kind from — a miss or an unavailable read knows nothing about the feature, yet still has to be
51
+ * rendered as one specific kind. On a hit the feature's own type wins.
52
+ */
53
+ readonly requestedKind?: FeatureEntitlementKind;
54
+ readonly requestedUsage?: number;
55
+ readonly requestedValues?: string[];
56
+ }
57
+ /** The four shapes a feature entitlement can be answered as. */
58
+ export type FeatureEntitlementKind = 'BOOLEAN' | 'NUMERIC' | 'METERED' | 'ENUM';
59
+ export type FeatureQuery = EntitlementQuery<EntitlementType.Feature>;
60
+ export type FeatureEntitlement = CachedEntitlement<EntitlementType.Feature> | null;
61
+ export type FeaturePredicate = (feature?: EntitlementFeature) => boolean;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXRsZW1lbnRFdmFsdWF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3R5cGVzL2VudGl0bGVtZW50RXZhbHVhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1,52 @@
1
+ /**
2
+ * What a caller wants returned when an entitlement can't be resolved.
3
+ *
4
+ * Kept out of `models.ts` deliberately: these carry no transport vocabulary, only primitives, so the
5
+ * evaluator can accept one without reaching for the GraphQL-flavoured models and every transport can
6
+ * expand it into its own representation.
7
+ */
8
+ export type BooleanEntitlementFallback = {
9
+ hasAccess: boolean;
10
+ };
11
+ export type NumericEntitlementFallback = {
12
+ hasAccess: boolean;
13
+ value?: number;
14
+ isUnlimited?: boolean;
15
+ };
16
+ export type MeteredEntitlementFallback = {
17
+ hasAccess: boolean;
18
+ usageLimit?: number;
19
+ isUnlimited?: boolean;
20
+ };
21
+ export type EnumEntitlementFallback = {
22
+ hasAccess: boolean;
23
+ enumValues?: string[];
24
+ };
25
+ export type CreditEntitlementFallback = {
26
+ hasAccess: boolean;
27
+ usageLimit?: number;
28
+ currentUsage?: number;
29
+ };
30
+ export type EntitlementFallback = BooleanEntitlementFallback | NumericEntitlementFallback | MeteredEntitlementFallback | EnumEntitlementFallback | CreditEntitlementFallback;
31
+ /**
32
+ * Any feature fallback, read structurally rather than per kind. The resolved answer carries a single
33
+ * limit, so a caller naming it `value` (numeric) and one naming it `usageLimit` (metered) both land
34
+ * on it — and reading with `??` rather than `in` keeps a present-but-undefined key from shadowing
35
+ * the other.
36
+ */
37
+ export type FeatureEntitlementFallback = Partial<NumericEntitlementFallback & MeteredEntitlementFallback & EnumEntitlementFallback>;
38
+ /** Feature fallbacks keyed by feature id. Credit is keyed separately, by currency id. */
39
+ export type EntitlementsFallback = {
40
+ [featureId: string]: BooleanEntitlementFallback | NumericEntitlementFallback | MeteredEntitlementFallback | EnumEntitlementFallback;
41
+ };
42
+ export type CreditEntitlementsFallback = {
43
+ [currencyId: string]: CreditEntitlementFallback;
44
+ };
45
+ /**
46
+ * Fallbacks the host was configured with once, rather than passed per call. A fallback passed on the
47
+ * call itself replaces the configured one wholesale — the two are never merged.
48
+ */
49
+ export type EntitlementFallbackConfig = {
50
+ entitlementsFallback?: EntitlementsFallback;
51
+ creditEntitlementsFallback?: CreditEntitlementsFallback;
52
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * What a caller wants returned when an entitlement can't be resolved.
4
+ *
5
+ * Kept out of `models.ts` deliberately: these carry no transport vocabulary, only primitives, so the
6
+ * evaluator can accept one without reaching for the GraphQL-flavoured models and every transport can
7
+ * expand it into its own representation.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXRsZW1lbnRGYWxsYmFjay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlcy9lbnRpdGxlbWVudEZhbGxiYWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7O0dBTUcifQ==
@@ -0,0 +1,39 @@
1
+ import { BooleanEntitlementFallback, CreditEntitlementFallback, EnumEntitlementFallback, MeteredEntitlementFallback, FeatureEntitlementFallback, NumericEntitlementFallback } from './entitlementFallback';
2
+ import { FeatureEntitlementKind } from './entitlementEvaluation';
3
+ /**
4
+ * Per-call options for a read, in cache vocabulary. `sdk-core` widens these with the SDKs'
5
+ * backward-compatible fields before publishing them under the same names.
6
+ */
7
+ export type BooleanEntitlementOptions = {
8
+ fallback?: BooleanEntitlementFallback;
9
+ };
10
+ export type NumericEntitlementOptions = {
11
+ fallback?: NumericEntitlementFallback;
12
+ };
13
+ export type MeteredEntitlementOptions = {
14
+ fallback?: MeteredEntitlementFallback;
15
+ requestedUsage?: number;
16
+ };
17
+ export type EnumEntitlementOptions = {
18
+ fallback?: EnumEntitlementFallback;
19
+ requestedValues?: string[];
20
+ };
21
+ export type CreditEntitlementOptions = {
22
+ fallback?: CreditEntitlementFallback;
23
+ };
24
+ /** Whatever any of the four feature reads may pass, for the paths they share. */
25
+ export type FeatureEntitlementOptions = BooleanEntitlementOptions & NumericEntitlementOptions & MeteredEntitlementOptions & EnumEntitlementOptions;
26
+ export type EntitlementReadOptions = FeatureEntitlementOptions & CreditEntitlementOptions;
27
+ /**
28
+ * Which kind an untyped read (`getEntitlement`, `getEntitlementV2`) is asking for. There is no
29
+ * feature to consult when the read cannot be answered, so the caller's own options are the only
30
+ * evidence — the same discrimination the SDKs applied before, kept in one place so every transport
31
+ * answers the same way.
32
+ */
33
+ export declare function featureKindFromOptions(options?: FeatureEntitlementOptions): FeatureEntitlementKind;
34
+ /**
35
+ * Which kind a *configured* fallback describes. A different question from the one above: there is no
36
+ * caller stating what they want, only an entry someone wrote in the config, so its own fields are
37
+ * the evidence. Used where entries are answered without anyone having asked for one by kind.
38
+ */
39
+ export declare function featureKindFromFallback(fallback?: FeatureEntitlementFallback): FeatureEntitlementKind;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.featureKindFromOptions = featureKindFromOptions;
4
+ exports.featureKindFromFallback = featureKindFromFallback;
5
+ /**
6
+ * Which kind an untyped read (`getEntitlement`, `getEntitlementV2`) is asking for. There is no
7
+ * feature to consult when the read cannot be answered, so the caller's own options are the only
8
+ * evidence — the same discrimination the SDKs applied before, kept in one place so every transport
9
+ * answers the same way.
10
+ */
11
+ function featureKindFromOptions(options) {
12
+ if (options == null) {
13
+ return 'BOOLEAN';
14
+ }
15
+ if ('requestedUsage' in options) {
16
+ return 'METERED';
17
+ }
18
+ if ('requestedValues' in options) {
19
+ return 'ENUM';
20
+ }
21
+ return options.fallback != null && 'value' in options.fallback ? 'NUMERIC' : 'BOOLEAN';
22
+ }
23
+ /**
24
+ * Which kind a *configured* fallback describes. A different question from the one above: there is no
25
+ * caller stating what they want, only an entry someone wrote in the config, so its own fields are
26
+ * the evidence. Used where entries are answered without anyone having asked for one by kind.
27
+ */
28
+ function featureKindFromFallback(fallback) {
29
+ if (fallback == null) {
30
+ return 'BOOLEAN';
31
+ }
32
+ if ('enumValues' in fallback) {
33
+ return 'ENUM';
34
+ }
35
+ if ('usageLimit' in fallback) {
36
+ return 'METERED';
37
+ }
38
+ return 'value' in fallback ? 'NUMERIC' : 'BOOLEAN';
39
+ }
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXRsZW1lbnRPcHRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3R5cGVzL2VudGl0bGVtZW50T3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWtEQSx3REFXQztBQU9ELDBEQVdDO0FBbkNEOzs7OztHQUtHO0FBQ0gsU0FBZ0Isc0JBQXNCLENBQUMsT0FBbUM7SUFDeEUsSUFBSSxPQUFPLElBQUksSUFBSSxFQUFFLENBQUM7UUFDcEIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUNELElBQUksZ0JBQWdCLElBQUksT0FBTyxFQUFFLENBQUM7UUFDaEMsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUNELElBQUksaUJBQWlCLElBQUksT0FBTyxFQUFFLENBQUM7UUFDakMsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDLFFBQVEsSUFBSSxJQUFJLElBQUksT0FBTyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQ3pGLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsdUJBQXVCLENBQUMsUUFBcUM7SUFDM0UsSUFBSSxRQUFRLElBQUksSUFBSSxFQUFFLENBQUM7UUFDckIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUNELElBQUksWUFBWSxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQzdCLE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFDRCxJQUFJLFlBQVksSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUM3QixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBQ0QsT0FBTyxPQUFPLElBQUksUUFBUSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUNyRCxDQUFDIn0=
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@stigg/entitlements-runtime",
3
+ "version": "2.0.0",
4
+ "description": "Transport-agnostic entitlement decision logic for Stigg SDKs",
5
+ "main": "dist/index.js",
6
+ "typings": "dist/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "main": "dist/index.js",
10
+ "typings": "dist/index.d.ts"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/stiggio/stigg-sdk.git",
15
+ "directory": "packages/entitlements-runtime"
16
+ },
17
+ "license": "MIT",
18
+ "scripts": {
19
+ "build": "yarn run -T tsc -p tsconfig.build.json",
20
+ "lint": "yarn run -T eslint \"src/**/*.ts\"",
21
+ "test": "yarn run -T jest --forceExit"
22
+ },
23
+ "files": [
24
+ "dist/**/*"
25
+ ],
26
+ "dependencies": {
27
+ "lodash": "^4.18.1"
28
+ },
29
+ "peerDependencies": {
30
+ "@stigg/cache-core": "1.27.0"
31
+ }
32
+ }