@configdirector/config-evaluator-internal 0.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 +29 -0
- package/dist/configdirector-config-evaluator.cjs +1 -0
- package/dist/configdirector-config-evaluator.d.cts +236 -0
- package/dist/configdirector-config-evaluator.d.cts.map +1 -0
- package/dist/configdirector-config-evaluator.d.mts +236 -0
- package/dist/configdirector-config-evaluator.d.mts.map +1 -0
- package/dist/configdirector-config-evaluator.mjs +2 -0
- package/dist/configdirector-config-evaluator.mjs.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @configdirector/config-evaluator-internal
|
|
2
|
+
|
|
3
|
+
**ConfigDirector internal package.** It exists so that the ConfigDirector dashboard can run the
|
|
4
|
+
same targeting rules evaluator the ConfigDirector server SDKs ship, from the same source. It is
|
|
5
|
+
not intended for use outside ConfigDirector, carries no compatibility promise, and its API can
|
|
6
|
+
change in any release. Applications should use one of the published SDKs instead:
|
|
7
|
+
[@configdirector/server-sdk](https://www.npmjs.com/package/@configdirector/server-sdk) for Node,
|
|
8
|
+
or a client SDK for the browser and mobile.
|
|
9
|
+
|
|
10
|
+
The server SDKs in this repository bundle the evaluator from source; nothing in this repository
|
|
11
|
+
depends on the published package. The evaluator's behaviour is pinned by the targeting rules
|
|
12
|
+
contract, which every ConfigDirector implementation is checked against.
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
|
|
16
|
+
- `ConfigEvaluator`: walks a config's targeting rules for a context and returns the served value.
|
|
17
|
+
`explain` returns the value with the trace of how it was reached: each rule's outcome, each
|
|
18
|
+
checked condition with what it resolved to, and the rollout share a context landed in.
|
|
19
|
+
`evaluate` is `explain` with only the value kept, so the two cannot disagree.
|
|
20
|
+
- `ConditionEvaluator`: decides whether one condition holds for a context; `explain` also says
|
|
21
|
+
what the condition was compared against.
|
|
22
|
+
- `assignPercentage`: the bucketing hash behind percentage rollouts.
|
|
23
|
+
- The rule, condition, config and context types the evaluator works on.
|
|
24
|
+
- `EVALUATOR_VERSION`: the published version, for showing which evaluator produced a result.
|
|
25
|
+
|
|
26
|
+
## Versioning
|
|
27
|
+
|
|
28
|
+
Versioned independently of the SDKs with changesets, starting at 0.1.0. Released with the
|
|
29
|
+
"Release @configdirector/config-evaluator-internal" workflow.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`@jsonjoy.com/json-pointer`),l=require(`luxon`),u=require(`semver`);u=s(u);let d=require(`rapidhash-js`);const f=(e,t,n)=>{let r=n[0];switch(t.toLowerCase()){case`=`:case`equals`:return r!==void 0&&e===r;case`!=`:case`does not equal`:return r!==void 0&&e!==r;case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.some(t=>e.startsWith(t));case`does not start with any of`:return!n.some(t=>e.startsWith(t));case`ends with any of`:return n.some(t=>e.endsWith(t));case`does not end with any of`:return!n.some(t=>e.endsWith(t));default:return!1}},p=(e,t,n)=>{let r=t.toLowerCase(),i=m(e);if(i===void 0)return r===`!=`||r===`does not equal`;let a=n[0];if(a===void 0)return!1;let o=m(a);if(o===void 0)return!1;switch(r){case`=`:case`equals`:return i===o;case`!=`:case`does not equal`:return i!==o;case`<`:return i<o;case`<=`:return i<=o;case`>`:return i>o;case`>=`:return i>=o;default:return!1}},m=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`||e.length===0||!h(e))return;let t=Number(e);return Number.isFinite(t)?t:void 0},h=e=>{for(let t of e)if(!(t>=`0`&&t<=`9`)&&!`+-.eE`.includes(t))return!1;return!0},g=(e,t,n)=>{let r=n[0];if(r===void 0)return!1;let i=_(e),a=_(r);if(i===void 0||a===void 0)return!1;switch(t){case`is after`:return i>a;case`is before`:return i<a;default:return!1}},_=e=>{let t=l.DateTime.fromISO(e,{zone:`utc`});return t.isValid?t.toMillis():void 0},v=(e,t,n)=>{let r=t.toLowerCase();if(e.trim().length===0)return r===`is not one of`;let i=u.coerce(e),a=n.map(e=>u.coerce(e)),o=a[0]??null;switch(r){case`=`:return y(i,o);case`<`:return b(i,o,u.lt);case`<=`:return b(i,o,u.lte);case`>`:return b(i,o,u.gt);case`>=`:return b(i,o,u.gte);case`is one of`:return a.some(e=>y(i,e));case`is not one of`:return!a.some(e=>y(i,e));default:return!1}},y=(e,t)=>e!==null&&t!==null&&u.eq(e,t),b=(e,t,n)=>e!==null&&t!==null&&n(e,t),x=Symbol(`absent`),S=Symbol(`unknown-attribute`),C=e=>typeof e==`string`||typeof e==`number`||typeof e==`boolean`,w=e=>e===x||e==null?``:C(e)?T(e):``,T=e=>typeof e==`string`?e:String(e),E=e=>e===x?void 0:e,D=e=>e===S?`unknown-attribute`:e===x?`absent`:C(e)?`scalar`:Array.isArray(e)?`array`:`object`,O=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r===`does not contain any of`;let i=e.filter(C).map(T);switch(r){case`contains any of`:return i.some(e=>n.includes(e));case`does not contain any of`:return!i.some(e=>n.includes(e));default:return!1}};var k=class{evaluate(e,t){return this.explain(e,t).matched}explain(e,t){let n=this.resolve(e,t),r=D(n),i=n===x||n===S?void 0:n;return n===S?{matched:!1,resolvedValue:i,resolvedType:r}:{matched:this.compare(e,n),resolvedValue:i,resolvedType:r}}compare(e,t){let n=e.targetValues??[];switch(e.targetType){case`text`:return f(w(t),e.operator,n);case`number`:return p(E(t),e.operator,n);case`datetime`:return g(w(t),e.operator,n);case`semver`:return v(w(t),e.operator,n);case`array`:return O(E(t),e.operator,n)}return!1}resolve(e,t){switch(e.attribute){case`identifier`:return A(t?.context?.id);case`name`:return A(t?.context?.name);case`appName`:return A(t?.metadata?.appName);case`appVersion`:return A(t?.metadata?.appVersion);case`traits`:return e.trait?A(this.findTraitValue(t?.context?.traits,e.trait)):x;default:return S}}findTraitValue(e,t){try{return(0,c.findByPointer)(t,e).val}catch{return}}};const A=e=>e??x,j=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number((0,d.rapidhash)(t,{seed:15426857351n})%1000n)/10};var M=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new k,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.explain(e,t).value}}explain(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1)),r=[],i;for(let a of n){if(i){r.push({ruleId:a.id,outcome:`not-evaluated`,conditions:[],bucket:void 0});continue}let n=this.explainRule(a,e,t);r.push(n.explanation),n.value!==void 0&&(i={ruleId:a.id,value:n.value})}return{value:i?.value??e.target?.defaultValue,servedBy:i?{kind:`rule`,ruleId:i.ruleId}:{kind:`default`},rules:r}}explainRule(e,t,n){let r={ruleId:e.id,outcome:`not-matched`,conditions:[],bucket:void 0};try{let i;return e.type==`percentage`?i=this.explainPercentage(e.percentages??[],t,n,r):e.type==`conditional`&&(i=this.explainConditionalRule(e,t,n,r)),i!==void 0&&(r.outcome=`matched`),{explanation:r,value:i}}catch(n){return this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id}),{explanation:{ruleId:e.id,outcome:`errored`,conditions:[],bucket:void 0},value:void 0}}}explainPercentage(e,t,n,r){let i=n?.context?.id,a=i??crypto.randomUUID(),o=j({configId:t.id,contextIdentifier:a}),s=[],c=0,l;for(let t of e){let e=c+t.percentage;s.push({percentageId:t.id,from:c,to:e,value:t.value?.toString()}),l===void 0&&o<e&&(l=t),c=e}return r.bucket={identifier:a,identifierWasGenerated:i==null,assignedPercentage:o,shares:s,selectedPercentageId:l?.id},l?.value==null?void 0:l.value.toString()}explainConditionalRule(e,t,n,r){let i=!1;for(let t of e.conditions??[]){if(i){r.conditions.push({conditionId:t.id,outcome:`not-evaluated`});continue}let e=this.conditionEvaluator.explain(t,n);r.conditions.push({conditionId:t.id,outcome:e.matched?`matched`:`not-matched`,resolvedValue:e.resolvedValue,resolvedType:e.resolvedType}),i=!e.matched}if(!i){if(e.target==`value`)return e.value==null?void 0:e.value.toString();if(e.target==`percentage`)return this.explainPercentage(e.percentages??[],t,n,r)}}};const N=[`equals`,`does NOT equal`,`is one of`,`is NOT one of`,`starts with any of`,`does NOT start with any of`,`ends with any of`,`does NOT end with any of`],P=[`=`,`!=`,`>`,`>=`,`<`,`<=`],F=[`is one of`,`is NOT one of`,`>`,`>=`,`<`,`<=`],I=[`is before`,`is after`],L=[`contains any of`,`does NOT contain any of`],R=[`text`,`number`,`semver`,`datetime`,`array`];exports.ArrayOperatorList=L,exports.ConditionEvaluator=k,exports.ConfigEvaluator=M,exports.DatetimeOperatorList=I,exports.EVALUATOR_VERSION=`0.1.0`,exports.NumberOperatorList=P,exports.SemverOperatorList=F,exports.TargetTypeList=R,exports.TextOperatorList=N,exports.assignPercentage=j;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//#region ../shared/src/types.d.ts
|
|
2
|
+
declare const ConfigTypeList: readonly ["custom", "boolean", "string", "integer", "float", "enum", "url", "json"];
|
|
3
|
+
type ConfigType = (typeof ConfigTypeList)[number];
|
|
4
|
+
type ConfigState = {
|
|
5
|
+
id: string;
|
|
6
|
+
key: string;
|
|
7
|
+
type: ConfigType;
|
|
8
|
+
value: string | undefined | null;
|
|
9
|
+
valueId?: string | undefined | null;
|
|
10
|
+
};
|
|
11
|
+
interface ConfigDirectorLogger {
|
|
12
|
+
debug(message: string, ...args: any): void;
|
|
13
|
+
info(message: string, ...args: any): void;
|
|
14
|
+
warn(message: string, ...args: any): void;
|
|
15
|
+
error(message: string, ...args: any): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
19
|
+
* rules evaluation.
|
|
20
|
+
*/
|
|
21
|
+
type ConfigDirectorContext = {
|
|
22
|
+
/**
|
|
23
|
+
* The user's identifier. This should be a value that uniquely identifies an application
|
|
24
|
+
* user.
|
|
25
|
+
* In the case of anonymous users, you could generate a UUID or alternatively not provide
|
|
26
|
+
* the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
|
|
27
|
+
* value is used for segmenting users in percentage rollouts, and changes to the {@link id}
|
|
28
|
+
* could result in the user being assigned to a different percentile.
|
|
29
|
+
*/
|
|
30
|
+
id?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The user's display name. This will be shown in the ConfigDirector dashboard and may be
|
|
33
|
+
* used for targeting rules.
|
|
34
|
+
*/
|
|
35
|
+
name?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Any arbitrary traits for the current user. They will be shown in the ConfigDirector
|
|
38
|
+
* dashboard and may be used for targeting rules.
|
|
39
|
+
*/
|
|
40
|
+
traits?: {
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Whether or not to treat the context as anonymous during evaluation. When `true`, the
|
|
45
|
+
* context values will be used for targeting rules evaluation but the context will not be
|
|
46
|
+
* persisted and will not appear in the dashboard.
|
|
47
|
+
*
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
anonymous?: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Metadata about your application. It is recommended you include these values when configuring
|
|
54
|
+
* a ConfigDirector client so that you can use them in targeting rules.
|
|
55
|
+
*/
|
|
56
|
+
type ConfigDirectorMetaContext = {
|
|
57
|
+
appVersion?: string;
|
|
58
|
+
appName?: string;
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/types.d.ts
|
|
62
|
+
declare const TextOperatorList: readonly ["equals", "does NOT equal", "is one of", "is NOT one of", "starts with any of", "does NOT start with any of", "ends with any of", "does NOT end with any of"];
|
|
63
|
+
declare const NumberOperatorList: readonly ["=", "!=", ">", ">=", "<", "<="];
|
|
64
|
+
declare const SemverOperatorList: readonly ["is one of", "is NOT one of", ">", ">=", "<", "<="];
|
|
65
|
+
declare const DatetimeOperatorList: readonly ["is before", "is after"];
|
|
66
|
+
declare const ArrayOperatorList: readonly ["contains any of", "does NOT contain any of"];
|
|
67
|
+
type TextOperator = (typeof TextOperatorList)[number];
|
|
68
|
+
type NumberOperator = (typeof NumberOperatorList)[number];
|
|
69
|
+
type SemverOperator = (typeof SemverOperatorList)[number];
|
|
70
|
+
type DatetimeOperator = (typeof DatetimeOperatorList)[number];
|
|
71
|
+
type ArrayOperator = (typeof ArrayOperatorList)[number];
|
|
72
|
+
type Operator = TextOperator | NumberOperator | SemverOperator | DatetimeOperator | ArrayOperator;
|
|
73
|
+
type Target = "value" | "percentage";
|
|
74
|
+
declare const TargetTypeList: readonly ["text", "number", "semver", "datetime", "array"];
|
|
75
|
+
type TargetType = (typeof TargetTypeList)[number];
|
|
76
|
+
type Condition = {
|
|
77
|
+
id: string;
|
|
78
|
+
attribute: string;
|
|
79
|
+
trait?: string | undefined;
|
|
80
|
+
operator: Operator;
|
|
81
|
+
targetType: TargetType;
|
|
82
|
+
targetValues: string[];
|
|
83
|
+
};
|
|
84
|
+
type ConditionalRule = {
|
|
85
|
+
id: string;
|
|
86
|
+
type: "conditional";
|
|
87
|
+
order: number;
|
|
88
|
+
conditions: Condition[];
|
|
89
|
+
target: Target;
|
|
90
|
+
value: string | number | boolean | undefined;
|
|
91
|
+
percentages: Percentage[];
|
|
92
|
+
};
|
|
93
|
+
type Percentage = {
|
|
94
|
+
id: string;
|
|
95
|
+
percentage: number;
|
|
96
|
+
value: string | number | boolean | undefined;
|
|
97
|
+
};
|
|
98
|
+
type PercentageRule = {
|
|
99
|
+
id: string;
|
|
100
|
+
type: "percentage";
|
|
101
|
+
target: "percentage";
|
|
102
|
+
order: number;
|
|
103
|
+
percentages: Percentage[];
|
|
104
|
+
};
|
|
105
|
+
type Rule = ConditionalRule | PercentageRule;
|
|
106
|
+
type TargetingRules = {
|
|
107
|
+
defaultValue: string;
|
|
108
|
+
rules: Rule[];
|
|
109
|
+
};
|
|
110
|
+
type Variation = {
|
|
111
|
+
name: string | null | undefined;
|
|
112
|
+
value: string | number | boolean;
|
|
113
|
+
};
|
|
114
|
+
type NumericTypeConstraints = {
|
|
115
|
+
min?: {
|
|
116
|
+
relation: ">" | ">=";
|
|
117
|
+
value: number;
|
|
118
|
+
} | undefined;
|
|
119
|
+
max?: {
|
|
120
|
+
relation: "<" | "<=";
|
|
121
|
+
value: number;
|
|
122
|
+
} | undefined;
|
|
123
|
+
};
|
|
124
|
+
type EnumTypeConstraints = {
|
|
125
|
+
valueType: "string" | "number";
|
|
126
|
+
values: string[];
|
|
127
|
+
};
|
|
128
|
+
type Config = {
|
|
129
|
+
id: string;
|
|
130
|
+
key: string;
|
|
131
|
+
type: ConfigType;
|
|
132
|
+
typeConstraints?: NumericTypeConstraints | EnumTypeConstraints | null | undefined;
|
|
133
|
+
variations: Variation[];
|
|
134
|
+
target: TargetingRules;
|
|
135
|
+
};
|
|
136
|
+
type EvaluationContext = {
|
|
137
|
+
context?: ConfigDirectorContext;
|
|
138
|
+
metadata?: ConfigDirectorMetaContext;
|
|
139
|
+
};
|
|
140
|
+
type ResolvedType = "absent" | "scalar" | "array" | "object" | "unknown-attribute";
|
|
141
|
+
type ConditionCheck = {
|
|
142
|
+
matched: boolean;
|
|
143
|
+
resolvedValue: unknown;
|
|
144
|
+
resolvedType: ResolvedType;
|
|
145
|
+
};
|
|
146
|
+
type ConditionExplanation = {
|
|
147
|
+
conditionId: string;
|
|
148
|
+
outcome: "not-evaluated";
|
|
149
|
+
} | {
|
|
150
|
+
conditionId: string;
|
|
151
|
+
outcome: "matched" | "not-matched";
|
|
152
|
+
resolvedValue: unknown;
|
|
153
|
+
resolvedType: ResolvedType;
|
|
154
|
+
};
|
|
155
|
+
type Share = {
|
|
156
|
+
percentageId: string;
|
|
157
|
+
from: number;
|
|
158
|
+
to: number;
|
|
159
|
+
value: string | undefined;
|
|
160
|
+
};
|
|
161
|
+
type BucketExplanation = {
|
|
162
|
+
identifier: string;
|
|
163
|
+
identifierWasGenerated: boolean;
|
|
164
|
+
assignedPercentage: number;
|
|
165
|
+
shares: Share[];
|
|
166
|
+
selectedPercentageId: string | undefined;
|
|
167
|
+
};
|
|
168
|
+
type RuleOutcome = "matched" | "not-matched" | "not-evaluated" | "errored";
|
|
169
|
+
type RuleExplanation = {
|
|
170
|
+
ruleId: string;
|
|
171
|
+
outcome: RuleOutcome;
|
|
172
|
+
conditions: ConditionExplanation[];
|
|
173
|
+
bucket: BucketExplanation | undefined;
|
|
174
|
+
};
|
|
175
|
+
type ServedBy = {
|
|
176
|
+
kind: "rule";
|
|
177
|
+
ruleId: string;
|
|
178
|
+
} | {
|
|
179
|
+
kind: "default";
|
|
180
|
+
};
|
|
181
|
+
type EvaluationExplanation = {
|
|
182
|
+
value: string | undefined;
|
|
183
|
+
servedBy: ServedBy;
|
|
184
|
+
rules: RuleExplanation[];
|
|
185
|
+
};
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/ConfigEvaluator.d.ts
|
|
188
|
+
declare class ConfigEvaluator {
|
|
189
|
+
private readonly logger;
|
|
190
|
+
private readonly conditionEvaluator;
|
|
191
|
+
constructor(logger: ConfigDirectorLogger);
|
|
192
|
+
evaluate(config: Config, context?: EvaluationContext): ConfigState;
|
|
193
|
+
/**
|
|
194
|
+
* Evaluate a config for a context and record how the value was reached: every rule in the
|
|
195
|
+
* order it was walked with its outcome, the conditions that were checked with what they
|
|
196
|
+
* resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with
|
|
197
|
+
* only the value kept, so the two cannot disagree.
|
|
198
|
+
*/
|
|
199
|
+
explain(config: Config, context?: EvaluationContext): EvaluationExplanation;
|
|
200
|
+
private explainRule;
|
|
201
|
+
private explainPercentage;
|
|
202
|
+
private explainConditionalRule;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/ConditionEvaluator.d.ts
|
|
206
|
+
declare class ConditionEvaluator {
|
|
207
|
+
/**
|
|
208
|
+
* Decide whether a condition holds for a context.
|
|
209
|
+
*
|
|
210
|
+
*
|
|
211
|
+
* An attribute the context does not carry is not an error: it resolves to the empty string, so
|
|
212
|
+
* a condition can still match or not match on its own terms.
|
|
213
|
+
*/
|
|
214
|
+
evaluate(condition: Condition, context?: EvaluationContext): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Decide whether a condition holds for a context, and say what the condition was compared
|
|
217
|
+
* against: the raw resolved value and its shape.
|
|
218
|
+
*/
|
|
219
|
+
explain(condition: Condition, context?: EvaluationContext): ConditionCheck;
|
|
220
|
+
private compare;
|
|
221
|
+
/** The raw value a condition targets, or one of the two sentinels. */
|
|
222
|
+
private resolve;
|
|
223
|
+
private findTraitValue;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/percent-hashing.d.ts
|
|
227
|
+
declare const assignPercentage: (data: {
|
|
228
|
+
configId: string;
|
|
229
|
+
contextIdentifier: string;
|
|
230
|
+
}) => number;
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/index.d.ts
|
|
233
|
+
declare const EVALUATOR_VERSION: string;
|
|
234
|
+
//#endregion
|
|
235
|
+
export { type ArrayOperator, ArrayOperatorList, type BucketExplanation, type Condition, type ConditionCheck, ConditionEvaluator, type ConditionExplanation, type ConditionalRule, type Config, type ConfigDirectorContext, type ConfigDirectorLogger, type ConfigDirectorMetaContext, ConfigEvaluator, type ConfigState, type ConfigType, type DatetimeOperator, DatetimeOperatorList, EVALUATOR_VERSION, type EnumTypeConstraints, type EvaluationContext, type EvaluationExplanation, type NumberOperator, NumberOperatorList, type NumericTypeConstraints, type Operator, type Percentage, type PercentageRule, type ResolvedType, type Rule, type RuleExplanation, type RuleOutcome, type SemverOperator, SemverOperatorList, type ServedBy, type Share, type Target, type TargetType, TargetTypeList, type TargetingRules, type TextOperator, TextOperatorList, type Variation, assignPercentage };
|
|
236
|
+
//# sourceMappingURL=configdirector-config-evaluator.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configdirector-config-evaluator.d.cts","names":[],"sources":["../../shared/src/types.ts","../src/types.ts","../src/ConfigEvaluator.ts","../src/ConditionEvaluator.ts","../src/percent-hashing.ts","../src/index.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//#region ../shared/src/types.d.ts
|
|
2
|
+
declare const ConfigTypeList: readonly ["custom", "boolean", "string", "integer", "float", "enum", "url", "json"];
|
|
3
|
+
type ConfigType = (typeof ConfigTypeList)[number];
|
|
4
|
+
type ConfigState = {
|
|
5
|
+
id: string;
|
|
6
|
+
key: string;
|
|
7
|
+
type: ConfigType;
|
|
8
|
+
value: string | undefined | null;
|
|
9
|
+
valueId?: string | undefined | null;
|
|
10
|
+
};
|
|
11
|
+
interface ConfigDirectorLogger {
|
|
12
|
+
debug(message: string, ...args: any): void;
|
|
13
|
+
info(message: string, ...args: any): void;
|
|
14
|
+
warn(message: string, ...args: any): void;
|
|
15
|
+
error(message: string, ...args: any): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
19
|
+
* rules evaluation.
|
|
20
|
+
*/
|
|
21
|
+
type ConfigDirectorContext = {
|
|
22
|
+
/**
|
|
23
|
+
* The user's identifier. This should be a value that uniquely identifies an application
|
|
24
|
+
* user.
|
|
25
|
+
* In the case of anonymous users, you could generate a UUID or alternatively not provide
|
|
26
|
+
* the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
|
|
27
|
+
* value is used for segmenting users in percentage rollouts, and changes to the {@link id}
|
|
28
|
+
* could result in the user being assigned to a different percentile.
|
|
29
|
+
*/
|
|
30
|
+
id?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The user's display name. This will be shown in the ConfigDirector dashboard and may be
|
|
33
|
+
* used for targeting rules.
|
|
34
|
+
*/
|
|
35
|
+
name?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Any arbitrary traits for the current user. They will be shown in the ConfigDirector
|
|
38
|
+
* dashboard and may be used for targeting rules.
|
|
39
|
+
*/
|
|
40
|
+
traits?: {
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Whether or not to treat the context as anonymous during evaluation. When `true`, the
|
|
45
|
+
* context values will be used for targeting rules evaluation but the context will not be
|
|
46
|
+
* persisted and will not appear in the dashboard.
|
|
47
|
+
*
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
anonymous?: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Metadata about your application. It is recommended you include these values when configuring
|
|
54
|
+
* a ConfigDirector client so that you can use them in targeting rules.
|
|
55
|
+
*/
|
|
56
|
+
type ConfigDirectorMetaContext = {
|
|
57
|
+
appVersion?: string;
|
|
58
|
+
appName?: string;
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/types.d.ts
|
|
62
|
+
declare const TextOperatorList: readonly ["equals", "does NOT equal", "is one of", "is NOT one of", "starts with any of", "does NOT start with any of", "ends with any of", "does NOT end with any of"];
|
|
63
|
+
declare const NumberOperatorList: readonly ["=", "!=", ">", ">=", "<", "<="];
|
|
64
|
+
declare const SemverOperatorList: readonly ["is one of", "is NOT one of", ">", ">=", "<", "<="];
|
|
65
|
+
declare const DatetimeOperatorList: readonly ["is before", "is after"];
|
|
66
|
+
declare const ArrayOperatorList: readonly ["contains any of", "does NOT contain any of"];
|
|
67
|
+
type TextOperator = (typeof TextOperatorList)[number];
|
|
68
|
+
type NumberOperator = (typeof NumberOperatorList)[number];
|
|
69
|
+
type SemverOperator = (typeof SemverOperatorList)[number];
|
|
70
|
+
type DatetimeOperator = (typeof DatetimeOperatorList)[number];
|
|
71
|
+
type ArrayOperator = (typeof ArrayOperatorList)[number];
|
|
72
|
+
type Operator = TextOperator | NumberOperator | SemverOperator | DatetimeOperator | ArrayOperator;
|
|
73
|
+
type Target = "value" | "percentage";
|
|
74
|
+
declare const TargetTypeList: readonly ["text", "number", "semver", "datetime", "array"];
|
|
75
|
+
type TargetType = (typeof TargetTypeList)[number];
|
|
76
|
+
type Condition = {
|
|
77
|
+
id: string;
|
|
78
|
+
attribute: string;
|
|
79
|
+
trait?: string | undefined;
|
|
80
|
+
operator: Operator;
|
|
81
|
+
targetType: TargetType;
|
|
82
|
+
targetValues: string[];
|
|
83
|
+
};
|
|
84
|
+
type ConditionalRule = {
|
|
85
|
+
id: string;
|
|
86
|
+
type: "conditional";
|
|
87
|
+
order: number;
|
|
88
|
+
conditions: Condition[];
|
|
89
|
+
target: Target;
|
|
90
|
+
value: string | number | boolean | undefined;
|
|
91
|
+
percentages: Percentage[];
|
|
92
|
+
};
|
|
93
|
+
type Percentage = {
|
|
94
|
+
id: string;
|
|
95
|
+
percentage: number;
|
|
96
|
+
value: string | number | boolean | undefined;
|
|
97
|
+
};
|
|
98
|
+
type PercentageRule = {
|
|
99
|
+
id: string;
|
|
100
|
+
type: "percentage";
|
|
101
|
+
target: "percentage";
|
|
102
|
+
order: number;
|
|
103
|
+
percentages: Percentage[];
|
|
104
|
+
};
|
|
105
|
+
type Rule = ConditionalRule | PercentageRule;
|
|
106
|
+
type TargetingRules = {
|
|
107
|
+
defaultValue: string;
|
|
108
|
+
rules: Rule[];
|
|
109
|
+
};
|
|
110
|
+
type Variation = {
|
|
111
|
+
name: string | null | undefined;
|
|
112
|
+
value: string | number | boolean;
|
|
113
|
+
};
|
|
114
|
+
type NumericTypeConstraints = {
|
|
115
|
+
min?: {
|
|
116
|
+
relation: ">" | ">=";
|
|
117
|
+
value: number;
|
|
118
|
+
} | undefined;
|
|
119
|
+
max?: {
|
|
120
|
+
relation: "<" | "<=";
|
|
121
|
+
value: number;
|
|
122
|
+
} | undefined;
|
|
123
|
+
};
|
|
124
|
+
type EnumTypeConstraints = {
|
|
125
|
+
valueType: "string" | "number";
|
|
126
|
+
values: string[];
|
|
127
|
+
};
|
|
128
|
+
type Config = {
|
|
129
|
+
id: string;
|
|
130
|
+
key: string;
|
|
131
|
+
type: ConfigType;
|
|
132
|
+
typeConstraints?: NumericTypeConstraints | EnumTypeConstraints | null | undefined;
|
|
133
|
+
variations: Variation[];
|
|
134
|
+
target: TargetingRules;
|
|
135
|
+
};
|
|
136
|
+
type EvaluationContext = {
|
|
137
|
+
context?: ConfigDirectorContext;
|
|
138
|
+
metadata?: ConfigDirectorMetaContext;
|
|
139
|
+
};
|
|
140
|
+
type ResolvedType = "absent" | "scalar" | "array" | "object" | "unknown-attribute";
|
|
141
|
+
type ConditionCheck = {
|
|
142
|
+
matched: boolean;
|
|
143
|
+
resolvedValue: unknown;
|
|
144
|
+
resolvedType: ResolvedType;
|
|
145
|
+
};
|
|
146
|
+
type ConditionExplanation = {
|
|
147
|
+
conditionId: string;
|
|
148
|
+
outcome: "not-evaluated";
|
|
149
|
+
} | {
|
|
150
|
+
conditionId: string;
|
|
151
|
+
outcome: "matched" | "not-matched";
|
|
152
|
+
resolvedValue: unknown;
|
|
153
|
+
resolvedType: ResolvedType;
|
|
154
|
+
};
|
|
155
|
+
type Share = {
|
|
156
|
+
percentageId: string;
|
|
157
|
+
from: number;
|
|
158
|
+
to: number;
|
|
159
|
+
value: string | undefined;
|
|
160
|
+
};
|
|
161
|
+
type BucketExplanation = {
|
|
162
|
+
identifier: string;
|
|
163
|
+
identifierWasGenerated: boolean;
|
|
164
|
+
assignedPercentage: number;
|
|
165
|
+
shares: Share[];
|
|
166
|
+
selectedPercentageId: string | undefined;
|
|
167
|
+
};
|
|
168
|
+
type RuleOutcome = "matched" | "not-matched" | "not-evaluated" | "errored";
|
|
169
|
+
type RuleExplanation = {
|
|
170
|
+
ruleId: string;
|
|
171
|
+
outcome: RuleOutcome;
|
|
172
|
+
conditions: ConditionExplanation[];
|
|
173
|
+
bucket: BucketExplanation | undefined;
|
|
174
|
+
};
|
|
175
|
+
type ServedBy = {
|
|
176
|
+
kind: "rule";
|
|
177
|
+
ruleId: string;
|
|
178
|
+
} | {
|
|
179
|
+
kind: "default";
|
|
180
|
+
};
|
|
181
|
+
type EvaluationExplanation = {
|
|
182
|
+
value: string | undefined;
|
|
183
|
+
servedBy: ServedBy;
|
|
184
|
+
rules: RuleExplanation[];
|
|
185
|
+
};
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/ConfigEvaluator.d.ts
|
|
188
|
+
declare class ConfigEvaluator {
|
|
189
|
+
private readonly logger;
|
|
190
|
+
private readonly conditionEvaluator;
|
|
191
|
+
constructor(logger: ConfigDirectorLogger);
|
|
192
|
+
evaluate(config: Config, context?: EvaluationContext): ConfigState;
|
|
193
|
+
/**
|
|
194
|
+
* Evaluate a config for a context and record how the value was reached: every rule in the
|
|
195
|
+
* order it was walked with its outcome, the conditions that were checked with what they
|
|
196
|
+
* resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with
|
|
197
|
+
* only the value kept, so the two cannot disagree.
|
|
198
|
+
*/
|
|
199
|
+
explain(config: Config, context?: EvaluationContext): EvaluationExplanation;
|
|
200
|
+
private explainRule;
|
|
201
|
+
private explainPercentage;
|
|
202
|
+
private explainConditionalRule;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/ConditionEvaluator.d.ts
|
|
206
|
+
declare class ConditionEvaluator {
|
|
207
|
+
/**
|
|
208
|
+
* Decide whether a condition holds for a context.
|
|
209
|
+
*
|
|
210
|
+
*
|
|
211
|
+
* An attribute the context does not carry is not an error: it resolves to the empty string, so
|
|
212
|
+
* a condition can still match or not match on its own terms.
|
|
213
|
+
*/
|
|
214
|
+
evaluate(condition: Condition, context?: EvaluationContext): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Decide whether a condition holds for a context, and say what the condition was compared
|
|
217
|
+
* against: the raw resolved value and its shape.
|
|
218
|
+
*/
|
|
219
|
+
explain(condition: Condition, context?: EvaluationContext): ConditionCheck;
|
|
220
|
+
private compare;
|
|
221
|
+
/** The raw value a condition targets, or one of the two sentinels. */
|
|
222
|
+
private resolve;
|
|
223
|
+
private findTraitValue;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/percent-hashing.d.ts
|
|
227
|
+
declare const assignPercentage: (data: {
|
|
228
|
+
configId: string;
|
|
229
|
+
contextIdentifier: string;
|
|
230
|
+
}) => number;
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/index.d.ts
|
|
233
|
+
declare const EVALUATOR_VERSION: string;
|
|
234
|
+
//#endregion
|
|
235
|
+
export { type ArrayOperator, ArrayOperatorList, type BucketExplanation, type Condition, type ConditionCheck, ConditionEvaluator, type ConditionExplanation, type ConditionalRule, type Config, type ConfigDirectorContext, type ConfigDirectorLogger, type ConfigDirectorMetaContext, ConfigEvaluator, type ConfigState, type ConfigType, type DatetimeOperator, DatetimeOperatorList, EVALUATOR_VERSION, type EnumTypeConstraints, type EvaluationContext, type EvaluationExplanation, type NumberOperator, NumberOperatorList, type NumericTypeConstraints, type Operator, type Percentage, type PercentageRule, type ResolvedType, type Rule, type RuleExplanation, type RuleOutcome, type SemverOperator, SemverOperatorList, type ServedBy, type Share, type Target, type TargetType, TargetTypeList, type TargetingRules, type TextOperator, TextOperatorList, type Variation, assignPercentage };
|
|
236
|
+
//# sourceMappingURL=configdirector-config-evaluator.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configdirector-config-evaluator.d.mts","names":[],"sources":["../../shared/src/types.ts","../src/types.ts","../src/ConfigEvaluator.ts","../src/ConditionEvaluator.ts","../src/percent-hashing.ts","../src/index.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{findByPointer as e}from"@jsonjoy.com/json-pointer";import{DateTime as t}from"luxon";import*as n from"semver";import{rapidhash as r}from"rapidhash-js";const i=(e,t,n)=>{let r=n[0];switch(t.toLowerCase()){case`=`:case`equals`:return r!==void 0&&e===r;case`!=`:case`does not equal`:return r!==void 0&&e!==r;case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.some(t=>e.startsWith(t));case`does not start with any of`:return!n.some(t=>e.startsWith(t));case`ends with any of`:return n.some(t=>e.endsWith(t));case`does not end with any of`:return!n.some(t=>e.endsWith(t));default:return!1}},a=(e,t,n)=>{let r=t.toLowerCase(),i=o(e);if(i===void 0)return r===`!=`||r===`does not equal`;let a=n[0];if(a===void 0)return!1;let s=o(a);if(s===void 0)return!1;switch(r){case`=`:case`equals`:return i===s;case`!=`:case`does not equal`:return i!==s;case`<`:return i<s;case`<=`:return i<=s;case`>`:return i>s;case`>=`:return i>=s;default:return!1}},o=e=>{if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e!=`string`||e.length===0||!s(e))return;let t=Number(e);return Number.isFinite(t)?t:void 0},s=e=>{for(let t of e)if(!(t>=`0`&&t<=`9`)&&!`+-.eE`.includes(t))return!1;return!0},c=(e,t,n)=>{let r=n[0];if(r===void 0)return!1;let i=l(e),a=l(r);if(i===void 0||a===void 0)return!1;switch(t){case`is after`:return i>a;case`is before`:return i<a;default:return!1}},l=e=>{let n=t.fromISO(e,{zone:`utc`});return n.isValid?n.toMillis():void 0},u=(e,t,r)=>{let i=t.toLowerCase();if(e.trim().length===0)return i===`is not one of`;let a=n.coerce(e),o=r.map(e=>n.coerce(e)),s=o[0]??null;switch(i){case`=`:return d(a,s);case`<`:return f(a,s,n.lt);case`<=`:return f(a,s,n.lte);case`>`:return f(a,s,n.gt);case`>=`:return f(a,s,n.gte);case`is one of`:return o.some(e=>d(a,e));case`is not one of`:return!o.some(e=>d(a,e));default:return!1}},d=(e,t)=>e!==null&&t!==null&&n.eq(e,t),f=(e,t,n)=>e!==null&&t!==null&&n(e,t),p=Symbol(`absent`),m=Symbol(`unknown-attribute`),h=e=>typeof e==`string`||typeof e==`number`||typeof e==`boolean`,g=e=>e===p||e==null?``:h(e)?_(e):``,_=e=>typeof e==`string`?e:String(e),v=e=>e===p?void 0:e,y=e=>e===m?`unknown-attribute`:e===p?`absent`:h(e)?`scalar`:Array.isArray(e)?`array`:`object`,b=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r===`does not contain any of`;let i=e.filter(h).map(_);switch(r){case`contains any of`:return i.some(e=>n.includes(e));case`does not contain any of`:return!i.some(e=>n.includes(e));default:return!1}};var x=class{evaluate(e,t){return this.explain(e,t).matched}explain(e,t){let n=this.resolve(e,t),r=y(n),i=n===p||n===m?void 0:n;return n===m?{matched:!1,resolvedValue:i,resolvedType:r}:{matched:this.compare(e,n),resolvedValue:i,resolvedType:r}}compare(e,t){let n=e.targetValues??[];switch(e.targetType){case`text`:return i(g(t),e.operator,n);case`number`:return a(v(t),e.operator,n);case`datetime`:return c(g(t),e.operator,n);case`semver`:return u(g(t),e.operator,n);case`array`:return b(v(t),e.operator,n)}return!1}resolve(e,t){switch(e.attribute){case`identifier`:return S(t?.context?.id);case`name`:return S(t?.context?.name);case`appName`:return S(t?.metadata?.appName);case`appVersion`:return S(t?.metadata?.appVersion);case`traits`:return e.trait?S(this.findTraitValue(t?.context?.traits,e.trait)):p;default:return m}}findTraitValue(t,n){try{return e(n,t).val}catch{return}}};const S=e=>e??p,C=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number(r(t,{seed:15426857351n})%1000n)/10};var w=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new x,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.explain(e,t).value}}explain(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1)),r=[],i;for(let a of n){if(i){r.push({ruleId:a.id,outcome:`not-evaluated`,conditions:[],bucket:void 0});continue}let n=this.explainRule(a,e,t);r.push(n.explanation),n.value!==void 0&&(i={ruleId:a.id,value:n.value})}return{value:i?.value??e.target?.defaultValue,servedBy:i?{kind:`rule`,ruleId:i.ruleId}:{kind:`default`},rules:r}}explainRule(e,t,n){let r={ruleId:e.id,outcome:`not-matched`,conditions:[],bucket:void 0};try{let i;return e.type==`percentage`?i=this.explainPercentage(e.percentages??[],t,n,r):e.type==`conditional`&&(i=this.explainConditionalRule(e,t,n,r)),i!==void 0&&(r.outcome=`matched`),{explanation:r,value:i}}catch(n){return this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id}),{explanation:{ruleId:e.id,outcome:`errored`,conditions:[],bucket:void 0},value:void 0}}}explainPercentage(e,t,n,r){let i=n?.context?.id,a=i??crypto.randomUUID(),o=C({configId:t.id,contextIdentifier:a}),s=[],c=0,l;for(let t of e){let e=c+t.percentage;s.push({percentageId:t.id,from:c,to:e,value:t.value?.toString()}),l===void 0&&o<e&&(l=t),c=e}return r.bucket={identifier:a,identifierWasGenerated:i==null,assignedPercentage:o,shares:s,selectedPercentageId:l?.id},l?.value==null?void 0:l.value.toString()}explainConditionalRule(e,t,n,r){let i=!1;for(let t of e.conditions??[]){if(i){r.conditions.push({conditionId:t.id,outcome:`not-evaluated`});continue}let e=this.conditionEvaluator.explain(t,n);r.conditions.push({conditionId:t.id,outcome:e.matched?`matched`:`not-matched`,resolvedValue:e.resolvedValue,resolvedType:e.resolvedType}),i=!e.matched}if(!i){if(e.target==`value`)return e.value==null?void 0:e.value.toString();if(e.target==`percentage`)return this.explainPercentage(e.percentages??[],t,n,r)}}};const T=[`equals`,`does NOT equal`,`is one of`,`is NOT one of`,`starts with any of`,`does NOT start with any of`,`ends with any of`,`does NOT end with any of`],E=[`=`,`!=`,`>`,`>=`,`<`,`<=`],D=[`is one of`,`is NOT one of`,`>`,`>=`,`<`,`<=`],O=[`is before`,`is after`],k=[`contains any of`,`does NOT contain any of`],A=[`text`,`number`,`semver`,`datetime`,`array`],j=`0.1.0`;export{k as ArrayOperatorList,x as ConditionEvaluator,w as ConfigEvaluator,O as DatetimeOperatorList,j as EVALUATOR_VERSION,E as NumberOperatorList,D as SemverOperatorList,A as TargetTypeList,T as TextOperatorList,C as assignPercentage};
|
|
2
|
+
//# sourceMappingURL=configdirector-config-evaluator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configdirector-config-evaluator.mjs","names":[],"sources":["../src/text-comparison.ts","../src/numeric-comparison.ts","../src/date-comparison.ts","../src/semver-comparison.ts","../src/render.ts","../src/array-comparison.ts","../src/ConditionEvaluator.ts","../src/percent-hashing.ts","../src/ConfigEvaluator.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["import type { Operator } from \"./types\";\n\n/**\n * Text comparisons.\n *\n * `value` is always a string: an absent attribute has already been rendered to \"\".\n *\n * An empty `targetValues` makes the single-target operators false — there is nothing to compare\n * against — while the \"any of\" operators fall out naturally, so \"is NOT one of nothing\" is true.\n */\nexport const compareText = (value: string, operator: Operator, targetValues: string[]) => {\n const first: string | undefined = targetValues[0];\n\n switch (operator.toLowerCase()) {\n case \"=\":\n case \"equals\":\n return first !== undefined && value === first;\n case \"!=\":\n case \"does not equal\":\n return first !== undefined && value !== first;\n case \"is one of\":\n return targetValues.includes(value);\n case \"is not one of\":\n return !targetValues.includes(value);\n case \"starts with any of\":\n return targetValues.some((target) => value.startsWith(target));\n case \"does not start with any of\":\n return !targetValues.some((target) => value.startsWith(target));\n case \"ends with any of\":\n return targetValues.some((target) => value.endsWith(target));\n case \"does not end with any of\":\n return !targetValues.some((target) => value.endsWith(target));\n default:\n return false;\n }\n};\n","import type { Operator } from \"./types\";\n\n/**\n * Numeric comparisons.\n *\n * A value that is not a finite number, the wrong JSON type, unparseable text, or an infinity,\n * makes every operator false except `!=`, which is true. It does not throw: a rule that targets a\n * numeric trait some users do not have should skip those users, not be discarded.\n */\nexport const compareNumeric = (givenValue: unknown, operator: Operator, targetValues: string[]) => {\n const lowercaseOperator = operator.toLowerCase();\n\n const value = parseFinite(givenValue);\n if (value === undefined) {\n return lowercaseOperator === \"!=\" || lowercaseOperator === \"does not equal\";\n }\n\n const first: string | undefined = targetValues[0];\n if (first === undefined) {\n return false;\n }\n const target = parseFinite(first);\n if (target === undefined) {\n return false;\n }\n\n switch (lowercaseOperator) {\n case \"=\":\n case \"equals\":\n return value === target;\n case \"!=\":\n case \"does not equal\":\n return value !== target;\n case \"<\":\n return value < target;\n case \"<=\":\n return value <= target;\n case \">\":\n return value > target;\n case \">=\":\n return value >= target;\n default:\n return false;\n }\n};\n\n/**\n * Parse strictly: no surrounding whitespace, no trailing characters, decimal notation only.\n * `10`, `10.5`, `-5` and `1e3` parse; `26abc`, ` 42 `, `0x10`, `Infinity` and `\"\"` do not.\n */\nconst parseFinite = (value: unknown): number | undefined => {\n // Booleans are not numbers here, even though JavaScript would happily coerce them.\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : undefined;\n }\n if (typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n // Number() would accept surrounding whitespace, `0x10`, `Infinity`, and returns 0 for a blank\n // string. Restricting the characters first rules those out; Number() then validates the\n // grammar, rejecting the likes of `1.2.3` and `1e`. Both steps are a single pass.\n if (!isDecimalCharacters(value)) {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n};\n\nconst isDecimalCharacters = (value: string): boolean => {\n for (const character of value) {\n const isDigit = character >= \"0\" && character <= \"9\";\n if (!isDigit && !\"+-.eE\".includes(character)) {\n return false;\n }\n }\n return true;\n};\n","import type { Operator } from \"./types\";\nimport { DateTime } from \"luxon\";\n\n/**\n * Date and time comparisons.\n *\n * Parsing is delegated to Luxon rather than to `new Date`, for two reasons. `new Date` reads a\n * date-time with no offset as *local* time, which would make an evaluation depend on the\n * timezone of whichever machine ran it; `{ zone: \"utc\" }` gives the required reading directly.\n * And `new Date` rolls impossible dates over — `2026-02-30` becomes March 2nd — where the\n * ConfigDirector server and the Python SDK both reject them.\n *\n * Returns false whenever either side is missing or unparseable.\n */\nexport const compareDate = (value: string, operator: Operator, targetValues: string[]) => {\n const first: string | undefined = targetValues[0];\n if (first === undefined) {\n return false;\n }\n\n const dateValue = parseDate(value);\n const dateTarget = parseDate(first);\n if (dateValue === undefined || dateTarget === undefined) {\n return false;\n }\n\n switch (operator) {\n case \"is after\":\n return dateValue > dateTarget;\n case \"is before\":\n return dateValue < dateTarget;\n default:\n return false;\n }\n};\n\n/** The instant as milliseconds since the epoch, or undefined when the value is not a date. */\nconst parseDate = (value: string): number | undefined => {\n const parsed = DateTime.fromISO(value, { zone: \"utc\" });\n return parsed.isValid ? parsed.toMillis() : undefined;\n};\n","import type { Operator } from \"./types\";\nimport * as semver from \"semver\";\n\n/**\n * Semantic version comparisons.\n *\n * Both sides are coerced first, so partial versions such as `1.0` and `v`-prefixed ones are\n * usable, and any prerelease or build suffix is dropped. A value that cannot be coerced matches\n * nothing, which makes `is NOT one of` the only operator that can be true for it.\n */\nexport const compareSemver = (value: string, operator: Operator, targetValues: string[]) => {\n const lowercaseOperator = operator.toLowerCase();\n if (value.trim().length === 0) {\n return lowercaseOperator === \"is not one of\";\n }\n\n const version = semver.coerce(value);\n const targets = targetValues.map((target) => semver.coerce(target));\n const first: semver.SemVer | null = targets[0] ?? null;\n\n switch (lowercaseOperator) {\n case \"=\":\n return equals(version, first);\n case \"<\":\n return ordered(version, first, semver.lt);\n case \"<=\":\n return ordered(version, first, semver.lte);\n case \">\":\n return ordered(version, first, semver.gt);\n case \">=\":\n return ordered(version, first, semver.gte);\n case \"is one of\":\n return targets.some((target) => equals(version, target));\n case \"is not one of\":\n return !targets.some((target) => equals(version, target));\n default:\n return false;\n }\n};\n\n/** An operand that could not be coerced never compares equal. */\nconst equals = (value: semver.SemVer | null, target: semver.SemVer | null): boolean =>\n value !== null && target !== null && semver.eq(value, target);\n\n/** An operand that could not be coerced never satisfies an ordering comparison. */\nconst ordered = (\n value: semver.SemVer | null,\n target: semver.SemVer | null,\n compare: (a: semver.SemVer, b: semver.SemVer) => boolean,\n): boolean => value !== null && target !== null && compare(value, target);\n","/**\n * Rendering a resolved context value to text.\n */\nimport type { ResolvedType } from \"./types\";\n\n/** A value the context does not carry. Compared as \"\", so a negative operator can still match. */\nexport const ABSENT = Symbol(\"absent\");\n\n/** An attribute this SDK version does not know about. Not compared at all. */\nexport const UNKNOWN_ATTRIBUTE = Symbol(\"unknown-attribute\");\n\nexport type Resolved = unknown | typeof ABSENT;\n\nexport const isScalar = (value: unknown): value is string | number | boolean =>\n typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\";\n\n/**\n * Render a resolved value to text. Absent values, and values with no meaningful text form —\n * arrays, objects, null — render to the empty string rather than to something like\n * \"[object Object]\".\n */\nexport const render = (value: Resolved): string => {\n if (value === ABSENT || value === null || value === undefined) {\n return \"\";\n }\n return isScalar(value) ? renderScalar(value) : \"\";\n};\n\n/** Render a scalar the way JSON would spell it: `true`, `26`, `26.5`. */\nexport const renderScalar = (value: string | number | boolean): string => {\n if (typeof value === \"string\") {\n return value;\n }\n return String(value);\n};\n\n/** The raw value, for comparisons that inspect its JSON type rather than its text. */\nexport const unwrap = (value: Resolved): unknown => (value === ABSENT ? undefined : value);\n\nexport const resolvedTypeOf = (value: Resolved): ResolvedType => {\n if (value === UNKNOWN_ATTRIBUTE) {\n return \"unknown-attribute\";\n }\n if (value === ABSENT) {\n return \"absent\";\n }\n if (isScalar(value)) {\n return \"scalar\";\n }\n return Array.isArray(value) ? \"array\" : \"object\";\n};\n","import type { Operator } from \"./types\";\nimport { isScalar, renderScalar } from \"./render\";\n\n/**\n * Array membership comparisons.\n *\n * Elements are rendered to text before matching, so a list of numbers matches a target of \"1\".\n * A value that is not an array — including a comma-separated string — contains nothing, so only\n * the negative operator can be true for it.\n */\nexport const compareArray = (givenValue: unknown, operator: Operator, targetValues: string[]) => {\n const lowercaseOperator = operator.toLowerCase();\n if (!Array.isArray(givenValue)) {\n return lowercaseOperator === \"does not contain any of\";\n }\n\n // Nested arrays, objects, and nulls have no text form, so they are dropped rather than matching\n // an empty target value.\n const elements = givenValue.filter(isScalar).map(renderScalar);\n\n switch (lowercaseOperator) {\n case \"contains any of\":\n return elements.some((element) => targetValues.includes(element));\n case \"does not contain any of\":\n return !elements.some((element) => targetValues.includes(element));\n default:\n return false;\n }\n};\n","import type { Condition, ConditionCheck, EvaluationContext } from \"./types\";\nimport { findByPointer } from \"@jsonjoy.com/json-pointer\";\nimport { compareText } from \"./text-comparison\";\nimport { compareNumeric } from \"./numeric-comparison\";\nimport { compareDate } from \"./date-comparison\";\nimport { compareSemver } from \"./semver-comparison\";\nimport { compareArray } from \"./array-comparison\";\nimport { ABSENT, UNKNOWN_ATTRIBUTE, render, resolvedTypeOf, unwrap, type Resolved } from \"./render\";\n\nexport class ConditionEvaluator {\n /**\n * Decide whether a condition holds for a context.\n *\n *\n * An attribute the context does not carry is not an error: it resolves to the empty string, so\n * a condition can still match or not match on its own terms.\n */\n public evaluate(condition: Condition, context?: EvaluationContext): boolean {\n return this.explain(condition, context).matched;\n }\n\n /**\n * Decide whether a condition holds for a context, and say what the condition was compared\n * against: the raw resolved value and its shape.\n */\n public explain(condition: Condition, context?: EvaluationContext): ConditionCheck {\n const value = this.resolve(condition, context);\n const resolvedType = resolvedTypeOf(value);\n const resolvedValue = value === ABSENT || value === UNKNOWN_ATTRIBUTE ? undefined : value;\n if (value === UNKNOWN_ATTRIBUTE) {\n return { matched: false, resolvedValue, resolvedType };\n }\n return { matched: this.compare(condition, value), resolvedValue, resolvedType };\n }\n\n private compare(condition: Condition, value: Resolved): boolean {\n const targetValues = condition.targetValues ?? [];\n\n switch (condition.targetType) {\n case \"text\":\n return compareText(render(value), condition.operator, targetValues);\n case \"number\":\n return compareNumeric(unwrap(value), condition.operator, targetValues);\n case \"datetime\":\n return compareDate(render(value), condition.operator, targetValues);\n case \"semver\":\n return compareSemver(render(value), condition.operator, targetValues);\n case \"array\":\n return compareArray(unwrap(value), condition.operator, targetValues);\n }\n\n return false;\n }\n\n /** The raw value a condition targets, or one of the two sentinels. */\n private resolve(condition: Condition, context?: EvaluationContext): Resolved {\n switch (condition.attribute) {\n case \"identifier\":\n return orAbsent(context?.context?.id);\n case \"name\":\n return orAbsent(context?.context?.name);\n case \"appName\":\n return orAbsent(context?.metadata?.appName);\n case \"appVersion\":\n return orAbsent(context?.metadata?.appVersion);\n case \"traits\":\n if (!condition.trait) {\n return ABSENT;\n }\n return orAbsent(this.findTraitValue(context?.context?.traits, condition.trait));\n default:\n return UNKNOWN_ATTRIBUTE;\n }\n }\n\n private findTraitValue(traits: Record<string, unknown> | undefined, path: string) {\n try {\n return findByPointer(path, traits).val;\n } catch {\n return undefined;\n }\n }\n}\n\nconst orAbsent = (value: unknown): Resolved => (value === undefined || value === null ? ABSENT : value);\n","import { rapidhash } from \"rapidhash-js\";\n\nconst SEED = 0x397832987n;\n\nexport const assignPercentage = (data: {configId: string, contextIdentifier: string}): number => {\n const value = `${data.contextIdentifier}-${data.configId}`;\n\n return Number(rapidhash(value, { seed: SEED }) % 1_000n) / 10.0;\n};\n","import { ConditionEvaluator } from \"./ConditionEvaluator\";\nimport { assignPercentage } from \"./percent-hashing\";\nimport type {\n ConditionalRule,\n Config,\n EvaluationContext,\n ConfigState,\n Percentage,\n Rule,\n ConfigDirectorLogger,\n EvaluationExplanation,\n RuleExplanation,\n Share,\n} from \"./types\";\n\ntype RuleResult = {\n explanation: RuleExplanation;\n value: string | undefined;\n};\n\nexport class ConfigEvaluator {\n private readonly conditionEvaluator = new ConditionEvaluator();\n\n constructor(private readonly logger: ConfigDirectorLogger) {\n if (!logger || !logger.warn || !logger.error || !logger.info) {\n throw new TypeError(\"The provided logger is not a valid 'ConfigDirectorLogger'\");\n }\n\n this.logger = logger;\n }\n\n public evaluate(config: Config, context?: EvaluationContext): ConfigState {\n return {\n id: config.id,\n key: config.key,\n type: config.type,\n value: this.explain(config, context).value,\n };\n }\n\n /**\n * Evaluate a config for a context and record how the value was reached: every rule in the\n * order it was walked with its outcome, the conditions that were checked with what they\n * resolved to, and the share a rollout assigned the context to. `evaluate` is this walk with\n * only the value kept, so the two cannot disagree.\n */\n public explain(config: Config, context?: EvaluationContext): EvaluationExplanation {\n const rules = [...(config.target?.rules ?? [])].sort(\n (a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER),\n );\n const explanations: RuleExplanation[] = [];\n let served: { ruleId: string; value: string } | undefined;\n for (const rule of rules) {\n if (served) {\n explanations.push({ ruleId: rule.id, outcome: \"not-evaluated\", conditions: [], bucket: undefined });\n continue;\n }\n const result = this.explainRule(rule, config, context);\n explanations.push(result.explanation);\n if (result.value !== undefined) {\n served = { ruleId: rule.id, value: result.value };\n }\n }\n return {\n value: served?.value ?? config.target?.defaultValue,\n servedBy: served ? { kind: \"rule\", ruleId: served.ruleId } : { kind: \"default\" },\n rules: explanations,\n };\n }\n\n private explainRule(rule: Rule, config: Config, context?: EvaluationContext): RuleResult {\n const explanation: RuleExplanation = {\n ruleId: rule.id,\n outcome: \"not-matched\",\n conditions: [],\n bucket: undefined,\n };\n try {\n let value: string | undefined;\n if (rule.type == \"percentage\") {\n value = this.explainPercentage(rule.percentages ?? [], config, context, explanation);\n } else if (rule.type == \"conditional\") {\n value = this.explainConditionalRule(rule, config, context, explanation);\n }\n if (value !== undefined) {\n explanation.outcome = \"matched\";\n }\n return { explanation, value };\n } catch (error) {\n this.logger.warn(\n `There was an error while evaluating a targeting rule '${rule?.id}' for '${config?.key}'. The rule will be disregarded.`,\n {\n error,\n configKey: config?.key,\n ruleId: rule?.id,\n },\n );\n return {\n explanation: { ruleId: rule.id, outcome: \"errored\", conditions: [], bucket: undefined },\n value: undefined,\n };\n }\n }\n\n private explainPercentage(\n percentages: Percentage[],\n config: Config,\n context: EvaluationContext | undefined,\n explanation: RuleExplanation,\n ): string | undefined {\n const contextIdentifier = context?.context?.id;\n const identifier = contextIdentifier ?? crypto.randomUUID();\n const assignedPercentage = assignPercentage({ configId: config.id, contextIdentifier: identifier });\n const shares: Share[] = [];\n let sum = 0.0;\n let selected: Percentage | undefined = undefined;\n // A bucket spans [sum, sum + percentage). Strict, so a context landing exactly on a boundary\n // belongs to the bucket that starts there -- which is what keeps a 0% bucket unreachable and\n // each bucket's share exact. See SEMANTICS.md §7.1 in targeting-rules-contract.\n for (const percentage of percentages) {\n const to = sum + percentage.percentage;\n shares.push({ percentageId: percentage.id, from: sum, to, value: percentage.value?.toString() });\n if (selected === undefined && assignedPercentage < to) {\n selected = percentage;\n }\n sum = to;\n }\n\n explanation.bucket = {\n identifier,\n identifierWasGenerated: contextIdentifier == null,\n assignedPercentage,\n shares,\n selectedPercentageId: selected?.id,\n };\n return selected?.value != null ? selected.value.toString() : undefined;\n }\n\n private explainConditionalRule(\n rule: ConditionalRule,\n config: Config,\n context: EvaluationContext | undefined,\n explanation: RuleExplanation,\n ): string | undefined {\n let failed = false;\n for (const condition of rule.conditions ?? []) {\n if (failed) {\n explanation.conditions.push({ conditionId: condition.id, outcome: \"not-evaluated\" });\n continue;\n }\n const check = this.conditionEvaluator.explain(condition, context);\n explanation.conditions.push({\n conditionId: condition.id,\n outcome: check.matched ? \"matched\" : \"not-matched\",\n resolvedValue: check.resolvedValue,\n resolvedType: check.resolvedType,\n });\n failed = !check.matched;\n }\n\n if (failed) {\n return undefined;\n }\n if (rule.target == \"value\") {\n return rule.value != null ? rule.value.toString() : undefined;\n }\n if (rule.target == \"percentage\") {\n return this.explainPercentage(rule.percentages ?? [], config, context, explanation);\n }\n return undefined;\n }\n}\n","import type { ConfigType, ConfigDirectorContext, ConfigDirectorMetaContext } from \"@shared/types\";\nexport type { ConfigState, ConfigDirectorLogger } from \"@shared/types\";\n\nexport const TextOperatorList = [\n \"equals\",\n \"does NOT equal\",\n \"is one of\",\n \"is NOT one of\",\n \"starts with any of\",\n \"does NOT start with any of\",\n \"ends with any of\",\n \"does NOT end with any of\",\n] as const;\nexport const NumberOperatorList = [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\"] as const;\nexport const SemverOperatorList = [\"is one of\", \"is NOT one of\", \">\", \">=\", \"<\", \"<=\"] as const;\nexport const DatetimeOperatorList = [\"is before\", \"is after\"] as const;\nexport const ArrayOperatorList = [\"contains any of\", \"does NOT contain any of\"] as const;\n\nexport type TextOperator = (typeof TextOperatorList)[number];\nexport type NumberOperator = (typeof NumberOperatorList)[number];\nexport type SemverOperator = (typeof SemverOperatorList)[number];\nexport type DatetimeOperator = (typeof DatetimeOperatorList)[number];\nexport type ArrayOperator = (typeof ArrayOperatorList)[number];\n\nexport type Operator = TextOperator | NumberOperator | SemverOperator | DatetimeOperator | ArrayOperator;\n\nexport type Target = \"value\" | \"percentage\";\n\nexport const TargetTypeList = [\"text\", \"number\", \"semver\", \"datetime\", \"array\"] as const;\nexport type TargetType = (typeof TargetTypeList)[number];\n\nexport type Condition = {\n id: string;\n attribute: string;\n trait?: string | undefined;\n operator: Operator;\n targetType: TargetType;\n targetValues: string[];\n};\nexport type ConditionalRule = {\n id: string;\n type: \"conditional\";\n order: number;\n conditions: Condition[];\n target: Target;\n value: string | number | boolean | undefined;\n percentages: Percentage[];\n};\nexport type Percentage = {\n id: string;\n percentage: number;\n value: string | number | boolean | undefined;\n};\nexport type PercentageRule = {\n id: string;\n type: \"percentage\";\n target: \"percentage\";\n order: number;\n percentages: Percentage[];\n};\n\nexport type Rule = ConditionalRule | PercentageRule;\n\nexport type TargetingRules = {\n defaultValue: string;\n rules: Rule[];\n};\n\nexport type Variation = {\n name: string | null | undefined;\n value: string | number | boolean;\n};\n\nexport type NumericTypeConstraints = {\n min?:\n | {\n relation: \">\" | \">=\";\n value: number;\n }\n | undefined;\n max?:\n | {\n relation: \"<\" | \"<=\";\n value: number;\n }\n | undefined;\n};\n\nexport type EnumTypeConstraints = {\n valueType: \"string\" | \"number\";\n values: string[];\n};\n\nexport type Config = {\n id: string;\n key: string;\n type: ConfigType;\n typeConstraints?: NumericTypeConstraints | EnumTypeConstraints | null | undefined;\n variations: Variation[];\n target: TargetingRules;\n};\n\nexport type EvaluationContext = {\n context?: ConfigDirectorContext;\n metadata?: ConfigDirectorMetaContext;\n};\n\nexport type ResolvedType = \"absent\" | \"scalar\" | \"array\" | \"object\" | \"unknown-attribute\";\n\nexport type ConditionCheck = {\n matched: boolean;\n resolvedValue: unknown;\n resolvedType: ResolvedType;\n};\n\nexport type ConditionExplanation =\n | { conditionId: string; outcome: \"not-evaluated\" }\n | {\n conditionId: string;\n outcome: \"matched\" | \"not-matched\";\n resolvedValue: unknown;\n resolvedType: ResolvedType;\n };\n\nexport type Share = {\n percentageId: string;\n from: number;\n to: number;\n value: string | undefined;\n};\n\nexport type BucketExplanation = {\n identifier: string;\n identifierWasGenerated: boolean;\n assignedPercentage: number;\n shares: Share[];\n selectedPercentageId: string | undefined;\n};\n\nexport type RuleOutcome = \"matched\" | \"not-matched\" | \"not-evaluated\" | \"errored\";\n\nexport type RuleExplanation = {\n ruleId: string;\n outcome: RuleOutcome;\n conditions: ConditionExplanation[];\n bucket: BucketExplanation | undefined;\n};\n\nexport type ServedBy = { kind: \"rule\"; ruleId: string } | { kind: \"default\" };\n\nexport type EvaluationExplanation = {\n value: string | undefined;\n servedBy: ServedBy;\n rules: RuleExplanation[];\n};\n","export { ConfigEvaluator } from \"./ConfigEvaluator\";\nexport { ConditionEvaluator } from \"./ConditionEvaluator\";\nexport { assignPercentage } from \"./percent-hashing\";\nexport type {\n ArrayOperator,\n BucketExplanation,\n Condition,\n ConditionCheck,\n ConditionExplanation,\n ConditionalRule,\n Config,\n ConfigDirectorLogger,\n ConfigState,\n DatetimeOperator,\n EnumTypeConstraints,\n EvaluationContext,\n EvaluationExplanation,\n NumberOperator,\n NumericTypeConstraints,\n Operator,\n Percentage,\n PercentageRule,\n ResolvedType,\n Rule,\n RuleExplanation,\n RuleOutcome,\n SemverOperator,\n ServedBy,\n Share,\n Target,\n TargetType,\n TargetingRules,\n TextOperator,\n Variation,\n} from \"./types\";\nexport {\n ArrayOperatorList,\n DatetimeOperatorList,\n NumberOperatorList,\n SemverOperatorList,\n TargetTypeList,\n TextOperatorList,\n} from \"./types\";\nexport type { ConfigDirectorContext, ConfigDirectorMetaContext, ConfigType } from \"@shared/types\";\n\nexport const EVALUATOR_VERSION: string = \"__VERSION__\";\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@configdirector/config-evaluator-internal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ConfigDirector internal package, not intended for use outside ConfigDirector. The targeting rules evaluator shared by the ConfigDirector server SDKs and the ConfigDirector dashboard; its API can change in any release.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "ConfigDirector",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"configdirector"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://www.configdirector.com",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/ConfigDirector/js-sdks.git"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/configdirector-config-evaluator.d.cts",
|
|
20
|
+
"default": "./dist/configdirector-config-evaluator.cjs"
|
|
21
|
+
},
|
|
22
|
+
"default": {
|
|
23
|
+
"types": "./dist/configdirector-config-evaluator.d.mts",
|
|
24
|
+
"default": "./dist/configdirector-config-evaluator.mjs"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/*config-evaluator.*"
|
|
30
|
+
],
|
|
31
|
+
"types": "dist/configdirector-config-evaluator.d.mts",
|
|
32
|
+
"module": "dist/configdirector-config-evaluator.mjs",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"verify:dist": "node ../../scripts/verify-dist-version.mjs",
|
|
39
|
+
"clean": "rm -fr dist",
|
|
40
|
+
"lint": "eslint .",
|
|
41
|
+
"test": "vitest",
|
|
42
|
+
"coverage": "vitest run --coverage",
|
|
43
|
+
"ci:test": "vitest run",
|
|
44
|
+
"release": "yarn clean && yarn lint && yarn ci:test && yarn build && yarn verify:dist && npm publish --access public",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^10.0.1",
|
|
49
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
50
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
51
|
+
"@types/luxon": "^3",
|
|
52
|
+
"@types/semver": "^7",
|
|
53
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
54
|
+
"eslint": "^10.7.0",
|
|
55
|
+
"globals": "^17.3.0",
|
|
56
|
+
"msw": "^2.12.9",
|
|
57
|
+
"prettier": "^3.8.1",
|
|
58
|
+
"rimraf": "^6.1.3",
|
|
59
|
+
"tsdown": "^0.21.7",
|
|
60
|
+
"tslib": "^2.8.1",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"typescript-eslint": "^8.58.0",
|
|
63
|
+
"vite": "^8.0.16",
|
|
64
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
65
|
+
"vitest": "^4.1.8"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@jsonjoy.com/json-pointer": "^18.1.0",
|
|
69
|
+
"luxon": ">=1.28.0 <4.0.0",
|
|
70
|
+
"rapidhash-js": "^3.0.0",
|
|
71
|
+
"semver": "^7.7.4"
|
|
72
|
+
}
|
|
73
|
+
}
|