@contractspec/lib.feature-flags 0.0.0-canary-20260113162409
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/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/contracts/index.d.ts +1010 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +908 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/src/app-config/lifecycle.d.ts +1 -0
- package/dist/contracts/src/app-config/runtime.d.ts +13 -0
- package/dist/contracts/src/app-config/spec.d.ts +9 -0
- package/dist/contracts/src/app-config/validation.d.ts +8 -0
- package/dist/contracts/src/capabilities/capabilities.d.ts +33 -0
- package/dist/contracts/src/capabilities/capabilities.d.ts.map +1 -0
- package/dist/contracts/src/capabilities/index.d.ts +2 -0
- package/dist/contracts/src/capabilities/openbanking.d.ts +1 -0
- package/dist/contracts/src/client/index.d.ts +1 -0
- package/dist/contracts/src/client/react/drivers/rn-reusables.d.ts +1 -0
- package/dist/contracts/src/client/react/drivers/shadcn.d.ts +1 -0
- package/dist/contracts/src/client/react/feature-render.d.ts +3 -0
- package/dist/contracts/src/client/react/form-render.d.ts +4 -0
- package/dist/contracts/src/client/react/index.d.ts +4 -0
- package/dist/contracts/src/contract-registry/index.d.ts +2 -0
- package/dist/contracts/src/contract-registry/schemas.d.ts +2 -0
- package/dist/contracts/src/contract-registry/types.d.ts +1 -0
- package/dist/contracts/src/data-views/index.d.ts +3 -0
- package/dist/contracts/src/data-views/registry.d.ts +2 -0
- package/dist/contracts/src/data-views/spec.d.ts +2 -0
- package/dist/contracts/src/data-views/types.d.ts +2 -0
- package/dist/contracts/src/docs/accessibility_wcag_compliance_specs.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/index.d.ts +25 -0
- package/dist/contracts/src/docs/presentations.d.ts +2 -0
- package/dist/contracts/src/docs/registry.d.ts +10 -0
- package/dist/contracts/src/docs/registry.d.ts.map +1 -0
- package/dist/contracts/src/docs/tech/auth/better-auth-nextjs.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/contracts/README.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/contracts/openapi-export.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/contracts/openapi-import.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/lifecycle-stage-system.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/llm/llm-integration.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/mcp-endpoints.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/presentation-runtime.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/schema/README.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/learning-events.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/learning-journeys.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/platform-admin-panel.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/project-access-teams.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/project-routing.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/sandbox-unlogged.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/team-invitations.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/workspace-ops.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/studio/workspaces.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/telemetry-ingest.docblock.d.ts +1 -0
- package/dist/contracts/src/docs/tech/vscode-extension.docblock.d.ts +1 -0
- package/dist/contracts/src/events.d.ts +25 -0
- package/dist/contracts/src/events.d.ts.map +1 -0
- package/dist/contracts/src/examples/index.d.ts +4 -0
- package/dist/contracts/src/examples/registry.d.ts +2 -0
- package/dist/contracts/src/examples/schema.d.ts +2 -0
- package/dist/contracts/src/examples/types.d.ts +3 -0
- package/dist/contracts/src/examples/validation.d.ts +1 -0
- package/dist/contracts/src/experiments/evaluator.d.ts +2 -0
- package/dist/contracts/src/experiments/spec-resolver.d.ts +4 -0
- package/dist/contracts/src/experiments/spec.d.ts +13 -0
- package/dist/contracts/src/experiments/spec.d.ts.map +1 -0
- package/dist/contracts/src/features/index.d.ts +4 -0
- package/dist/contracts/src/features/install.d.ts +6 -0
- package/dist/contracts/src/features/registry.d.ts +2 -0
- package/dist/contracts/src/features/types.d.ts +86 -0
- package/dist/contracts/src/features/types.d.ts.map +1 -0
- package/dist/contracts/src/features/validation.d.ts +2 -0
- package/dist/contracts/src/forms/forms.d.ts +3 -0
- package/dist/contracts/src/forms/index.d.ts +1 -0
- package/dist/contracts/src/index.d.ts +51 -0
- package/dist/contracts/src/install.d.ts +7 -0
- package/dist/contracts/src/integrations/connection.d.ts +1 -0
- package/dist/contracts/src/integrations/index.d.ts +6 -0
- package/dist/contracts/src/integrations/openbanking/contracts/accounts.d.ts +4 -0
- package/dist/contracts/src/integrations/openbanking/contracts/balances.d.ts +4 -0
- package/dist/contracts/src/integrations/openbanking/contracts/index.d.ts +5 -0
- package/dist/contracts/src/integrations/openbanking/contracts/transactions.d.ts +4 -0
- package/dist/contracts/src/integrations/openbanking/guards.d.ts +1 -0
- package/dist/contracts/src/integrations/openbanking/models.d.ts +1 -0
- package/dist/contracts/src/integrations/openbanking/openbanking.feature.d.ts +1 -0
- package/dist/contracts/src/integrations/operations.d.ts +4 -0
- package/dist/contracts/src/integrations/providers/elevenlabs.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/gcs-storage.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/gmail.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/google-calendar.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/index.d.ts +11 -0
- package/dist/contracts/src/integrations/providers/mistral.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/postmark.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/powens.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/qdrant.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/registry.d.ts +1 -0
- package/dist/contracts/src/integrations/providers/stripe.d.ts +2 -0
- package/dist/contracts/src/integrations/providers/twilio-sms.d.ts +2 -0
- package/dist/contracts/src/integrations/spec.d.ts +3 -0
- package/dist/contracts/src/jsonschema.d.ts +4 -0
- package/dist/contracts/src/knowledge/index.d.ts +2 -0
- package/dist/contracts/src/knowledge/operations.d.ts +4 -0
- package/dist/contracts/src/knowledge/spaces/email-threads.d.ts +1 -0
- package/dist/contracts/src/knowledge/spaces/financial-docs.d.ts +1 -0
- package/dist/contracts/src/knowledge/spaces/financial-overview.d.ts +1 -0
- package/dist/contracts/src/knowledge/spaces/index.d.ts +6 -0
- package/dist/contracts/src/knowledge/spaces/product-canon.d.ts +1 -0
- package/dist/contracts/src/knowledge/spaces/support-faq.d.ts +1 -0
- package/dist/contracts/src/knowledge/spaces/uploaded-docs.d.ts +1 -0
- package/dist/contracts/src/knowledge/spec.d.ts +3 -0
- package/dist/contracts/src/llm/exporters.d.ts +7 -0
- package/dist/contracts/src/llm/index.d.ts +3 -0
- package/dist/contracts/src/llm/prompts.d.ts +2 -0
- package/dist/contracts/src/llm/types.d.ts +5 -0
- package/dist/contracts/src/migrations.d.ts +1 -0
- package/dist/contracts/src/model-registry.d.ts +1 -0
- package/dist/contracts/src/onboarding-base.d.ts +2 -0
- package/dist/contracts/src/openapi.d.ts +1 -0
- package/dist/contracts/src/operations/index.d.ts +2 -0
- package/dist/contracts/src/operations/operation.d.ts +170 -0
- package/dist/contracts/src/operations/operation.d.ts.map +1 -0
- package/dist/contracts/src/operations/registry.d.ts +6 -0
- package/dist/contracts/src/ownership.d.ts +61 -0
- package/dist/contracts/src/ownership.d.ts.map +1 -0
- package/dist/contracts/src/policy/engine.d.ts +3 -0
- package/dist/contracts/src/policy/index.d.ts +4 -0
- package/dist/contracts/src/policy/opa-adapter.d.ts +3 -0
- package/dist/contracts/src/policy/registry.d.ts +2 -0
- package/dist/contracts/src/policy/spec.d.ts +11 -0
- package/dist/contracts/src/policy/spec.d.ts.map +1 -0
- package/dist/contracts/src/presentations/index.d.ts +3 -0
- package/dist/contracts/src/presentations/presentations.d.ts +9 -0
- package/dist/contracts/src/presentations/presentations.d.ts.map +1 -0
- package/dist/contracts/src/presentations/registry.d.ts +2 -0
- package/dist/contracts/src/presentations/transform-engine.d.ts +2 -0
- package/dist/contracts/src/prompt.d.ts +2 -0
- package/dist/contracts/src/promptRegistry.d.ts +2 -0
- package/dist/contracts/src/regenerator/adapters.d.ts +1 -0
- package/dist/contracts/src/regenerator/executor.d.ts +1 -0
- package/dist/contracts/src/regenerator/index.d.ts +6 -0
- package/dist/contracts/src/regenerator/service.d.ts +2 -0
- package/dist/contracts/src/regenerator/sinks.d.ts +2 -0
- package/dist/contracts/src/regenerator/types.d.ts +3 -0
- package/dist/contracts/src/regenerator/utils.d.ts +1 -0
- package/dist/contracts/src/registry-utils.d.ts +1 -0
- package/dist/contracts/src/registry.d.ts +3 -0
- package/dist/contracts/src/resources.d.ts +19 -0
- package/dist/contracts/src/resources.d.ts.map +1 -0
- package/dist/contracts/src/schema-to-markdown.d.ts +1 -0
- package/dist/contracts/src/server/graphql-pothos.d.ts +7 -0
- package/dist/contracts/src/server/index.d.ts +7 -0
- package/dist/contracts/src/server/mcp/createMcpServer.d.ts +5 -0
- package/dist/contracts/src/server/mcp/mcpTypes.d.ts +2 -0
- package/dist/contracts/src/server/provider-mcp.d.ts +1 -0
- package/dist/contracts/src/server/rest-elysia.d.ts +3 -0
- package/dist/contracts/src/server/rest-express.d.ts +3 -0
- package/dist/contracts/src/server/rest-generic.d.ts +2 -0
- package/dist/contracts/src/server/rest-next-app.d.ts +3 -0
- package/dist/contracts/src/server/rest-next-pages.d.ts +3 -0
- package/dist/contracts/src/telemetry/anomaly.d.ts +2 -0
- package/dist/contracts/src/telemetry/index.d.ts +3 -0
- package/dist/contracts/src/telemetry/spec.d.ts +2 -0
- package/dist/contracts/src/telemetry/tracker.d.ts +2 -0
- package/dist/contracts/src/tests/index.d.ts +2 -0
- package/dist/contracts/src/tests/runner.d.ts +3 -0
- package/dist/contracts/src/tests/spec.d.ts +11 -0
- package/dist/contracts/src/tests/spec.d.ts.map +1 -0
- package/dist/contracts/src/themes.d.ts +2 -0
- package/dist/contracts/src/types.d.ts +5 -0
- package/dist/contracts/src/workflow/adapters/db-adapter.d.ts +1 -0
- package/dist/contracts/src/workflow/adapters/file-adapter.d.ts +1 -0
- package/dist/contracts/src/workflow/adapters/index.d.ts +3 -0
- package/dist/contracts/src/workflow/adapters/memory-store.d.ts +1 -0
- package/dist/contracts/src/workflow/index.d.ts +5 -0
- package/dist/contracts/src/workflow/overview.docblock.d.ts +1 -0
- package/dist/contracts/src/workflow/runner.d.ts +5 -0
- package/dist/contracts/src/workflow/spec.d.ts +5 -0
- package/dist/contracts/src/workflow/state.d.ts +1 -0
- package/dist/contracts/src/workflow/validation.d.ts +3 -0
- package/dist/contracts/src/workspace-config/contractsrc-schema.d.ts +1 -0
- package/dist/contracts/src/workspace-config/index.d.ts +1 -0
- package/dist/contracts/src/workspace-config/workspace-config.docblock.d.ts +1 -0
- package/dist/docs/feature-flags.docblock.d.ts +1 -0
- package/dist/docs/feature-flags.docblock.js +76 -0
- package/dist/docs/feature-flags.docblock.js.map +1 -0
- package/dist/docs/index.d.ts +1 -0
- package/dist/docs/index.js +1 -0
- package/dist/entities/index.d.ts +201 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +325 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/evaluation/index.d.ts +163 -0
- package/dist/evaluation/index.d.ts.map +1 -0
- package/dist/evaluation/index.js +221 -0
- package/dist/evaluation/index.js.map +1 -0
- package/dist/events.d.ts +628 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +536 -0
- package/dist/events.js.map +1 -0
- package/dist/feature-flags.capability.d.ts +9 -0
- package/dist/feature-flags.capability.d.ts.map +1 -0
- package/dist/feature-flags.capability.js +25 -0
- package/dist/feature-flags.capability.js.map +1 -0
- package/dist/feature-flags.feature.d.ts +12 -0
- package/dist/feature-flags.feature.d.ts.map +1 -0
- package/dist/feature-flags.feature.js +148 -0
- package/dist/feature-flags.feature.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -0
- package/dist/schema/src/EnumType.d.ts +36 -0
- package/dist/schema/src/EnumType.d.ts.map +1 -0
- package/dist/schema/src/FieldType.d.ts +30 -0
- package/dist/schema/src/FieldType.d.ts.map +1 -0
- package/dist/schema/src/GraphQLSchemaType.d.ts +2 -0
- package/dist/schema/src/JsonSchemaType.d.ts +2 -0
- package/dist/schema/src/ScalarTypeEnum.d.ts +1 -0
- package/dist/schema/src/SchemaModel.d.ts +70 -0
- package/dist/schema/src/SchemaModel.d.ts.map +1 -0
- package/dist/schema/src/SchemaModelType.d.ts +38 -0
- package/dist/schema/src/SchemaModelType.d.ts.map +1 -0
- package/dist/schema/src/ZodSchemaType.d.ts +2 -0
- package/dist/schema/src/entity/defineEntity.d.ts +1 -0
- package/dist/schema/src/entity/generator.d.ts +1 -0
- package/dist/schema/src/entity/index.d.ts +3 -0
- package/dist/schema/src/entity/types.d.ts +146 -0
- package/dist/schema/src/entity/types.d.ts.map +1 -0
- package/dist/schema/src/index.d.ts +10 -0
- package/package.json +74 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
//#region src/evaluation/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Simple hash function for consistent bucketing.
|
|
4
|
+
* Uses a deterministic algorithm so the same input always produces the same bucket.
|
|
5
|
+
*/
|
|
6
|
+
function hashToBucket(value, seed = "") {
|
|
7
|
+
const input = `${seed}:${value}`;
|
|
8
|
+
let hash = 0;
|
|
9
|
+
for (let i = 0; i < input.length; i++) {
|
|
10
|
+
const char = input.charCodeAt(i);
|
|
11
|
+
hash = (hash << 5) - hash + char;
|
|
12
|
+
hash = hash & hash;
|
|
13
|
+
}
|
|
14
|
+
return Math.abs(hash % 100);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get subject identifier from context for consistent hashing.
|
|
18
|
+
*/
|
|
19
|
+
function getSubjectId(context) {
|
|
20
|
+
return context.userId || context.sessionId || context.orgId || "anonymous";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Evaluate a single targeting rule condition.
|
|
24
|
+
*/
|
|
25
|
+
function evaluateRuleCondition(rule, context) {
|
|
26
|
+
const attributeValue = getAttributeValue(rule.attribute, context);
|
|
27
|
+
switch (rule.operator) {
|
|
28
|
+
case "EQ": return attributeValue === rule.value;
|
|
29
|
+
case "NEQ": return attributeValue !== rule.value;
|
|
30
|
+
case "IN":
|
|
31
|
+
if (!Array.isArray(rule.value)) return false;
|
|
32
|
+
return rule.value.includes(attributeValue);
|
|
33
|
+
case "NIN":
|
|
34
|
+
if (!Array.isArray(rule.value)) return true;
|
|
35
|
+
return !rule.value.includes(attributeValue);
|
|
36
|
+
case "CONTAINS":
|
|
37
|
+
if (typeof attributeValue !== "string" || typeof rule.value !== "string") return false;
|
|
38
|
+
return attributeValue.includes(rule.value);
|
|
39
|
+
case "NOT_CONTAINS":
|
|
40
|
+
if (typeof attributeValue !== "string" || typeof rule.value !== "string") return true;
|
|
41
|
+
return !attributeValue.includes(rule.value);
|
|
42
|
+
case "GT":
|
|
43
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number") return false;
|
|
44
|
+
return attributeValue > rule.value;
|
|
45
|
+
case "GTE":
|
|
46
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number") return false;
|
|
47
|
+
return attributeValue >= rule.value;
|
|
48
|
+
case "LT":
|
|
49
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number") return false;
|
|
50
|
+
return attributeValue < rule.value;
|
|
51
|
+
case "LTE":
|
|
52
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number") return false;
|
|
53
|
+
return attributeValue <= rule.value;
|
|
54
|
+
case "PERCENTAGE": return hashToBucket(getSubjectId(context), rule.attribute) < (typeof rule.value === "number" ? rule.value : 0);
|
|
55
|
+
default: return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get attribute value from context.
|
|
60
|
+
*/
|
|
61
|
+
function getAttributeValue(attribute, context) {
|
|
62
|
+
switch (attribute) {
|
|
63
|
+
case "userId": return context.userId;
|
|
64
|
+
case "orgId": return context.orgId;
|
|
65
|
+
case "plan": return context.plan;
|
|
66
|
+
case "segment": return context.segment;
|
|
67
|
+
case "sessionId": return context.sessionId;
|
|
68
|
+
default: return context.attributes?.[attribute];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Feature flag evaluator.
|
|
73
|
+
*
|
|
74
|
+
* Evaluates flags based on:
|
|
75
|
+
* 1. Flag status (OFF/ON/GRADUAL)
|
|
76
|
+
* 2. Targeting rules (in priority order)
|
|
77
|
+
* 3. Experiments (if running)
|
|
78
|
+
* 4. Default value (fallback)
|
|
79
|
+
*/
|
|
80
|
+
var FlagEvaluator = class {
|
|
81
|
+
repository;
|
|
82
|
+
logger;
|
|
83
|
+
logEvaluations;
|
|
84
|
+
constructor(options) {
|
|
85
|
+
this.repository = options.repository;
|
|
86
|
+
this.logger = options.logger;
|
|
87
|
+
this.logEvaluations = options.logEvaluations ?? false;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Evaluate a feature flag.
|
|
91
|
+
*/
|
|
92
|
+
async evaluate(key, context) {
|
|
93
|
+
const orgId = context.orgId;
|
|
94
|
+
const flag = await this.repository.getFlag(key, orgId);
|
|
95
|
+
if (!flag) return this.makeResult(false, "FLAG_NOT_FOUND");
|
|
96
|
+
if (flag.status === "OFF") return this.logAndReturn(flag, context, this.makeResult(false, "FLAG_OFF"));
|
|
97
|
+
if (flag.status === "ON") return this.logAndReturn(flag, context, this.makeResult(true, "FLAG_ON"));
|
|
98
|
+
const sortedRules = [...await this.repository.getRules(flag.id)].filter((r) => r.enabled).sort((a, b) => a.priority - b.priority);
|
|
99
|
+
for (const rule of sortedRules) if (evaluateRuleCondition(rule, context)) {
|
|
100
|
+
if (rule.rolloutPercentage !== void 0 && rule.rolloutPercentage !== null) {
|
|
101
|
+
if (hashToBucket(getSubjectId(context), flag.key) >= rule.rolloutPercentage) continue;
|
|
102
|
+
}
|
|
103
|
+
const enabled = rule.serveValue ?? true;
|
|
104
|
+
return this.logAndReturn(flag, context, this.makeResult(enabled, "RULE_MATCH", rule.serveVariant, rule.id));
|
|
105
|
+
}
|
|
106
|
+
const experiment = await this.repository.getActiveExperiment(flag.id);
|
|
107
|
+
if (experiment && experiment.status === "RUNNING") {
|
|
108
|
+
const result = await this.evaluateExperiment(experiment, context);
|
|
109
|
+
if (result) return this.logAndReturn(flag, context, result);
|
|
110
|
+
}
|
|
111
|
+
return this.logAndReturn(flag, context, this.makeResult(flag.defaultValue, "DEFAULT_VALUE"));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Evaluate experiment and assign variant.
|
|
115
|
+
*/
|
|
116
|
+
async evaluateExperiment(experiment, context) {
|
|
117
|
+
const subjectId = getSubjectId(context);
|
|
118
|
+
const subjectType = context.userId ? "user" : context.orgId ? "org" : "session";
|
|
119
|
+
if (hashToBucket(subjectId, `${experiment.key}:audience`) >= experiment.audiencePercentage) return null;
|
|
120
|
+
let variant = await this.repository.getExperimentAssignment(experiment.id, subjectType, subjectId);
|
|
121
|
+
if (!variant) {
|
|
122
|
+
const variantBucket = hashToBucket(subjectId, experiment.key);
|
|
123
|
+
variant = this.assignVariant(experiment.variants, variantBucket);
|
|
124
|
+
await this.repository.saveExperimentAssignment(experiment.id, subjectType, subjectId, variant, variantBucket);
|
|
125
|
+
}
|
|
126
|
+
const enabled = variant !== "control";
|
|
127
|
+
return this.makeResult(enabled, "EXPERIMENT_VARIANT", variant, void 0, experiment.id);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Assign a variant based on bucket and variant percentages.
|
|
131
|
+
*/
|
|
132
|
+
assignVariant(variants, bucket) {
|
|
133
|
+
let cumulative = 0;
|
|
134
|
+
for (const variant of variants) {
|
|
135
|
+
cumulative += variant.percentage;
|
|
136
|
+
if (bucket < cumulative) return variant.key;
|
|
137
|
+
}
|
|
138
|
+
return variants[variants.length - 1]?.key ?? "control";
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create evaluation result.
|
|
142
|
+
*/
|
|
143
|
+
makeResult(enabled, reason, variant, ruleId, experimentId) {
|
|
144
|
+
return {
|
|
145
|
+
enabled,
|
|
146
|
+
variant,
|
|
147
|
+
reason,
|
|
148
|
+
ruleId,
|
|
149
|
+
experimentId
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Log evaluation and return result.
|
|
154
|
+
*/
|
|
155
|
+
logAndReturn(flag, context, result) {
|
|
156
|
+
if (this.logEvaluations && this.logger) {
|
|
157
|
+
const subjectId = getSubjectId(context);
|
|
158
|
+
const subjectType = context.userId ? "user" : context.orgId ? "org" : "session";
|
|
159
|
+
this.logger.log({
|
|
160
|
+
flagId: flag.id,
|
|
161
|
+
flagKey: flag.key,
|
|
162
|
+
subjectType,
|
|
163
|
+
subjectId,
|
|
164
|
+
result: result.enabled,
|
|
165
|
+
variant: result.variant,
|
|
166
|
+
reason: result.reason,
|
|
167
|
+
ruleId: result.ruleId,
|
|
168
|
+
experimentId: result.experimentId,
|
|
169
|
+
context
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* In-memory flag repository for testing and development.
|
|
177
|
+
*/
|
|
178
|
+
var InMemoryFlagRepository = class {
|
|
179
|
+
flags = /* @__PURE__ */ new Map();
|
|
180
|
+
rules = /* @__PURE__ */ new Map();
|
|
181
|
+
experiments = /* @__PURE__ */ new Map();
|
|
182
|
+
assignments = /* @__PURE__ */ new Map();
|
|
183
|
+
addFlag(flag) {
|
|
184
|
+
this.flags.set(flag.key, flag);
|
|
185
|
+
}
|
|
186
|
+
addRule(flagId, rule) {
|
|
187
|
+
const existing = this.rules.get(flagId) || [];
|
|
188
|
+
existing.push(rule);
|
|
189
|
+
this.rules.set(flagId, existing);
|
|
190
|
+
}
|
|
191
|
+
addExperiment(experiment, flagId) {
|
|
192
|
+
this.experiments.set(flagId, experiment);
|
|
193
|
+
}
|
|
194
|
+
async getFlag(key) {
|
|
195
|
+
return this.flags.get(key) || null;
|
|
196
|
+
}
|
|
197
|
+
async getRules(flagId) {
|
|
198
|
+
return this.rules.get(flagId) || [];
|
|
199
|
+
}
|
|
200
|
+
async getActiveExperiment(flagId) {
|
|
201
|
+
return this.experiments.get(flagId) || null;
|
|
202
|
+
}
|
|
203
|
+
async getExperimentAssignment(experimentId, subjectType, subjectId) {
|
|
204
|
+
const key = `${experimentId}:${subjectType}:${subjectId}`;
|
|
205
|
+
return this.assignments.get(key) || null;
|
|
206
|
+
}
|
|
207
|
+
async saveExperimentAssignment(experimentId, subjectType, subjectId, variant) {
|
|
208
|
+
const key = `${experimentId}:${subjectType}:${subjectId}`;
|
|
209
|
+
this.assignments.set(key, variant);
|
|
210
|
+
}
|
|
211
|
+
clear() {
|
|
212
|
+
this.flags.clear();
|
|
213
|
+
this.rules.clear();
|
|
214
|
+
this.experiments.clear();
|
|
215
|
+
this.assignments.clear();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
//#endregion
|
|
220
|
+
export { FlagEvaluator, InMemoryFlagRepository, evaluateRuleCondition, getSubjectId, hashToBucket };
|
|
221
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/evaluation/index.ts"],"sourcesContent":["/**\n * Feature flag evaluation engine.\n *\n * Provides deterministic evaluation of feature flags based on targeting rules\n * and experiment assignments.\n */\n\n// ============ Types ============\n\nexport interface EvaluationContext {\n /** User identifier */\n userId?: string;\n /** Organization identifier */\n orgId?: string;\n /** User's plan (free, pro, enterprise, etc.) */\n plan?: string;\n /** User segment or cohort */\n segment?: string;\n /** Session identifier for anonymous users */\n sessionId?: string;\n /** Additional custom attributes */\n attributes?: Record<string, unknown>;\n}\n\nexport interface FeatureFlag {\n id: string;\n key: string;\n status: 'OFF' | 'ON' | 'GRADUAL';\n defaultValue: boolean;\n variants?: VariantConfig[];\n}\n\nexport interface TargetingRule {\n id: string;\n priority: number;\n enabled: boolean;\n attribute: string;\n operator: RuleOperator;\n value: unknown;\n rolloutPercentage?: number;\n serveValue?: boolean;\n serveVariant?: string;\n}\n\nexport interface Experiment {\n id: string;\n key: string;\n status: 'DRAFT' | 'RUNNING' | 'PAUSED' | 'COMPLETED' | 'CANCELLED';\n variants: ExperimentVariant[];\n audiencePercentage: number;\n audienceFilter?: Record<string, unknown>;\n}\n\nexport interface VariantConfig {\n key: string;\n name: string;\n description?: string;\n weight?: number;\n}\n\nexport interface ExperimentVariant {\n key: string;\n name: string;\n percentage: number;\n}\n\nexport interface EvaluationResult {\n enabled: boolean;\n variant?: string;\n reason: EvaluationReason;\n ruleId?: string;\n experimentId?: string;\n}\n\nexport type EvaluationReason =\n | 'FLAG_OFF'\n | 'FLAG_ON'\n | 'DEFAULT_VALUE'\n | 'RULE_MATCH'\n | 'PERCENTAGE_ROLLOUT'\n | 'EXPERIMENT_VARIANT'\n | 'FLAG_NOT_FOUND';\n\nexport type RuleOperator =\n | 'EQ'\n | 'NEQ'\n | 'IN'\n | 'NIN'\n | 'CONTAINS'\n | 'NOT_CONTAINS'\n | 'GT'\n | 'GTE'\n | 'LT'\n | 'LTE'\n | 'PERCENTAGE';\n\n// ============ Hashing ============\n\n/**\n * Simple hash function for consistent bucketing.\n * Uses a deterministic algorithm so the same input always produces the same bucket.\n */\nexport function hashToBucket(value: string, seed = ''): number {\n const input = `${seed}:${value}`;\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n // Return a number between 0 and 99\n return Math.abs(hash % 100);\n}\n\n/**\n * Get subject identifier from context for consistent hashing.\n */\nexport function getSubjectId(context: EvaluationContext): string {\n return context.userId || context.sessionId || context.orgId || 'anonymous';\n}\n\n// ============ Rule Evaluation ============\n\n/**\n * Evaluate a single targeting rule condition.\n */\nexport function evaluateRuleCondition(\n rule: TargetingRule,\n context: EvaluationContext\n): boolean {\n const attributeValue = getAttributeValue(rule.attribute, context);\n\n switch (rule.operator) {\n case 'EQ':\n return attributeValue === rule.value;\n\n case 'NEQ':\n return attributeValue !== rule.value;\n\n case 'IN':\n if (!Array.isArray(rule.value)) return false;\n return rule.value.includes(attributeValue);\n\n case 'NIN':\n if (!Array.isArray(rule.value)) return true;\n return !rule.value.includes(attributeValue);\n\n case 'CONTAINS':\n if (typeof attributeValue !== 'string' || typeof rule.value !== 'string')\n return false;\n return attributeValue.includes(rule.value);\n\n case 'NOT_CONTAINS':\n if (typeof attributeValue !== 'string' || typeof rule.value !== 'string')\n return true;\n return !attributeValue.includes(rule.value);\n\n case 'GT':\n if (typeof attributeValue !== 'number' || typeof rule.value !== 'number')\n return false;\n return attributeValue > rule.value;\n\n case 'GTE':\n if (typeof attributeValue !== 'number' || typeof rule.value !== 'number')\n return false;\n return attributeValue >= rule.value;\n\n case 'LT':\n if (typeof attributeValue !== 'number' || typeof rule.value !== 'number')\n return false;\n return attributeValue < rule.value;\n\n case 'LTE':\n if (typeof attributeValue !== 'number' || typeof rule.value !== 'number')\n return false;\n return attributeValue <= rule.value;\n\n case 'PERCENTAGE':\n // Percentage-based targeting uses consistent hashing\n return (\n hashToBucket(getSubjectId(context), rule.attribute) <\n (typeof rule.value === 'number' ? rule.value : 0)\n );\n\n default:\n return false;\n }\n}\n\n/**\n * Get attribute value from context.\n */\nfunction getAttributeValue(\n attribute: string,\n context: EvaluationContext\n): unknown {\n switch (attribute) {\n case 'userId':\n return context.userId;\n case 'orgId':\n return context.orgId;\n case 'plan':\n return context.plan;\n case 'segment':\n return context.segment;\n case 'sessionId':\n return context.sessionId;\n default:\n return context.attributes?.[attribute];\n }\n}\n\n// ============ Flag Evaluator ============\n\nexport interface FlagRepository {\n getFlag(key: string, orgId?: string): Promise<FeatureFlag | null>;\n getRules(flagId: string): Promise<TargetingRule[]>;\n getActiveExperiment(flagId: string): Promise<Experiment | null>;\n getExperimentAssignment(\n experimentId: string,\n subjectType: string,\n subjectId: string\n ): Promise<string | null>;\n saveExperimentAssignment(\n experimentId: string,\n subjectType: string,\n subjectId: string,\n variant: string,\n bucket: number\n ): Promise<void>;\n}\n\nexport interface EvaluationLogger {\n log(evaluation: {\n flagId: string;\n flagKey: string;\n subjectType: string;\n subjectId: string;\n result: boolean;\n variant?: string;\n reason: string;\n ruleId?: string;\n experimentId?: string;\n context?: EvaluationContext;\n }): void;\n}\n\nexport interface FlagEvaluatorOptions {\n repository: FlagRepository;\n logger?: EvaluationLogger;\n /** Whether to log evaluations (default: false for performance) */\n logEvaluations?: boolean;\n}\n\n/**\n * Feature flag evaluator.\n *\n * Evaluates flags based on:\n * 1. Flag status (OFF/ON/GRADUAL)\n * 2. Targeting rules (in priority order)\n * 3. Experiments (if running)\n * 4. Default value (fallback)\n */\nexport class FlagEvaluator {\n private repository: FlagRepository;\n private logger?: EvaluationLogger;\n private logEvaluations: boolean;\n\n constructor(options: FlagEvaluatorOptions) {\n this.repository = options.repository;\n this.logger = options.logger;\n this.logEvaluations = options.logEvaluations ?? false;\n }\n\n /**\n * Evaluate a feature flag.\n */\n async evaluate(\n key: string,\n context: EvaluationContext\n ): Promise<EvaluationResult> {\n const orgId = context.orgId;\n const flag = await this.repository.getFlag(key, orgId);\n\n if (!flag) {\n return this.makeResult(false, 'FLAG_NOT_FOUND');\n }\n\n // Check flag status\n if (flag.status === 'OFF') {\n return this.logAndReturn(\n flag,\n context,\n this.makeResult(false, 'FLAG_OFF')\n );\n }\n\n if (flag.status === 'ON') {\n return this.logAndReturn(flag, context, this.makeResult(true, 'FLAG_ON'));\n }\n\n // Status is GRADUAL - evaluate rules and experiments\n const rules = await this.repository.getRules(flag.id);\n\n // Sort rules by priority (lower = higher priority)\n const sortedRules = [...rules]\n .filter((r) => r.enabled)\n .sort((a, b) => a.priority - b.priority);\n\n // Evaluate rules in order\n for (const rule of sortedRules) {\n if (evaluateRuleCondition(rule, context)) {\n // Rule matched - check for percentage rollout\n if (\n rule.rolloutPercentage !== undefined &&\n rule.rolloutPercentage !== null\n ) {\n const bucket = hashToBucket(getSubjectId(context), flag.key);\n if (bucket >= rule.rolloutPercentage) {\n continue; // User not in rollout percentage, try next rule\n }\n }\n\n const enabled = rule.serveValue ?? true;\n return this.logAndReturn(\n flag,\n context,\n this.makeResult(enabled, 'RULE_MATCH', rule.serveVariant, rule.id)\n );\n }\n }\n\n // Check for active experiment\n const experiment = await this.repository.getActiveExperiment(flag.id);\n if (experiment && experiment.status === 'RUNNING') {\n const result = await this.evaluateExperiment(experiment, context);\n if (result) {\n return this.logAndReturn(flag, context, result);\n }\n }\n\n // Fall back to default value\n return this.logAndReturn(\n flag,\n context,\n this.makeResult(flag.defaultValue, 'DEFAULT_VALUE')\n );\n }\n\n /**\n * Evaluate experiment and assign variant.\n */\n private async evaluateExperiment(\n experiment: Experiment,\n context: EvaluationContext\n ): Promise<EvaluationResult | null> {\n const subjectId = getSubjectId(context);\n const subjectType = context.userId\n ? 'user'\n : context.orgId\n ? 'org'\n : 'session';\n\n // Check audience percentage\n const audienceBucket = hashToBucket(\n subjectId,\n `${experiment.key}:audience`\n );\n if (audienceBucket >= experiment.audiencePercentage) {\n return null; // User not in experiment audience\n }\n\n // Check for existing assignment\n let variant = await this.repository.getExperimentAssignment(\n experiment.id,\n subjectType,\n subjectId\n );\n\n if (!variant) {\n // Assign to variant based on consistent hashing\n const variantBucket = hashToBucket(subjectId, experiment.key);\n variant = this.assignVariant(experiment.variants, variantBucket);\n\n // Save assignment\n await this.repository.saveExperimentAssignment(\n experiment.id,\n subjectType,\n subjectId,\n variant,\n variantBucket\n );\n }\n\n // Control variant typically means feature is off\n const enabled = variant !== 'control';\n\n return this.makeResult(\n enabled,\n 'EXPERIMENT_VARIANT',\n variant,\n undefined,\n experiment.id\n );\n }\n\n /**\n * Assign a variant based on bucket and variant percentages.\n */\n private assignVariant(variants: ExperimentVariant[], bucket: number): string {\n let cumulative = 0;\n for (const variant of variants) {\n cumulative += variant.percentage;\n if (bucket < cumulative) {\n return variant.key;\n }\n }\n // Fallback to last variant (shouldn't happen if percentages sum to 100)\n return variants[variants.length - 1]?.key ?? 'control';\n }\n\n /**\n * Create evaluation result.\n */\n private makeResult(\n enabled: boolean,\n reason: EvaluationReason,\n variant?: string,\n ruleId?: string,\n experimentId?: string\n ): EvaluationResult {\n return {\n enabled,\n variant,\n reason,\n ruleId,\n experimentId,\n };\n }\n\n /**\n * Log evaluation and return result.\n */\n private logAndReturn(\n flag: FeatureFlag,\n context: EvaluationContext,\n result: EvaluationResult\n ): EvaluationResult {\n if (this.logEvaluations && this.logger) {\n const subjectId = getSubjectId(context);\n const subjectType = context.userId\n ? 'user'\n : context.orgId\n ? 'org'\n : 'session';\n\n this.logger.log({\n flagId: flag.id,\n flagKey: flag.key,\n subjectType,\n subjectId,\n result: result.enabled,\n variant: result.variant,\n reason: result.reason,\n ruleId: result.ruleId,\n experimentId: result.experimentId,\n context,\n });\n }\n return result;\n }\n}\n\n// ============ In-Memory Repository ============\n\n/**\n * In-memory flag repository for testing and development.\n */\nexport class InMemoryFlagRepository implements FlagRepository {\n private flags = new Map<string, FeatureFlag>();\n private rules = new Map<string, TargetingRule[]>();\n private experiments = new Map<string, Experiment>();\n private assignments = new Map<string, string>();\n\n addFlag(flag: FeatureFlag): void {\n this.flags.set(flag.key, flag);\n }\n\n addRule(flagId: string, rule: TargetingRule): void {\n const existing = this.rules.get(flagId) || [];\n existing.push(rule);\n this.rules.set(flagId, existing);\n }\n\n addExperiment(experiment: Experiment, flagId: string): void {\n this.experiments.set(flagId, experiment);\n }\n\n async getFlag(key: string): Promise<FeatureFlag | null> {\n return this.flags.get(key) || null;\n }\n\n async getRules(flagId: string): Promise<TargetingRule[]> {\n return this.rules.get(flagId) || [];\n }\n\n async getActiveExperiment(flagId: string): Promise<Experiment | null> {\n return this.experiments.get(flagId) || null;\n }\n\n async getExperimentAssignment(\n experimentId: string,\n subjectType: string,\n subjectId: string\n ): Promise<string | null> {\n const key = `${experimentId}:${subjectType}:${subjectId}`;\n return this.assignments.get(key) || null;\n }\n\n async saveExperimentAssignment(\n experimentId: string,\n subjectType: string,\n subjectId: string,\n variant: string\n ): Promise<void> {\n const key = `${experimentId}:${subjectType}:${subjectId}`;\n this.assignments.set(key, variant);\n }\n\n clear(): void {\n this.flags.clear();\n this.rules.clear();\n this.experiments.clear();\n this.assignments.clear();\n }\n}\n"],"mappings":"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"}
|