@contractspec/lib.feature-flags 1.56.1 → 1.58.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/dist/browser/contracts/index.js +636 -0
- package/dist/browser/docs/feature-flags.docblock.js +71 -0
- package/dist/browser/docs/index.js +71 -0
- package/dist/browser/entities/index.js +306 -0
- package/dist/browser/evaluation/index.js +223 -0
- package/dist/browser/events.js +296 -0
- package/dist/browser/feature-flags.capability.js +28 -0
- package/dist/browser/feature-flags.feature.js +55 -0
- package/dist/browser/index.js +1583 -0
- package/dist/contracts/index.d.ts +944 -953
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +635 -906
- package/dist/docs/feature-flags.docblock.d.ts +2 -1
- package/dist/docs/feature-flags.docblock.d.ts.map +1 -0
- package/dist/docs/feature-flags.docblock.js +18 -22
- package/dist/docs/index.d.ts +2 -1
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +72 -1
- package/dist/entities/index.d.ts +159 -164
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/index.js +297 -315
- package/dist/evaluation/index.d.ts +119 -122
- package/dist/evaluation/index.d.ts.map +1 -1
- package/dist/evaluation/index.js +215 -212
- package/dist/events.d.ts +480 -489
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +272 -511
- package/dist/feature-flags.capability.d.ts +2 -8
- package/dist/feature-flags.capability.d.ts.map +1 -1
- package/dist/feature-flags.capability.js +29 -25
- package/dist/feature-flags.feature.d.ts +1 -7
- package/dist/feature-flags.feature.d.ts.map +1 -1
- package/dist/feature-flags.feature.js +54 -146
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1584 -8
- package/dist/node/contracts/index.js +636 -0
- package/dist/node/docs/feature-flags.docblock.js +71 -0
- package/dist/node/docs/index.js +71 -0
- package/dist/node/entities/index.js +306 -0
- package/dist/node/evaluation/index.js +223 -0
- package/dist/node/events.js +296 -0
- package/dist/node/feature-flags.capability.js +28 -0
- package/dist/node/feature-flags.feature.js +55 -0
- package/dist/node/index.js +1583 -0
- package/package.json +117 -30
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/src/app-config/lifecycle.d.ts +0 -1
- package/dist/contracts/src/app-config/runtime.d.ts +0 -13
- package/dist/contracts/src/app-config/spec.d.ts +0 -9
- package/dist/contracts/src/app-config/validation.d.ts +0 -8
- package/dist/contracts/src/capabilities/capabilities.d.ts +0 -60
- package/dist/contracts/src/capabilities/capabilities.d.ts.map +0 -1
- package/dist/contracts/src/capabilities/context.d.ts +0 -1
- package/dist/contracts/src/capabilities/guards.d.ts +0 -4
- package/dist/contracts/src/capabilities/index.d.ts +0 -5
- package/dist/contracts/src/capabilities/openbanking.d.ts +0 -1
- package/dist/contracts/src/capabilities/validation.d.ts +0 -4
- package/dist/contracts/src/client/index.d.ts +0 -1
- package/dist/contracts/src/client/react/drivers/rn-reusables.d.ts +0 -1
- package/dist/contracts/src/client/react/drivers/shadcn.d.ts +0 -1
- package/dist/contracts/src/client/react/feature-render.d.ts +0 -3
- package/dist/contracts/src/client/react/form-render.d.ts +0 -4
- package/dist/contracts/src/client/react/index.d.ts +0 -4
- package/dist/contracts/src/contract-registry/index.d.ts +0 -2
- package/dist/contracts/src/contract-registry/schemas.d.ts +0 -2
- package/dist/contracts/src/contract-registry/types.d.ts +0 -1
- package/dist/contracts/src/data-views/index.d.ts +0 -4
- package/dist/contracts/src/data-views/registry.d.ts +0 -2
- package/dist/contracts/src/data-views/report/contractVerificationTable.d.ts +0 -1
- package/dist/contracts/src/data-views/spec.d.ts +0 -3
- package/dist/contracts/src/data-views/types.d.ts +0 -2
- package/dist/contracts/src/docs/accessibility_wcag_compliance_specs.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/capabilities/documentationSystem.capability.d.ts +0 -1
- package/dist/contracts/src/docs/capabilities/index.d.ts +0 -1
- package/dist/contracts/src/docs/commands/docsGenerate.command.d.ts +0 -2
- package/dist/contracts/src/docs/commands/docsPublish.command.d.ts +0 -2
- package/dist/contracts/src/docs/commands/index.d.ts +0 -2
- package/dist/contracts/src/docs/contracts.d.ts +0 -7
- package/dist/contracts/src/docs/events/docsGenerated.event.d.ts +0 -2
- package/dist/contracts/src/docs/events/docsPublished.event.d.ts +0 -2
- package/dist/contracts/src/docs/events/index.d.ts +0 -2
- package/dist/contracts/src/docs/forms/docsSearch.form.d.ts +0 -2
- package/dist/contracts/src/docs/forms/index.d.ts +0 -1
- package/dist/contracts/src/docs/index.d.ts +0 -33
- package/dist/contracts/src/docs/presentations/docsLayout.presentation.d.ts +0 -1
- package/dist/contracts/src/docs/presentations/docsReferencePage.presentation.d.ts +0 -1
- package/dist/contracts/src/docs/presentations/index.d.ts +0 -2
- package/dist/contracts/src/docs/presentations.d.ts +0 -2
- package/dist/contracts/src/docs/queries/contractReference.query.d.ts +0 -2
- package/dist/contracts/src/docs/queries/docsIndex.query.d.ts +0 -2
- package/dist/contracts/src/docs/queries/index.d.ts +0 -2
- package/dist/contracts/src/docs/registry.d.ts +0 -10
- package/dist/contracts/src/docs/registry.d.ts.map +0 -1
- package/dist/contracts/src/docs/tech/auth/better-auth-nextjs.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/contracts/README.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/contracts/openapi-export.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/contracts/openapi-import.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/lifecycle-stage-system.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/llm/llm-integration.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/mcp-endpoints.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/presentation-runtime.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/schema/README.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/learning-events.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/learning-journeys.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/platform-admin-panel.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/project-access-teams.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/project-routing.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/sandbox-unlogged.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/team-invitations.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/workspace-ops.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/studio/workspaces.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/telemetry-ingest.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/tech/vscode-extension.docblock.d.ts +0 -1
- package/dist/contracts/src/docs/views/contractReference.dataView.d.ts +0 -1
- package/dist/contracts/src/docs/views/docsIndex.dataView.d.ts +0 -1
- package/dist/contracts/src/docs/views/exampleCatalog.dataView.d.ts +0 -1
- package/dist/contracts/src/docs/views/index.d.ts +0 -3
- package/dist/contracts/src/events.d.ts +0 -45
- package/dist/contracts/src/events.d.ts.map +0 -1
- package/dist/contracts/src/examples/index.d.ts +0 -4
- package/dist/contracts/src/examples/registry.d.ts +0 -2
- package/dist/contracts/src/examples/schema.d.ts +0 -2
- package/dist/contracts/src/examples/types.d.ts +0 -3
- package/dist/contracts/src/examples/validation.d.ts +0 -1
- package/dist/contracts/src/experiments/evaluator.d.ts +0 -2
- package/dist/contracts/src/experiments/spec-resolver.d.ts +0 -4
- package/dist/contracts/src/experiments/spec.d.ts +0 -16
- package/dist/contracts/src/experiments/spec.d.ts.map +0 -1
- package/dist/contracts/src/features/index.d.ts +0 -4
- package/dist/contracts/src/features/install.d.ts +0 -6
- package/dist/contracts/src/features/registry.d.ts +0 -2
- package/dist/contracts/src/features/types.d.ts +0 -80
- package/dist/contracts/src/features/types.d.ts.map +0 -1
- package/dist/contracts/src/features/validation.d.ts +0 -2
- package/dist/contracts/src/forms/forms.d.ts +0 -3
- package/dist/contracts/src/forms/index.d.ts +0 -1
- package/dist/contracts/src/index.d.ts +0 -53
- package/dist/contracts/src/install.d.ts +0 -7
- package/dist/contracts/src/integrations/connection.d.ts +0 -1
- package/dist/contracts/src/integrations/index.d.ts +0 -6
- package/dist/contracts/src/integrations/openbanking/contracts/accounts.d.ts +0 -4
- package/dist/contracts/src/integrations/openbanking/contracts/balances.d.ts +0 -4
- package/dist/contracts/src/integrations/openbanking/contracts/index.d.ts +0 -5
- package/dist/contracts/src/integrations/openbanking/contracts/transactions.d.ts +0 -4
- package/dist/contracts/src/integrations/openbanking/guards.d.ts +0 -1
- package/dist/contracts/src/integrations/openbanking/models.d.ts +0 -1
- package/dist/contracts/src/integrations/openbanking/openbanking.feature.d.ts +0 -1
- package/dist/contracts/src/integrations/operations.d.ts +0 -4
- package/dist/contracts/src/integrations/providers/elevenlabs.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/gcs-storage.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/gmail.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/google-calendar.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/index.d.ts +0 -11
- package/dist/contracts/src/integrations/providers/mistral.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/postmark.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/powens.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/qdrant.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/registry.d.ts +0 -1
- package/dist/contracts/src/integrations/providers/stripe.d.ts +0 -2
- package/dist/contracts/src/integrations/providers/twilio-sms.d.ts +0 -2
- package/dist/contracts/src/integrations/spec.d.ts +0 -3
- package/dist/contracts/src/jsonschema.d.ts +0 -4
- package/dist/contracts/src/knowledge/index.d.ts +0 -2
- package/dist/contracts/src/knowledge/operations.d.ts +0 -4
- package/dist/contracts/src/knowledge/spaces/email-threads.d.ts +0 -1
- package/dist/contracts/src/knowledge/spaces/financial-docs.d.ts +0 -1
- package/dist/contracts/src/knowledge/spaces/financial-overview.d.ts +0 -1
- package/dist/contracts/src/knowledge/spaces/index.d.ts +0 -6
- package/dist/contracts/src/knowledge/spaces/product-canon.d.ts +0 -1
- package/dist/contracts/src/knowledge/spaces/support-faq.d.ts +0 -1
- package/dist/contracts/src/knowledge/spaces/uploaded-docs.d.ts +0 -1
- package/dist/contracts/src/knowledge/spec.d.ts +0 -3
- package/dist/contracts/src/llm/exporters.d.ts +0 -7
- package/dist/contracts/src/llm/index.d.ts +0 -3
- package/dist/contracts/src/llm/prompts.d.ts +0 -2
- package/dist/contracts/src/llm/types.d.ts +0 -5
- package/dist/contracts/src/migrations.d.ts +0 -1
- package/dist/contracts/src/model-registry.d.ts +0 -1
- package/dist/contracts/src/onboarding-base.d.ts +0 -2
- package/dist/contracts/src/openapi.d.ts +0 -1
- package/dist/contracts/src/operations/index.d.ts +0 -3
- package/dist/contracts/src/operations/operation.d.ts +0 -176
- package/dist/contracts/src/operations/operation.d.ts.map +0 -1
- package/dist/contracts/src/operations/registry.d.ts +0 -6
- package/dist/contracts/src/operations/report/getContractVerificationStatus.d.ts +0 -3
- package/dist/contracts/src/operations/report/index.d.ts +0 -4
- package/dist/contracts/src/ownership.d.ts +0 -164
- package/dist/contracts/src/ownership.d.ts.map +0 -1
- package/dist/contracts/src/policy/engine.d.ts +0 -3
- package/dist/contracts/src/policy/guards.d.ts +0 -1
- package/dist/contracts/src/policy/index.d.ts +0 -6
- package/dist/contracts/src/policy/opa-adapter.d.ts +0 -3
- package/dist/contracts/src/policy/registry.d.ts +0 -2
- package/dist/contracts/src/policy/spec.d.ts +0 -13
- package/dist/contracts/src/policy/spec.d.ts.map +0 -1
- package/dist/contracts/src/policy/validation.d.ts +0 -3
- package/dist/contracts/src/presentations/index.d.ts +0 -3
- package/dist/contracts/src/presentations/presentations.d.ts +0 -10
- package/dist/contracts/src/presentations/presentations.d.ts.map +0 -1
- package/dist/contracts/src/presentations/registry.d.ts +0 -2
- package/dist/contracts/src/presentations/transform-engine.d.ts +0 -2
- package/dist/contracts/src/prompt.d.ts +0 -2
- package/dist/contracts/src/promptRegistry.d.ts +0 -2
- package/dist/contracts/src/regenerator/adapters.d.ts +0 -1
- package/dist/contracts/src/regenerator/executor.d.ts +0 -1
- package/dist/contracts/src/regenerator/index.d.ts +0 -6
- package/dist/contracts/src/regenerator/service.d.ts +0 -2
- package/dist/contracts/src/regenerator/sinks.d.ts +0 -2
- package/dist/contracts/src/regenerator/types.d.ts +0 -3
- package/dist/contracts/src/regenerator/utils.d.ts +0 -1
- package/dist/contracts/src/registry-utils.d.ts +0 -1
- package/dist/contracts/src/registry.d.ts +0 -3
- package/dist/contracts/src/resources.d.ts +0 -19
- package/dist/contracts/src/resources.d.ts.map +0 -1
- package/dist/contracts/src/schema-to-markdown.d.ts +0 -1
- package/dist/contracts/src/serialization/index.d.ts +0 -1
- package/dist/contracts/src/serialization/serializers.d.ts +0 -7
- package/dist/contracts/src/server/graphql-pothos.d.ts +0 -7
- package/dist/contracts/src/server/index.d.ts +0 -7
- package/dist/contracts/src/server/mcp/createMcpServer.d.ts +0 -5
- package/dist/contracts/src/server/mcp/mcpTypes.d.ts +0 -2
- package/dist/contracts/src/server/provider-mcp.d.ts +0 -1
- package/dist/contracts/src/server/rest-elysia.d.ts +0 -3
- package/dist/contracts/src/server/rest-express.d.ts +0 -3
- package/dist/contracts/src/server/rest-generic.d.ts +0 -2
- package/dist/contracts/src/server/rest-next-app.d.ts +0 -3
- package/dist/contracts/src/server/rest-next-pages.d.ts +0 -3
- package/dist/contracts/src/telemetry/anomaly.d.ts +0 -2
- package/dist/contracts/src/telemetry/index.d.ts +0 -3
- package/dist/contracts/src/telemetry/spec.d.ts +0 -2
- package/dist/contracts/src/telemetry/tracker.d.ts +0 -3
- package/dist/contracts/src/tests/index.d.ts +0 -2
- package/dist/contracts/src/tests/runner.d.ts +0 -3
- package/dist/contracts/src/tests/spec.d.ts +0 -13
- package/dist/contracts/src/tests/spec.d.ts.map +0 -1
- package/dist/contracts/src/themes.d.ts +0 -2
- package/dist/contracts/src/types.d.ts +0 -5
- package/dist/contracts/src/versioning/refs.d.ts +0 -47
- package/dist/contracts/src/versioning/refs.d.ts.map +0 -1
- package/dist/contracts/src/workflow/adapters/db-adapter.d.ts +0 -1
- package/dist/contracts/src/workflow/adapters/file-adapter.d.ts +0 -1
- package/dist/contracts/src/workflow/adapters/index.d.ts +0 -3
- package/dist/contracts/src/workflow/adapters/memory-store.d.ts +0 -1
- package/dist/contracts/src/workflow/context.d.ts +0 -2
- package/dist/contracts/src/workflow/index.d.ts +0 -8
- package/dist/contracts/src/workflow/overview.docblock.d.ts +0 -1
- package/dist/contracts/src/workflow/runner.d.ts +0 -5
- package/dist/contracts/src/workflow/sla-monitor.d.ts +0 -2
- package/dist/contracts/src/workflow/spec.d.ts +0 -6
- package/dist/contracts/src/workflow/state.d.ts +0 -1
- package/dist/contracts/src/workflow/validation.d.ts +0 -5
- package/dist/contracts/src/workspace-config/contractsrc-schema.d.ts +0 -1
- package/dist/contracts/src/workspace-config/index.d.ts +0 -1
- package/dist/contracts/src/workspace-config/workspace-config.docblock.d.ts +0 -1
- package/dist/docs/feature-flags.docblock.js.map +0 -1
- package/dist/entities/index.js.map +0 -1
- package/dist/evaluation/index.js.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/feature-flags.capability.js.map +0 -1
- package/dist/feature-flags.feature.js.map +0 -1
- package/dist/schema/src/EnumType.d.ts +0 -36
- package/dist/schema/src/EnumType.d.ts.map +0 -1
- package/dist/schema/src/FieldType.d.ts +0 -30
- package/dist/schema/src/FieldType.d.ts.map +0 -1
- package/dist/schema/src/GraphQLSchemaType.d.ts +0 -2
- package/dist/schema/src/JsonSchemaType.d.ts +0 -2
- package/dist/schema/src/ScalarTypeEnum.d.ts +0 -1
- package/dist/schema/src/SchemaModel.d.ts +0 -70
- package/dist/schema/src/SchemaModel.d.ts.map +0 -1
- package/dist/schema/src/SchemaModelType.d.ts +0 -38
- package/dist/schema/src/SchemaModelType.d.ts.map +0 -1
- package/dist/schema/src/ZodSchemaType.d.ts +0 -2
- package/dist/schema/src/entity/defineEntity.d.ts +0 -1
- package/dist/schema/src/entity/generator.d.ts +0 -1
- package/dist/schema/src/entity/index.d.ts +0 -3
- package/dist/schema/src/entity/types.d.ts +0 -146
- package/dist/schema/src/entity/types.d.ts.map +0 -1
- package/dist/schema/src/index.d.ts +0 -10
package/dist/evaluation/index.js
CHANGED
|
@@ -1,221 +1,224 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Simple hash function for consistent bucketing.
|
|
4
|
-
* Uses a deterministic algorithm so the same input always produces the same bucket.
|
|
5
|
-
*/
|
|
1
|
+
// @bun
|
|
2
|
+
// src/evaluation/index.ts
|
|
6
3
|
function hashToBucket(value, seed = "") {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
const input = `${seed}:${value}`;
|
|
5
|
+
let hash = 0;
|
|
6
|
+
for (let i = 0;i < input.length; i++) {
|
|
7
|
+
const char = input.charCodeAt(i);
|
|
8
|
+
hash = (hash << 5) - hash + char;
|
|
9
|
+
hash = hash & hash;
|
|
10
|
+
}
|
|
11
|
+
return Math.abs(hash % 100);
|
|
15
12
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Get subject identifier from context for consistent hashing.
|
|
18
|
-
*/
|
|
19
13
|
function getSubjectId(context) {
|
|
20
|
-
|
|
14
|
+
return context.userId || context.sessionId || context.orgId || "anonymous";
|
|
21
15
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Evaluate a single targeting rule condition.
|
|
24
|
-
*/
|
|
25
16
|
function evaluateRuleCondition(rule, context) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
17
|
+
const attributeValue = getAttributeValue(rule.attribute, context);
|
|
18
|
+
switch (rule.operator) {
|
|
19
|
+
case "EQ":
|
|
20
|
+
return attributeValue === rule.value;
|
|
21
|
+
case "NEQ":
|
|
22
|
+
return attributeValue !== rule.value;
|
|
23
|
+
case "IN":
|
|
24
|
+
if (!Array.isArray(rule.value))
|
|
25
|
+
return false;
|
|
26
|
+
return rule.value.includes(attributeValue);
|
|
27
|
+
case "NIN":
|
|
28
|
+
if (!Array.isArray(rule.value))
|
|
29
|
+
return true;
|
|
30
|
+
return !rule.value.includes(attributeValue);
|
|
31
|
+
case "CONTAINS":
|
|
32
|
+
if (typeof attributeValue !== "string" || typeof rule.value !== "string")
|
|
33
|
+
return false;
|
|
34
|
+
return attributeValue.includes(rule.value);
|
|
35
|
+
case "NOT_CONTAINS":
|
|
36
|
+
if (typeof attributeValue !== "string" || typeof rule.value !== "string")
|
|
37
|
+
return true;
|
|
38
|
+
return !attributeValue.includes(rule.value);
|
|
39
|
+
case "GT":
|
|
40
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number")
|
|
41
|
+
return false;
|
|
42
|
+
return attributeValue > rule.value;
|
|
43
|
+
case "GTE":
|
|
44
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number")
|
|
45
|
+
return false;
|
|
46
|
+
return attributeValue >= rule.value;
|
|
47
|
+
case "LT":
|
|
48
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number")
|
|
49
|
+
return false;
|
|
50
|
+
return attributeValue < rule.value;
|
|
51
|
+
case "LTE":
|
|
52
|
+
if (typeof attributeValue !== "number" || typeof rule.value !== "number")
|
|
53
|
+
return false;
|
|
54
|
+
return attributeValue <= rule.value;
|
|
55
|
+
case "PERCENTAGE":
|
|
56
|
+
return hashToBucket(getSubjectId(context), rule.attribute) < (typeof rule.value === "number" ? rule.value : 0);
|
|
57
|
+
default:
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
57
60
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Get attribute value from context.
|
|
60
|
-
*/
|
|
61
61
|
function getAttributeValue(attribute, context) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
switch (attribute) {
|
|
63
|
+
case "userId":
|
|
64
|
+
return context.userId;
|
|
65
|
+
case "orgId":
|
|
66
|
+
return context.orgId;
|
|
67
|
+
case "plan":
|
|
68
|
+
return context.plan;
|
|
69
|
+
case "segment":
|
|
70
|
+
return context.segment;
|
|
71
|
+
case "sessionId":
|
|
72
|
+
return context.sessionId;
|
|
73
|
+
default:
|
|
74
|
+
return context.attributes?.[attribute];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class FlagEvaluator {
|
|
79
|
+
repository;
|
|
80
|
+
logger;
|
|
81
|
+
logEvaluations;
|
|
82
|
+
constructor(options) {
|
|
83
|
+
this.repository = options.repository;
|
|
84
|
+
this.logger = options.logger;
|
|
85
|
+
this.logEvaluations = options.logEvaluations ?? false;
|
|
86
|
+
}
|
|
87
|
+
async evaluate(key, context) {
|
|
88
|
+
const orgId = context.orgId;
|
|
89
|
+
const flag = await this.repository.getFlag(key, orgId);
|
|
90
|
+
if (!flag) {
|
|
91
|
+
return this.makeResult(false, "FLAG_NOT_FOUND");
|
|
92
|
+
}
|
|
93
|
+
if (flag.status === "OFF") {
|
|
94
|
+
return this.logAndReturn(flag, context, this.makeResult(false, "FLAG_OFF"));
|
|
95
|
+
}
|
|
96
|
+
if (flag.status === "ON") {
|
|
97
|
+
return this.logAndReturn(flag, context, this.makeResult(true, "FLAG_ON"));
|
|
98
|
+
}
|
|
99
|
+
const rules = await this.repository.getRules(flag.id);
|
|
100
|
+
const sortedRules = [...rules].filter((r) => r.enabled).sort((a, b) => a.priority - b.priority);
|
|
101
|
+
for (const rule of sortedRules) {
|
|
102
|
+
if (evaluateRuleCondition(rule, context)) {
|
|
103
|
+
if (rule.rolloutPercentage !== undefined && rule.rolloutPercentage !== null) {
|
|
104
|
+
const bucket = hashToBucket(getSubjectId(context), flag.key);
|
|
105
|
+
if (bucket >= rule.rolloutPercentage) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const enabled = rule.serveValue ?? true;
|
|
110
|
+
return this.logAndReturn(flag, context, this.makeResult(enabled, "RULE_MATCH", rule.serveVariant, rule.id));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const experiment = await this.repository.getActiveExperiment(flag.id);
|
|
114
|
+
if (experiment && experiment.status === "RUNNING") {
|
|
115
|
+
const result = await this.evaluateExperiment(experiment, context);
|
|
116
|
+
if (result) {
|
|
117
|
+
return this.logAndReturn(flag, context, result);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return this.logAndReturn(flag, context, this.makeResult(flag.defaultValue, "DEFAULT_VALUE"));
|
|
121
|
+
}
|
|
122
|
+
async evaluateExperiment(experiment, context) {
|
|
123
|
+
const subjectId = getSubjectId(context);
|
|
124
|
+
const subjectType = context.userId ? "user" : context.orgId ? "org" : "session";
|
|
125
|
+
const audienceBucket = hashToBucket(subjectId, `${experiment.key}:audience`);
|
|
126
|
+
if (audienceBucket >= experiment.audiencePercentage) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
let variant = await this.repository.getExperimentAssignment(experiment.id, subjectType, subjectId);
|
|
130
|
+
if (!variant) {
|
|
131
|
+
const variantBucket = hashToBucket(subjectId, experiment.key);
|
|
132
|
+
variant = this.assignVariant(experiment.variants, variantBucket);
|
|
133
|
+
await this.repository.saveExperimentAssignment(experiment.id, subjectType, subjectId, variant, variantBucket);
|
|
134
|
+
}
|
|
135
|
+
const enabled = variant !== "control";
|
|
136
|
+
return this.makeResult(enabled, "EXPERIMENT_VARIANT", variant, undefined, experiment.id);
|
|
137
|
+
}
|
|
138
|
+
assignVariant(variants, bucket) {
|
|
139
|
+
let cumulative = 0;
|
|
140
|
+
for (const variant of variants) {
|
|
141
|
+
cumulative += variant.percentage;
|
|
142
|
+
if (bucket < cumulative) {
|
|
143
|
+
return variant.key;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return variants[variants.length - 1]?.key ?? "control";
|
|
147
|
+
}
|
|
148
|
+
makeResult(enabled, reason, variant, ruleId, experimentId) {
|
|
149
|
+
return {
|
|
150
|
+
enabled,
|
|
151
|
+
variant,
|
|
152
|
+
reason,
|
|
153
|
+
ruleId,
|
|
154
|
+
experimentId
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
logAndReturn(flag, context, result) {
|
|
158
|
+
if (this.logEvaluations && this.logger) {
|
|
159
|
+
const subjectId = getSubjectId(context);
|
|
160
|
+
const subjectType = context.userId ? "user" : context.orgId ? "org" : "session";
|
|
161
|
+
this.logger.log({
|
|
162
|
+
flagId: flag.id,
|
|
163
|
+
flagKey: flag.key,
|
|
164
|
+
subjectType,
|
|
165
|
+
subjectId,
|
|
166
|
+
result: result.enabled,
|
|
167
|
+
variant: result.variant,
|
|
168
|
+
reason: result.reason,
|
|
169
|
+
ruleId: result.ruleId,
|
|
170
|
+
experimentId: result.experimentId,
|
|
171
|
+
context
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
70
176
|
}
|
|
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
177
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
178
|
+
class InMemoryFlagRepository {
|
|
179
|
+
flags = new Map;
|
|
180
|
+
rules = new Map;
|
|
181
|
+
experiments = new Map;
|
|
182
|
+
assignments = 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
|
+
export {
|
|
219
|
+
hashToBucket,
|
|
220
|
+
getSubjectId,
|
|
221
|
+
evaluateRuleCondition,
|
|
222
|
+
InMemoryFlagRepository,
|
|
223
|
+
FlagEvaluator
|
|
224
|
+
};
|