@flag-engine/core 0.0.9 → 0.0.11

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.
@@ -1,3 +1,10 @@
1
1
  import { FlagConfiguration, UserConfiguration } from "./types";
2
+ /**
3
+ * Evaluates a feature flag for a given user configuration.
4
+ *
5
+ * @param flagConfig - The flag configuration to evaluate
6
+ * @param userConfiguration - The user context containing __id and custom fields
7
+ * @returns true if flag is enabled with no variants, variant name if matched, false otherwise
8
+ */
2
9
  export declare const evaluateFlag: (flagConfig: FlagConfiguration, userConfiguration: UserConfiguration) => string | boolean;
3
10
  //# sourceMappingURL=evaluateFlag.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"evaluateFlag.d.ts","sourceRoot":"","sources":["../src/evaluateFlag.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE/D,eAAO,MAAM,YAAY,GACvB,YAAY,iBAAiB,EAC7B,mBAAmB,iBAAiB,qBAgBrC,CAAC"}
1
+ {"version":3,"file":"evaluateFlag.d.ts","sourceRoot":"","sources":["../src/evaluateFlag.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,YAAY,iBAAiB,EAC7B,mBAAmB,iBAAiB,KACnC,MAAM,GAAG,OAiBX,CAAC"}
@@ -1,4 +1,20 @@
1
- import { FlagConfiguration, Rule, UserConfiguration } from "./types";
2
- export declare const isEligibleForStrategy: (rules: Rule[], userConfiguration: UserConfiguration) => boolean;
3
- export declare const getEligibleStrategy: (flagConfig: FlagConfiguration, userConfiguration: UserConfiguration) => import("./types").Strategy | undefined;
1
+ import { FlagConfiguration, Rule, Strategy, UserConfiguration } from "./types";
2
+ /**
3
+ * Evaluates whether a user matches all rules in a strategy.
4
+ * Supports nested segment evaluation with cycle/depth protection.
5
+ *
6
+ * @param rules - Array of rules to evaluate
7
+ * @param userConfiguration - User context with __id and custom fields
8
+ * @param depth - Current recursion depth for cycle protection
9
+ * @returns true if user matches all rules, false otherwise
10
+ */
11
+ export declare const isEligibleForStrategy: (rules: Rule[], userConfiguration: UserConfiguration, depth?: number) => boolean;
12
+ /**
13
+ * Finds the first strategy in a flag configuration that the user is eligible for.
14
+ *
15
+ * @param flagConfig - The flag configuration containing strategies
16
+ * @param userConfiguration - User context with __id and custom fields
17
+ * @returns The first matching strategy, or undefined if none match
18
+ */
19
+ export declare const getEligibleStrategy: (flagConfig: FlagConfiguration, userConfiguration: UserConfiguration) => Strategy | undefined;
4
20
  //# sourceMappingURL=getEligibleStrategy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getEligibleStrategy.d.ts","sourceRoot":"","sources":["../src/getEligibleStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAOrE,eAAO,MAAM,qBAAqB,GAChC,OAAO,IAAI,EAAE,EACb,mBAAmB,iBAAiB,KACnC,OAuEF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,YAAY,iBAAiB,EAC7B,mBAAmB,iBAAiB,2CAKrC,CAAC"}
1
+ {"version":3,"file":"getEligibleStrategy.d.ts","sourceRoot":"","sources":["../src/getEligibleStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAY/E;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,IAAI,EAAE,EACb,mBAAmB,iBAAiB,EACpC,QAAO,MAAU,KAChB,OA6EF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,YAAY,iBAAiB,EAC7B,mBAAmB,iBAAiB,KACnC,QAAQ,GAAG,SAIb,CAAC"}
package/dist/index.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("murmurhash-js");const t=e=>"string"==typeof e,r=e=>null==e,n=(e,a)=>0===e.length||e.every((e=>{if("inSegment"in e)return n(e.inSegment.rules,a);switch(e.operator){case"equals":return a[e.field]===e.value;case"not_equals":return a[e.field]!==e.value;case"greater_than":{const t=a[e.field];return!r(t)&&!r(e.value)&&t>e.value}case"less_than":{const t=a[e.field];return!r(t)&&!r(e.value)&&t<e.value}case"contains":{const r=a[e.field];return t(r)&&t(e.value)&&r.includes(e.value)}case"not_contains":{const r=a[e.field];return t(r)&&t(e.value)&&!r.includes(e.value)}case"in":{const t=a[e.field];return Array.isArray(e.value)&&-1!==e.value.indexOf(t)}case"not_in":{const t=a[e.field];return Array.isArray(e.value)&&-1===e.value.indexOf(t)}default:return!1}})),a=Math.pow(2,32),s=(t,r)=>{if(0===t.strategies.length)return!0;const s=((e,t)=>e.strategies.find((e=>n(e.rules,t))))(t,r);if(!s)return!1;if(0===s.variants.length)return!0;const u=((t,r)=>{t.variants.sort(((e,t)=>e.percent-t.percent));let n=0;const s=`${t.name}-${r}`,u=e.murmur3(s),l=t.variants.find((e=>{const t=u/a*100;if(n+=e.percent,t<=n)return e}));return l||!1})(s,r.__id);return!!u&&u.name};exports.createFlagEngine=e=>({createUserContext:t=>((e,t)=>{let r=t;return{evaluateAll:()=>{const t={};for(const n of e)t[n.key]="enabled"===n.status&&s(n,r);return t},evaluate:t=>{const n=e.find((e=>e.key===t));return!!n&&"disabled"!==n.status&&s(n,r)},setUserConfiguration:e=>{r=e}}})(e,t)});
1
+ "use strict";var e=require("murmurhash-js");const r=e=>"string"==typeof e,t=e=>null==e,n=(e,a,s=0)=>!(s>10)&&(0===e.length||e.every((e=>{if("inSegment"in e)return n(e.inSegment.rules,a,s+1);switch(e.operator){case"equals":return a[e.field]===e.value;case"not_equals":return a[e.field]!==e.value;case"greater_than":{const r=a[e.field];return!t(r)&&!t(e.value)&&r>e.value}case"less_than":{const r=a[e.field];return!t(r)&&!t(e.value)&&r<e.value}case"contains":{const t=a[e.field];return r(t)&&r(e.value)&&t.includes(e.value)}case"not_contains":{const t=a[e.field];return!!r(e.value)&&(!r(t)||!t.includes(e.value))}case"in":{const r=a[e.field];return Array.isArray(e.value)&&-1!==e.value.indexOf(r)}case"not_in":{const r=a[e.field];return!!Array.isArray(e.value)&&-1===e.value.indexOf(r)}default:return!1}}))),a=2**32,s=(r,t)=>{if(0===r.strategies.length)return!0;const s=((e,r)=>e.strategies.find((e=>n(e.rules,r))))(r,t);if(!s)return!1;if(0===s.variants.length)return!0;const u=((r,t)=>{const n=[...r.variants].sort(((e,r)=>e.percent-r.percent)),s=`${r.name}-${t}`,u=e.murmur3(s)/a*100;let l=0;for(const e of n)if(l+=e.percent,u<l)return e;return!1})(s,t.__id);return!1!==u&&u.name};exports.createFlagEngine=e=>({createUserContext:r=>((e,r)=>{let t=r;return{evaluateAll:()=>{const r={};for(const n of e)r[n.key]="enabled"===n.status&&s(n,t);return r},evaluate:r=>{const n=e.find((e=>e.key===r));return!!n&&"enabled"===n.status&&s(n,t)},setUserConfiguration:e=>{t=e}}})(e,r)});
package/dist/index.d.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  import { EvaluationMachine, FlagsConfiguration } from "./types";
2
+ /**
3
+ * Creates a feature flag evaluation engine.
4
+ *
5
+ * @param flagsConfig - Array of flag configurations with strategies and variants
6
+ * @returns An evaluation machine that can create user contexts for flag evaluation
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const engine = createFlagEngine(flagsConfig);
11
+ * const userContext = engine.createUserContext({ __id: 'user-123', plan: 'premium' });
12
+ * const isEnabled = userContext.evaluate('my-feature');
13
+ * ```
14
+ */
2
15
  export declare const createFlagEngine: (flagsConfig: FlagsConfiguration) => EvaluationMachine;
3
16
  export type { FlagsConfiguration, UserConfiguration } from "./types";
4
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAEjB,kBAAkB,EAEnB,MAAM,SAAS,CAAC;AAoCjB,eAAO,MAAM,gBAAgB,GAC3B,aAAa,kBAAkB,KAC9B,iBAKF,CAAC;AAEF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAEjB,kBAAkB,EAGnB,MAAM,SAAS,CAAC;AAoCjB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAC3B,aAAa,kBAAkB,KAC9B,iBAKF,CAAC;AAEF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{murmur3 as e}from"murmurhash-js";const t=e=>"string"==typeof e,n=e=>null==e,r=(e,a)=>0===e.length||e.every((e=>{if("inSegment"in e)return r(e.inSegment.rules,a);switch(e.operator){case"equals":return a[e.field]===e.value;case"not_equals":return a[e.field]!==e.value;case"greater_than":{const t=a[e.field];return!n(t)&&!n(e.value)&&t>e.value}case"less_than":{const t=a[e.field];return!n(t)&&!n(e.value)&&t<e.value}case"contains":{const n=a[e.field];return t(n)&&t(e.value)&&n.includes(e.value)}case"not_contains":{const n=a[e.field];return t(n)&&t(e.value)&&!n.includes(e.value)}case"in":{const t=a[e.field];return Array.isArray(e.value)&&-1!==e.value.indexOf(t)}case"not_in":{const t=a[e.field];return Array.isArray(e.value)&&-1===e.value.indexOf(t)}default:return!1}})),a=Math.pow(2,32),s=(t,n)=>{if(0===t.strategies.length)return!0;const s=((e,t)=>e.strategies.find((e=>r(e.rules,t))))(t,n);if(!s)return!1;if(0===s.variants.length)return!0;const u=((t,n)=>{t.variants.sort(((e,t)=>e.percent-t.percent));let r=0;const s=`${t.name}-${n}`,u=e(s),l=t.variants.find((e=>{const t=u/a*100;if(r+=e.percent,t<=r)return e}));return l||!1})(s,n.__id);return!!u&&u.name},u=e=>({createUserContext:t=>((e,t)=>{let n=t;return{evaluateAll:()=>{const t={};for(const r of e)t[r.key]="enabled"===r.status&&s(r,n);return t},evaluate:t=>{const r=e.find((e=>e.key===t));return!!r&&"disabled"!==r.status&&s(r,n)},setUserConfiguration:e=>{n=e}}})(e,t)});export{u as createFlagEngine};
1
+ import{murmur3 as e}from"murmurhash-js";const t=e=>"string"==typeof e,r=e=>null==e,n=(e,a,s=0)=>!(s>10)&&(0===e.length||e.every((e=>{if("inSegment"in e)return n(e.inSegment.rules,a,s+1);switch(e.operator){case"equals":return a[e.field]===e.value;case"not_equals":return a[e.field]!==e.value;case"greater_than":{const t=a[e.field];return!r(t)&&!r(e.value)&&t>e.value}case"less_than":{const t=a[e.field];return!r(t)&&!r(e.value)&&t<e.value}case"contains":{const r=a[e.field];return t(r)&&t(e.value)&&r.includes(e.value)}case"not_contains":{const r=a[e.field];return!!t(e.value)&&(!t(r)||!r.includes(e.value))}case"in":{const t=a[e.field];return Array.isArray(e.value)&&-1!==e.value.indexOf(t)}case"not_in":{const t=a[e.field];return!!Array.isArray(e.value)&&-1===e.value.indexOf(t)}default:return!1}}))),a=2**32,s=(t,r)=>{if(0===t.strategies.length)return!0;const s=((e,t)=>e.strategies.find((e=>n(e.rules,t))))(t,r);if(!s)return!1;if(0===s.variants.length)return!0;const u=((t,r)=>{const n=[...t.variants].sort(((e,t)=>e.percent-t.percent)),s=`${t.name}-${r}`,u=e(s)/a*100;let l=0;for(const e of n)if(l+=e.percent,u<l)return e;return!1})(s,r.__id);return!1!==u&&u.name},u=e=>({createUserContext:t=>((e,t)=>{let r=t;return{evaluateAll:()=>{const t={};for(const n of e)t[n.key]="enabled"===n.status&&s(n,r);return t},evaluate:t=>{const n=e.find((e=>e.key===t));return!!n&&"enabled"===n.status&&s(n,r)},setUserConfiguration:e=>{r=e}}})(e,t)});export{u as createFlagEngine};
@@ -1,3 +1,12 @@
1
- import { Strategy } from "./types";
2
- export declare const resolveStrategyVariant: (strategy: Strategy, userKey: string) => false | import("./types").Variant;
1
+ import { Strategy, Variant } from "./types";
2
+ /**
3
+ * Resolves which variant a user should see based on consistent hashing.
4
+ * Uses MurmurHash3 to generate a deterministic percentage for the user,
5
+ * then maps that to a variant based on cumulative percentages.
6
+ *
7
+ * @param strategy - The strategy containing variants with percentages
8
+ * @param userKey - Unique identifier for the user
9
+ * @returns The resolved variant, or false if no variant matches
10
+ */
11
+ export declare const resolveStrategyVariant: (strategy: Strategy, userKey: string) => Variant | false;
3
12
  //# sourceMappingURL=resolveStrategyVariant.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveStrategyVariant.d.ts","sourceRoot":"","sources":["../src/resolveStrategyVariant.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAInC,eAAO,MAAM,sBAAsB,GAAI,UAAU,QAAQ,EAAE,SAAS,MAAM,sCAsBzE,CAAC"}
1
+ {"version":3,"file":"resolveStrategyVariant.d.ts","sourceRoot":"","sources":["../src/resolveStrategyVariant.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAI5C;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GACjC,UAAU,QAAQ,EAClB,SAAS,MAAM,KACd,OAAO,GAAG,KAoBZ,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flag-engine/core",
3
3
  "private": false,
4
- "version": "0.0.9",
4
+ "version": "0.0.11",
5
5
  "description": "Feature flags evaluation engine, runtime agnostic",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs.js",
package/vitest.config.ts CHANGED
@@ -3,5 +3,10 @@ import { defineConfig } from "vitest/config";
3
3
  export default defineConfig({
4
4
  test: {
5
5
  testTimeout: 15000,
6
+ coverage: {
7
+ provider: "v8",
8
+ reporter: ["text", "html", "lcov"],
9
+ exclude: ["**/__tests__/**", "**/*.test.ts", "vitest.config.ts"],
10
+ },
6
11
  },
7
12
  });