New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@bucketco/flag-evaluation

Package Overview
Dependencies
Maintainers
0
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bucketco/flag-evaluation - npm Package Compare versions

Comparing version 0.0.7 to 0.1.0

141

./dist/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluate = exports.hashInt = exports.evaluateTargeting = exports.unflattenJSON = exports.flattenJSON = void 0;
exports.evaluateFeatureRules = exports.evaluate = exports.hashInt = exports.unflattenJSON = exports.flattenJSON = void 0;
const node_crypto_1 = require("node:crypto");
/**
* Flattens a nested JSON object into a single-level object, with keys indicating the nesting levels.
* Keys in the resulting object are represented in a dot notation to reflect the nesting structure of the original data.
*
* @param {object} data - The nested JSON object to be flattened.
* @return {Record<string, string>} A flattened JSON object with "stringified" keys and values.
*/
function flattenJSON(data) {

@@ -34,2 +41,8 @@ if (Object.keys(data).length === 0)

exports.flattenJSON = flattenJSON;
/**
* Converts a flattened JSON object with dot-separated keys into a nested JSON object.
*
* @param {Record<string, any>} data - The flattened JSON object where keys are dot-separated representing nested levels.
* @return {Record<string, any>} The unflattened JSON object with nested structure restored.
*/
function unflattenJSON(data) {

@@ -41,3 +54,5 @@ const result = {};

if (index === keys.length - 1) {
acc[key] = data[i];
if (typeof acc === "object") {
acc[key] = data[i];
}
}

@@ -53,18 +68,9 @@ else if (!acc[key]) {

exports.unflattenJSON = unflattenJSON;
function evaluateTargeting({ context, feature, }) {
const flatContext = flattenJSON(context);
const missingContextFieldsSet = new Set();
const ruleEvaluationResults = feature.targeting.rules.map((rule) => evaluateRecursively(rule.filter, flatContext, missingContextFieldsSet));
const missingContextFields = Array.from(missingContextFieldsSet);
const firstIdx = ruleEvaluationResults.findIndex(Boolean);
return {
value: firstIdx > -1,
feature,
context: flatContext,
ruleEvaluationResults,
reason: firstIdx > -1 ? `rule #${firstIdx} matched` : "no matched rules",
missingContextFields,
};
}
exports.evaluateTargeting = evaluateTargeting;
/**
* Generates a hashed integer based on the input string. The method extracts 20 bits from the hash,
* scales it to a range between 0 and 100000, and returns the resultant integer.
*
* @param {string} hashInput - The input string used to generate the hash.
* @return {number} A number between 0 and 100000 derived from the hash of the input string.
*/
function hashInt(hashInput) {

@@ -81,37 +87,13 @@ // 1. hash the key and the partial rollout attribute

exports.hashInt = hashInt;
function evaluateRecursively(filter, context, missingContextFieldsSet) {
switch (filter.type) {
case "constant":
return filter.value;
case "context":
if (!(filter.field in context)) {
missingContextFieldsSet.add(filter.field);
return false;
}
return evaluate(context[filter.field], filter.operator, filter.values || []);
case "rolloutPercentage": {
if (!(filter.partialRolloutAttribute in context)) {
missingContextFieldsSet.add(filter.partialRolloutAttribute);
return false;
}
const hashVal = hashInt(`${filter.key}.${context[filter.partialRolloutAttribute]}`);
return hashVal < filter.partialRolloutThreshold;
}
case "group":
return filter.filters.reduce((acc, current) => {
if (filter.operator === "and") {
return (acc &&
evaluateRecursively(current, context, missingContextFieldsSet));
}
return (acc || evaluateRecursively(current, context, missingContextFieldsSet));
}, filter.operator === "and");
case "negation":
return !evaluateRecursively(filter.filter, context, missingContextFieldsSet);
default:
return false;
}
}
function evaluate(fieldValue, op, values) {
/**
* Evaluates a field value against a specified operator and comparison values.
*
* @param {string} fieldValue - The value to be evaluated.
* @param {ContextFilterOperator} operator - The operator used for the evaluation (e.g., "CONTAINS", "GT").
* @param {string[]} values - An array of comparison values for evaluation.
* @return {boolean} The result of the evaluation based on the operator and comparison values.
*/
function evaluate(fieldValue, operator, values) {
const value = values[0];
switch (op) {
switch (operator) {
case "CONTAINS":

@@ -140,3 +122,3 @@ return fieldValue.toLowerCase().includes(value.toLowerCase());

const fieldValueDate = new Date(fieldValue).getTime();
return op === "AFTER"
return operator === "AFTER"
? fieldValueDate > daysAgo.getTime()

@@ -160,3 +142,3 @@ : fieldValueDate < daysAgo.getTime();

default:
console.error(`unknown operator: ${op}`);
console.error(`unknown operator: ${operator}`);
return false;

@@ -166,2 +148,53 @@ }

exports.evaluate = evaluate;
function evaluateRecursively(filter, context, missingContextFieldsSet) {
switch (filter.type) {
case "constant":
return filter.value;
case "context":
if (!(filter.field in context)) {
missingContextFieldsSet.add(filter.field);
return false;
}
return evaluate(context[filter.field], filter.operator, filter.values || []);
case "rolloutPercentage": {
if (!(filter.partialRolloutAttribute in context)) {
missingContextFieldsSet.add(filter.partialRolloutAttribute);
return false;
}
const hashVal = hashInt(`${filter.key}.${context[filter.partialRolloutAttribute]}`);
return hashVal < filter.partialRolloutThreshold;
}
case "group":
return filter.filters.reduce((acc, current) => {
if (filter.operator === "and") {
return (acc &&
evaluateRecursively(current, context, missingContextFieldsSet));
}
return (acc || evaluateRecursively(current, context, missingContextFieldsSet));
}, filter.operator === "and");
case "negation":
return !evaluateRecursively(filter.filter, context, missingContextFieldsSet);
default:
return false;
}
}
function evaluateFeatureRules({ context, featureKey, rules, }) {
const flatContext = flattenJSON(context);
const missingContextFieldsSet = new Set();
const ruleEvaluationResults = rules.map((rule) => evaluateRecursively(rule.filter, flatContext, missingContextFieldsSet));
const missingContextFields = Array.from(missingContextFieldsSet);
const firstMatchedRuleIndex = ruleEvaluationResults.findIndex(Boolean);
const firstMatchedRule = firstMatchedRuleIndex > -1 ? rules[firstMatchedRuleIndex] : undefined;
return {
value: firstMatchedRule === null || firstMatchedRule === void 0 ? void 0 : firstMatchedRule.value,
featureKey,
context: flatContext,
ruleEvaluationResults,
reason: firstMatchedRuleIndex > -1
? `rule #${firstMatchedRuleIndex} matched`
: "no matched rules",
missingContextFields,
};
}
exports.evaluateFeatureRules = evaluateFeatureRules;
//# sourceMappingURL=index.js.map

@@ -0,4 +1,21 @@

/**
* Represents a filter class with a specific type property.
*
* This type is intended to define the structure for objects
* that classify or categorize based on a particular filter type.
*
* Properties:
* - type: Specifies the classification type as a string.
*/
export type FilterClass = {
type: string;
};
/**
* Represents a group of filters that can be combined with a logical operator.
*
* @template T The type of filter class that defines the criteria within the filter group.
* @property type The fixed type indicator for this filter structure, always "group".
* @property operator The logical operator used to combine the filters in the group. It can be either "and" (all conditions must pass) or "or" (at least one condition must pass).
* @property filters An array of filter trees containing individual filters or nested groups of filters.
*/
export type FilterGroup<T extends FilterClass> = {

@@ -9,2 +26,12 @@ type: "group";

};
/**
* Represents a filter negation structure for use within filtering systems.
*
* A `FilterNegation` is used to encapsulate a negation operation,
* which negates the conditions defined in the provided `filter`.
*
* @template T - A generic type that extends FilterClass, indicating the type of the filter.
* @property type - Specifies the type of this filter operation as "negation".
* @property filter - A `FilterTree` structure of type `T` that defines the filter conditions to be negated.
*/
export type FilterNegation<T extends FilterClass> = {

@@ -14,31 +41,70 @@ type: "negation";

};
/**
* Represents a tree structure for filters that can be composed of filter groups,
* filter negations, or individual filter instances of a specified type.
*
* @template T - A type that extends the `FilterClass`.
*/
export type FilterTree<T extends FilterClass> = FilterGroup<T> | FilterNegation<T> | T;
export interface Rule {
filter: RuleFilter;
}
export type FeatureData = {
key: string;
targeting: {
rules: Rule[];
};
};
export interface EvaluateTargetingParams {
context: Record<string, unknown>;
feature: FeatureData;
}
export interface EvaluateTargetingResult {
value: boolean;
feature: FeatureData;
context: Record<string, any>;
ruleEvaluationResults: boolean[];
reason?: string;
missingContextFields?: string[];
}
type ContextFilterOp = "IS" | "IS_NOT" | "ANY_OF" | "NOT_ANY_OF" | "CONTAINS" | "NOT_CONTAINS" | "GT" | "LT" | "AFTER" | "BEFORE" | "SET" | "NOT_SET" | "IS_TRUE" | "IS_FALSE";
export type ContextFilter = {
/**
* Represents a set of predefined operators that can be used to filter a specific context.
* These operators can express various conditions, including equality checks, comparison,
* set membership, and boolean evaluations.
*
* Possible values:
* - "IS": Specifies exact match.
* - "IS_NOT": Specifies a negation of exact match.
* - "ANY_OF": Checks if a value is present in a set of specified values.
* - "NOT_ANY_OF": Checks if a value is not present in a set of specified values.
* - "CONTAINS": Verifies if a value contains a specific substring or element.
* - "NOT_CONTAINS": Verifies if a value does not contain a specific substring or element.
* - "GT": Greater than comparison.
* - "LT": Less than comparison.
* - "AFTER": Compares if a value is after a specified point (e.g., time, rank).
* - "BEFORE": Compares if a value is before a specified point (e.g., time, rank).
* - "SET": Checks if a value is set or exists.
* - "NOT_SET": Checks if a value is not set or does not exist.
* - "IS_TRUE": Checks if a boolean value is true.
* - "IS_FALSE": Checks if a boolean value is false.
*/
type ContextFilterOperator = "IS" | "IS_NOT" | "ANY_OF" | "NOT_ANY_OF" | "CONTAINS" | "NOT_CONTAINS" | "GT" | "LT" | "AFTER" | "BEFORE" | "SET" | "NOT_SET" | "IS_TRUE" | "IS_FALSE";
/**
* Represents a filter configuration used to filter data based on specific context.
*
* This interface defines the structure of a context filter, containing a field,
* an operator, and optional values to control the filtering behavior.
*
* The `type` property must always have the value "context" to classify filters
* of this type.
*
* The `field` property specifies the name of the context field to filter.
*
* The `operator` property defines the filtering operation to perform on the
* specified field (e.g., equals, contains, etc.).
*
* The optional `values` property is an array of strings that lists the values
* to be used in conjunction with the operator for filtering.
*
* This interface is typically utilized in contexts where data needs to be
* dynamically filtered based on specific criteria derived from contextual
* attributes.
*/
export interface ContextFilter {
type: "context";
field: string;
operator: ContextFilterOp;
operator: ContextFilterOperator;
values?: string[];
};
}
/**
* Represents a filter configuration to enable percentage-based rollout of a feature or functionality.
*
* This type defines the necessary parameters to control access to a feature
* by evaluating a specific attribute and applying it against a defined percentage threshold.
*
* Properties:
* - `type` - Indicates the type of the filter. For this filter type, it will always be "rolloutPercentage".
* - `key` - A unique key or identifier that distinguishes this rollout filter.
* - `partialRolloutAttribute` - Specifies the attribute used to evaluate eligibility for the rollout.
* - `partialRolloutThreshold` - A numeric value representing the upper-bound threshold (0-100) for the percentage-based rollout.
*/
export type PercentageRolloutFilter = {

@@ -50,2 +116,11 @@ type: "rolloutPercentage";

};
/**
* Represents a constant filter configuration.
*
* The ConstantFilter type is used to define a filter configuration with a fixed,
* immutable value. It always evaluates to the specified boolean `value`.
*
* @property {string} type - Indicates the type of filter, which is always "constant".
* @property {boolean} value - The fixed boolean value for the filter.
*/
export type ConstantFilter = {

@@ -55,8 +130,107 @@ type: "constant";

};
/**
* A composite type for representing a rule-based filter system.
*
* This type is constructed using a `FilterTree` structure that consists of
* nested filters of the following types:
* - `ContextFilter`: A filter that evaluates based on specified context criteria.
* - `PercentageRolloutFilter`: A filter that performs a percentage-based rollout.
* - `ConstantFilter`: A filter that evaluates based on fixed conditions or constants.
*
* `RuleFilter` is typically used in scenarios where a hierarchical filtering mechanism
* is needed to determine outcomes based on multiple layered conditions.
*/
export type RuleFilter = FilterTree<ContextFilter | PercentageRolloutFilter | ConstantFilter>;
/**
* Represents a value that can be used in a rule configuration.
*
* RuleValue can take on different types, allowing flexibility based on the
* specific rule's requirements. This can include:
* - A boolean value: to represent true/false conditions.
* - A string: typically used for textual or keyword-based rules.
* - A number: for numerical rules or thresholds.
* - An object: for more complex rule definitions or configurations.
*
* This type is useful for accommodating various rule structures in applications
* that work with dynamic or user-defined regulations.
*/
type RuleValue = boolean | string | number | object;
/**
* Represents a rule that defines a filtering criterion and an associated value.
*
* @template T - Specifies the type of the associated value that extends RuleValue.
* @property {RuleFilter} filter - The filtering criterion used by the rule.
* @property {T} value - The value associated with the rule.
*/
export interface Rule<T extends RuleValue> {
filter: RuleFilter;
value: T;
}
/**
* Flattens a nested JSON object into a single-level object, with keys indicating the nesting levels.
* Keys in the resulting object are represented in a dot notation to reflect the nesting structure of the original data.
*
* @param {object} data - The nested JSON object to be flattened.
* @return {Record<string, string>} A flattened JSON object with "stringified" keys and values.
*/
export declare function flattenJSON(data: object): Record<string, string>;
/**
* Converts a flattened JSON object with dot-separated keys into a nested JSON object.
*
* @param {Record<string, any>} data - The flattened JSON object where keys are dot-separated representing nested levels.
* @return {Record<string, any>} The unflattened JSON object with nested structure restored.
*/
export declare function unflattenJSON(data: Record<string, any>): Record<string, any>;
export declare function evaluateTargeting({ context, feature, }: EvaluateTargetingParams): EvaluateTargetingResult;
/**
* Generates a hashed integer based on the input string. The method extracts 20 bits from the hash,
* scales it to a range between 0 and 100000, and returns the resultant integer.
*
* @param {string} hashInput - The input string used to generate the hash.
* @return {number} A number between 0 and 100000 derived from the hash of the input string.
*/
export declare function hashInt(hashInput: string): number;
export declare function evaluate(fieldValue: string, op: ContextFilterOp, values: string[]): boolean;
/**
* Evaluates a field value against a specified operator and comparison values.
*
* @param {string} fieldValue - The value to be evaluated.
* @param {ContextFilterOperator} operator - The operator used for the evaluation (e.g., "CONTAINS", "GT").
* @param {string[]} values - An array of comparison values for evaluation.
* @return {boolean} The result of the evaluation based on the operator and comparison values.
*/
export declare function evaluate(fieldValue: string, operator: ContextFilterOperator, values: string[]): boolean;
/**
* Represents the parameters required for evaluating rules against a specific feature in a given context.
*
* @template T - The type of the rule value used in evaluation.
*
* @property {string} featureKey - The key that identifies the specific feature to be evaluated.
* @property {Rule<T>[]} rules - An array of rules used for evaluation.
* @property {Record<string, unknown>} context - The contextual data used during the evaluation process.
*/
export interface EvaluationParams<T extends RuleValue> {
featureKey: string;
rules: Rule<T>[];
context: Record<string, unknown>;
}
/**
* Represents the result of an evaluation process for a specific feature and its associated rules.
*
* @template T - The type of the rule value being evaluated.
*
* @property {string} featureKey - The unique key identifying the feature being evaluated.
* @property {T | undefined} value - The resolved value of the feature, if the evaluation is successful.
* @property {Record<string, any>} context - The contextual information used during the evaluation process.
* @property {boolean[]} ruleEvaluationResults - Array indicating the success or failure of each rule evaluated.
* @property {string} [reason] - Optional field providing additional explanation regarding the evaluation result.
* @property {string[]} [missingContextFields] - Optional array of context fields that were required but not provided during the evaluation.
*/
export interface EvaluationResult<T extends RuleValue> {
featureKey: string;
value: T | undefined;
context: Record<string, any>;
ruleEvaluationResults: boolean[];
reason?: string;
missingContextFields?: string[];
}
export declare function evaluateFeatureRules<T extends RuleValue>({ context, featureKey, rules, }: EvaluationParams<T>): EvaluationResult<T>;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluate = exports.hashInt = exports.evaluateTargeting = exports.unflattenJSON = exports.flattenJSON = void 0;
exports.evaluateFeatureRules = exports.evaluate = exports.hashInt = exports.unflattenJSON = exports.flattenJSON = void 0;
const node_crypto_1 = require("node:crypto");
/**
* Flattens a nested JSON object into a single-level object, with keys indicating the nesting levels.
* Keys in the resulting object are represented in a dot notation to reflect the nesting structure of the original data.
*
* @param {object} data - The nested JSON object to be flattened.
* @return {Record<string, string>} A flattened JSON object with "stringified" keys and values.
*/
function flattenJSON(data) {

@@ -34,2 +41,8 @@ if (Object.keys(data).length === 0)

exports.flattenJSON = flattenJSON;
/**
* Converts a flattened JSON object with dot-separated keys into a nested JSON object.
*
* @param {Record<string, any>} data - The flattened JSON object where keys are dot-separated representing nested levels.
* @return {Record<string, any>} The unflattened JSON object with nested structure restored.
*/
function unflattenJSON(data) {

@@ -41,3 +54,5 @@ const result = {};

if (index === keys.length - 1) {
acc[key] = data[i];
if (typeof acc === "object") {
acc[key] = data[i];
}
}

@@ -53,18 +68,9 @@ else if (!acc[key]) {

exports.unflattenJSON = unflattenJSON;
function evaluateTargeting({ context, feature, }) {
const flatContext = flattenJSON(context);
const missingContextFieldsSet = new Set();
const ruleEvaluationResults = feature.targeting.rules.map((rule) => evaluateRecursively(rule.filter, flatContext, missingContextFieldsSet));
const missingContextFields = Array.from(missingContextFieldsSet);
const firstIdx = ruleEvaluationResults.findIndex(Boolean);
return {
value: firstIdx > -1,
feature,
context: flatContext,
ruleEvaluationResults,
reason: firstIdx > -1 ? `rule #${firstIdx} matched` : "no matched rules",
missingContextFields,
};
}
exports.evaluateTargeting = evaluateTargeting;
/**
* Generates a hashed integer based on the input string. The method extracts 20 bits from the hash,
* scales it to a range between 0 and 100000, and returns the resultant integer.
*
* @param {string} hashInput - The input string used to generate the hash.
* @return {number} A number between 0 and 100000 derived from the hash of the input string.
*/
function hashInt(hashInput) {

@@ -81,37 +87,13 @@ // 1. hash the key and the partial rollout attribute

exports.hashInt = hashInt;
function evaluateRecursively(filter, context, missingContextFieldsSet) {
switch (filter.type) {
case "constant":
return filter.value;
case "context":
if (!(filter.field in context)) {
missingContextFieldsSet.add(filter.field);
return false;
}
return evaluate(context[filter.field], filter.operator, filter.values || []);
case "rolloutPercentage": {
if (!(filter.partialRolloutAttribute in context)) {
missingContextFieldsSet.add(filter.partialRolloutAttribute);
return false;
}
const hashVal = hashInt(`${filter.key}.${context[filter.partialRolloutAttribute]}`);
return hashVal < filter.partialRolloutThreshold;
}
case "group":
return filter.filters.reduce((acc, current) => {
if (filter.operator === "and") {
return (acc &&
evaluateRecursively(current, context, missingContextFieldsSet));
}
return (acc || evaluateRecursively(current, context, missingContextFieldsSet));
}, filter.operator === "and");
case "negation":
return !evaluateRecursively(filter.filter, context, missingContextFieldsSet);
default:
return false;
}
}
function evaluate(fieldValue, op, values) {
/**
* Evaluates a field value against a specified operator and comparison values.
*
* @param {string} fieldValue - The value to be evaluated.
* @param {ContextFilterOperator} operator - The operator used for the evaluation (e.g., "CONTAINS", "GT").
* @param {string[]} values - An array of comparison values for evaluation.
* @return {boolean} The result of the evaluation based on the operator and comparison values.
*/
function evaluate(fieldValue, operator, values) {
const value = values[0];
switch (op) {
switch (operator) {
case "CONTAINS":

@@ -140,3 +122,3 @@ return fieldValue.toLowerCase().includes(value.toLowerCase());

const fieldValueDate = new Date(fieldValue).getTime();
return op === "AFTER"
return operator === "AFTER"
? fieldValueDate > daysAgo.getTime()

@@ -160,3 +142,3 @@ : fieldValueDate < daysAgo.getTime();

default:
console.error(`unknown operator: ${op}`);
console.error(`unknown operator: ${operator}`);
return false;

@@ -166,2 +148,53 @@ }

exports.evaluate = evaluate;
function evaluateRecursively(filter, context, missingContextFieldsSet) {
switch (filter.type) {
case "constant":
return filter.value;
case "context":
if (!(filter.field in context)) {
missingContextFieldsSet.add(filter.field);
return false;
}
return evaluate(context[filter.field], filter.operator, filter.values || []);
case "rolloutPercentage": {
if (!(filter.partialRolloutAttribute in context)) {
missingContextFieldsSet.add(filter.partialRolloutAttribute);
return false;
}
const hashVal = hashInt(`${filter.key}.${context[filter.partialRolloutAttribute]}`);
return hashVal < filter.partialRolloutThreshold;
}
case "group":
return filter.filters.reduce((acc, current) => {
if (filter.operator === "and") {
return (acc &&
evaluateRecursively(current, context, missingContextFieldsSet));
}
return (acc || evaluateRecursively(current, context, missingContextFieldsSet));
}, filter.operator === "and");
case "negation":
return !evaluateRecursively(filter.filter, context, missingContextFieldsSet);
default:
return false;
}
}
function evaluateFeatureRules({ context, featureKey, rules, }) {
const flatContext = flattenJSON(context);
const missingContextFieldsSet = new Set();
const ruleEvaluationResults = rules.map((rule) => evaluateRecursively(rule.filter, flatContext, missingContextFieldsSet));
const missingContextFields = Array.from(missingContextFieldsSet);
const firstMatchedRuleIndex = ruleEvaluationResults.findIndex(Boolean);
const firstMatchedRule = firstMatchedRuleIndex > -1 ? rules[firstMatchedRuleIndex] : undefined;
return {
value: firstMatchedRule === null || firstMatchedRule === void 0 ? void 0 : firstMatchedRule.value,
featureKey,
context: flatContext,
ruleEvaluationResults,
reason: firstMatchedRuleIndex > -1
? `rule #${firstMatchedRuleIndex} matched`
: "no matched rules",
missingContextFields,
};
}
exports.evaluateFeatureRules = evaluateFeatureRules;
//# sourceMappingURL=index.js.map
{
"name": "@bucketco/flag-evaluation",
"version": "0.0.7",
"version": "0.1.0",
"license": "MIT",

@@ -37,3 +37,3 @@ "repository": {

},
"gitHead": "a4f39af2ab2aa92e134a4f70d12c6abaeec7f591"
"gitHead": "4d51b5dbce8f6f1bb9b6b2b3e12a586512409ed8"
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc