Guides

Core Evaluation Engine

Reuse FlagForge's pure TypeScript engine for deterministic local flag evaluation

Core Evaluation Engine

The @flagforge/core package evaluates feature flags without network access, storage, a clock, or randomness. Give it a per-environment EvaluatableFlag definition and an EvaluationContext, and it returns the selected variation and value.

This is useful when you need FlagForge semantics in a custom Node.js service, worker, edge adapter, or another runtime where the hosted API and SDK are not the right integration. The REST API and @flagforge/sdk use the same engine, so local results follow the same targeting, segment, and rollout rules.

Note:

The core package does not fetch configuration or manage API keys. Obtain or define the flag definitions separately, then pass them to the synchronous evaluation functions.

Install

Install the package in the application that will perform evaluation:

bash
pnpm add @flagforge/core

The package has no runtime dependencies and exposes its public API from @flagforge/core.

Evaluate one flag

An EvaluatableFlag is a flattened, per-environment definition. It includes the flag type, named variations, enabled state, fallback variation keys, and ordered targeting rules. The context has a stable subject key and optional primitive attributes.

typescript
import { evaluateFlag } from "@flagforge/core";
import type { EvaluatableFlag, EvaluationContext } from "@flagforge/core";

const checkoutFlag: EvaluatableFlag = {
  key: "new-checkout",
  type: "boolean",
  enabled: true,
  variations: [
    { key: "on", value: true },
    { key: "off", value: false },
  ],
  defaultVariationKey: "off",
  offVariationKey: "off",
  rules: [
    {
      id: "pro-users",
      conditions: [
        { attribute: "plan", operator: "equals", values: ["pro"] },
      ],
      rollout: {
        branches: [
          { variationKey: "on", weight: 25_000 },
          { variationKey: "off", weight: 75_000 },
        ],
      },
    },
  ],
};

const context: EvaluationContext = {
  key: "user-123",
  attributes: { plan: "pro" },
};

const result = evaluateFlag(checkoutFlag, context);

console.log(result);
// {
//   flagKey: "new-checkout",
//   value: true, // or false, depending on the deterministic bucket
//   variationKey: "on", // or "off"
//   reason: { kind: "RULE_MATCH", ruleId: "pro-users", ruleIndex: 0 }
// }

evaluateFlag is synchronous. Pass a third argument when the flag uses segment conditions:

typescript
import { evaluateFlag } from "@flagforge/core";
import type {
  EvaluatableFlag,
  EvaluationContext,
  Segment,
} from "@flagforge/core";

const betaFlag: EvaluatableFlag = {
  key: "beta-dashboard",
  type: "boolean",
  enabled: true,
  variations: [
    { key: "on", value: true },
    { key: "off", value: false },
  ],
  defaultVariationKey: "off",
  offVariationKey: "off",
  rules: [
    {
      id: "beta-segment",
      conditions: [
        {
          attribute: "segment",
          operator: "inSegment",
          values: ["beta-users"],
        },
      ],
      variationKey: "on",
    },
  ],
};

const segments: Segment[] = [
  {
    key: "beta-users",
    conditions: [
      { attribute: "beta", operator: "equals", values: [true] },
    ],
  },
];

const context: EvaluationContext = {
  key: "user-123",
  attributes: { beta: true },
};

const result = evaluateFlag(betaFlag, context, segments);

For an inSegment condition, values contains segment keys and attribute is ignored. A missing segment does not match. Segment conditions are combined with logical AND, and nested segment references are cycle-protected.

Evaluate all flags

Use evaluateAll when the same context must be evaluated against a collection of definitions. It returns a map keyed by each flag's key, with one EvaluationResult per flag.

typescript
import { evaluateAll } from "@flagforge/core";
import type { EvaluatableFlag, EvaluationContext } from "@flagforge/core";

const flags: EvaluatableFlag[] = [checkoutFlag, {
  key: "checkout-label",
  type: "string",
  enabled: true,
  variations: [
    { key: "control", value: "Continue" },
    { key: "new", value: "Start checkout" },
  ],
  defaultVariationKey: "control",
  offVariationKey: "control",
  rules: [],
}];

const results = evaluateAll(flags, context);

console.log(results["new-checkout"].value);
console.log(results["checkout-label"].variationKey);

Provide the same optional segments array as the third argument to evaluateAll when any definition references segments:

typescript
const results = evaluateAll(flags, context, segments);

Evaluation behavior

The engine resolves each flag in this order:

  1. A disabled flag serves offVariationKey and reports reason.kind as OFF.

  2. Enabled flags check rules in array order. Every condition in a rule must match; the first matching rule wins and reports RULE_MATCH with its ruleId and zero-based ruleIndex.

  3. If no rule matches, the flag serves defaultVariationKey and reports FALLTHROUGH.

A rule can select a fixed variationKey or a rollout. Rollout weights use a resolution of 100,000, so 25_000 represents 25%. Bucketing hashes the flag key and the selected bucket value. It uses context.key by default; set rollout.bucketBy to use an attribute instead. Keep that identifier stable when you want a subject to remain in the same branch.

Conditions support equals, in, contains, startsWith, endsWith, numeric comparisons, semantic-version comparisons, regular expressions, and inSegment. A condition's values are alternatives, while conditions within a rule or segment are combined with logical AND. Set negate: true to invert a condition.

Note:

Variation and fallback keys must refer to entries in variations. If resolution encounters an unknown variation key, the result uses the off variation when available and reports reason.kind as ERROR with an error message. Keep definitions internally consistent before distributing them to applications.

Read the result safely

EvaluationResult contains four fields:

FieldMeaning
flagKeyThe evaluated flag's key.
valueThe selected boolean, string, number, or JSON value.
variationKeyThe named variation selected by the engine. An error result uses an empty string.
reasonA discriminated object explaining why the value was selected.

Use reason.kind for application diagnostics rather than parsing messages. RULE_MATCH also identifies the matching rule; ERROR includes an error string. A result is still returned for evaluation errors, with a fallback value chosen from the off variation, the first variation, or false when no variation value is available.

When to use the core package

Use @flagforge/core directly when your application already has configuration delivery, needs evaluation in a non-SDK runtime, or wants to share the exact engine semantics with another TypeScript component.

Use @flagforge/sdk when you want FlagForge to bootstrap definitions from GET /v1/config, evaluate them locally, and optionally poll for changes. Use the REST evaluation endpoints when configuration should remain behind the API instead of being copied into the application process.

Continue with the SDK

Bootstrap definitions from FlagForge and use typed getters, polling, and change listeners in a Node.js application.

Understand evaluation semantics

Review evaluation context fields, targeting order, segment matching, rollout behavior, and result reasons.