npm package: @omniretail/omniflags-core
Description: OmniFlags feature flag SDK — core evaluator, cache, and transport
Latest version: 1.0.25

# @omniretail/omniflags-core

JavaScript/TypeScript SDK for OmniFlags.

## Installation

```bash
npm install @omniretail/omniflags-core
```

## Quick start

```ts
import { OmniFlagsClient } from "@omniretail/omniflags-core";

const client = new OmniFlagsClient({ sdkKey: "your-sdk-key" });

await client.waitForReady();

const enabled = client.isEnabled("store.dark-mode");
const theme   = client.getString("store.theme", "light");
```

## Configuration

```ts
const client = new OmniFlagsClient({
  sdkKey: "your-sdk-key",   // required
  hooks:  [loggingHook],    // optional evaluation hooks
});
```

| Option | Type | Description |
|--------|------|-------------|
| `sdkKey` | `string` | **Required.** SDK key from the OmniFlags dashboard. |
| `hooks` | `Hook[]` | Lifecycle hooks called before/after each evaluation. |

## Evaluation

Flag keys are namespaced by project: `{projectKey}.{flagKey}`.

```ts
// Boolean
client.isEnabled("store.show-banner");
client.isEnabled("store.show-banner", { customerId: "cust-123" }); // with context

// Typed value methods
client.getBoolean("store.dark-mode", false);
client.getString("store.theme", "light");
client.getNumber("store.max-items", 10);

// Generic — useful when the type is only known at runtime
client.getValue<{ color: string }>("store.config", defaultConfig);

// Full evaluation result (variant, reason, rule matched)
const result = client.getVariant("store.show-banner");
// result.value, result.variant, result.reason, result.ruleId, result.errorCode
```

## Context

Pass context per evaluation call — customer, business, branch, country, etc. There is no persistent context state on the client.

```ts
const ctx = {
  customerId:       request.customerId,
  agentId:          request.agentId,
  businessId:       request.businessId,
  businessBranchId: request.businessBranchId,
};

client.isEnabled("store.show-banner", ctx);
client.getString("store.theme", "light", ctx);
```

## Loading state

```ts
const state = client.loadingState;
// state.isFetching  — a network request is in flight
// state.isLoading   — no flags have been loaded yet (first fetch pending)
// state.origin      — "NONE" | "CACHE" | "SERVER"
// state.error       — last fetch error, or null
```

Subscribe to changes:

```ts
const unsub = client.on("PROVIDER_LOADING_STATE_CHANGED", () => {
  console.log(client.loadingState);
});

unsub(); // remove the listener
```

## Status & lifecycle

```ts
client.status;               // "not_ready" | "ready" | "stale" | "error"
await client.waitForReady(); // resolves after the first fetch or cache hit
await client.shutdown();     // stop polling and flush metrics
```

The SDK polls for flag updates automatically on a server-driven interval (default: 5 minutes).

## Events

```ts
const unsub = client.on("PROVIDER_CONFIGURATION_CHANGED", () => {
  // flags updated
});

unsub(); // remove the listener
```

| Event | When |
|-------|------|
| `PROVIDER_READY` | First snapshot loaded |
| `PROVIDER_STALE` | Fetch failed but cached flags are available |
| `PROVIDER_ERROR` | Fetch failed and no cache |
| `PROVIDER_CONFIGURATION_CHANGED` | Flags updated by a background poll |
| `PROVIDER_LOADING_STATE_CHANGED` | Loading state changed |

## Hooks

```ts
import type { Hook } from "@omniretail/omniflags-core";

const loggingHook: Hook = {
  after(flagKey, result) {
    console.log(`[flag] ${flagKey} → ${result.variant} (${result.reason})`);
  },
};

const client = new OmniFlagsClient({ sdkKey: "your-sdk-key", hooks: [loggingHook] });

// Or add after construction
client.addHook(loggingHook);
```

## Metrics

Flag evaluations are tracked and flushed to OmniFlags automatically. The flush interval and batch size are driven by the server — no configuration needed.