> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.clubedge.live/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK configuration

> FeatureFlagsClient options, lifecycle methods, events, and cache behavior across all SDKs.

All official SDKs (Node.js, Browser, React, Next.js, NestJS) wrap a single `FeatureFlagsClient` from `@clubedge/feature-flags-sdk-core`. This page covers the configuration options, lifecycle methods, event model, and cache behavior shared across every SDK.

## Installation

```bash theme={null}
pnpm add @clubedge/feature-flags-node      # Node.js / backend
pnpm add @clubedge/feature-flags-browser   # Browser (vanilla)
pnpm add @clubedge/feature-flags-react     # React hooks + provider
pnpm add @clubedge/feature-flags-nextjs    # Next.js (App + Pages Router)
pnpm add @clubedge/feature-flags-nestjs    # NestJS module + DI
```

Packages are published to GitHub Packages. Configure your `.npmrc`:

```ini theme={null}
@clubedge:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}
```

The token needs `read:packages` scope. In CI, inject it as a masked secret.

<Note>
  `@clubedge/feature-flags-sdk-core` is a shared dependency of `-node`, `-react`, `-nextjs`, and `-nestjs`. Keep these packages' versions in sync within a project to avoid type or runtime mismatches.
</Note>

## Configuration options

Every SDK accepts the same options object:

```typescript theme={null}
new FeatureFlagsClient({
  sdkKey: 'ffk_...',      // required
  baseUrl: 'https://flags.clubedge.live',  // optional, defaults to http://localhost:3000
  pollIntervalMs: 30_000,  // optional, defaults to 30000
  cacheTtlSeconds: 60,     // optional, defaults to 60000
  retries: 3,              // optional, defaults to 3 (1–10)
  retryDelayMs: 1_000,     // optional, defaults to 1000
  logger: new ConsoleLogger('my-app'),  // optional, defaults to NoOpLogger
});
```

| Option            | Type           | Required | Default                 | Description                                                                                                                                           |
| ----------------- | -------------- | -------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sdkKey`          | `string`       | Yes      | —                       | Environment-scoped runtime key. Shown once at environment creation.                                                                                   |
| `baseUrl`         | `string`       | No       | `http://localhost:3000` | Base URL of your Feature Flags API instance.                                                                                                          |
| `pollIntervalMs`  | `number`       | No       | `30000`                 | How often the SDK polls `GET /sdk/v1/config` for updates. Set to `0` to disable automatic polling; call `refresh()` manually instead.                 |
| `cacheTtlSeconds` | `number`       | No       | `60000`                 | In-memory cache TTL. **Note:** despite the name, this value is used as milliseconds — `60000` = 60s. See [Cache TTL caveat](#cache-ttl-caveat) below. |
| `retries`         | `number`       | No       | `3`                     | Retry attempts (with exponential backoff) on transport failure. Range: 1–10.                                                                          |
| `retryDelayMs`    | `number`       | No       | `1000`                  | Base delay for exponential backoff between retries.                                                                                                   |
| `cache`           | `Cache`        | No       | `InMemoryCache`         | Custom cache implementation.                                                                                                                          |
| `transport`       | `Transport`    | No       | `FetchTransport`        | Custom HTTP transport for non-browser/non-Node environments.                                                                                          |
| `logger`          | `Logger`       | No       | `NoOpLogger`            | Any object implementing the `Logger` interface.                                                                                                       |
| `eventEmitter`    | `EventEmitter` | No       | built-in                | Custom event emitter; use to receive SDK lifecycle events.                                                                                            |

<Warning>
  **cacheTtlSeconds is actually in milliseconds.** The config field is named `cacheTtlSeconds` but the value is passed directly to the cache as milliseconds (matching `DEFAULT_CACHE_TTL_MS = 60_000`). The default of 60000 works correctly as 60 seconds, but setting `cacheTtlSeconds: 30` would yield a 30-millisecond TTL, not 30 seconds. Treat the value as milliseconds.
</Warning>

## Lifecycle

### `client.initialize()`

Fetches all flag configurations from `GET /sdk/v1/config` and populates the local in-memory cache. Starts background polling if `pollIntervalMs > 0`.

```typescript theme={null}
await client.initialize();
```

<Warning>
  Must be called and awaited before any `isEnabled()` or `getValue()` call. Calling evaluation methods before initialization throws `FeatureFlagsError` with code `NOT_INITIALIZED`.
</Warning>

### `client.shutdown()`

Stops the poll timer, clears pending requests, clears the in-memory cache, and emits a `shutdown` event.

```typescript theme={null}
process.on('SIGTERM', () => {
  client.shutdown();
  process.exit(0);
});
```

### `client.isReady()`

Returns `true` once `initialize()` has completed.

### `client.refresh()`

Forces an immediate re-fetch of flag configurations from the server, bypassing the poll interval.

```typescript theme={null}
await client.refresh();
```

## Evaluation methods

### `isEnabled(key, context?)`

Returns `true` or `false` for the given flag key.

```typescript theme={null}
const enabled = client.isEnabled('new_dashboard', {
  userId: user.id,
  country: user.country,
});
```

<Warning>
  **Context is evaluated at fetch time, not per-call.** The SDK pre-evaluates all flags when fetching configs from `GET /sdk/v1/config`, using an **empty context** (`{}`). The `context` argument to `isEnabled()` is accepted for API compatibility but does **not** influence the result — it returns the cached pre-computed value.

  This means:

  * Targeting rules that match on context attributes (e.g. `country == "US"`) will **never match** through the SDK, since the context is empty at evaluation time.
  * Percentage rollouts that require `userId` or `tenantId` will **always fall through** to the base flag value.

  If you need per-user targeting or rollout evaluation, use **Direct HTTP** — `POST /sdk/v1/evaluate` — which evaluates with the full context you pass at request time. See [Evaluation API](/feature-flags/reference/evaluation-api).
</Warning>

### `getValue(key, context?)`

Despite its name, `getValue()` returns the **evaluation reason** (a string), not the flag value.

```typescript theme={null}
const reason = client.getValue('new_dashboard', { userId: user.id });
// "flag_enabled" | "flag_disabled" | "flag_archived" | "targeting_rule" | "rollout"
```

| Return           | Meaning                                                                         |
| ---------------- | ------------------------------------------------------------------------------- |
| `flag_enabled`   | Flag is enabled in the environment; no rules or rollout                         |
| `flag_disabled`  | Flag is disabled in the environment                                             |
| `flag_archived`  | Flag has been archived                                                          |
| `targeting_rule` | A targeting rule matched (note: with empty context, rules rarely match)         |
| `rollout`        | Percentage rollout bucketed the user (note: rarely matches via SDK — see above) |

## Event model

The SDK emits lifecycle events. Provide a custom `EventEmitter` at construction or use the built-in one:

```typescript theme={null}
const client = new FeatureFlagsClient({
  sdkKey: process.env.FF_SDK_KEY!,
  eventEmitter: {
    on: (event) => {
      switch (event.type) {
        case 'ready':             console.log('SDK ready'); break;
        case 'refresh:success':   console.log('Configs refreshed'); break;
        case 'refresh:error':     console.error('Refresh failed', event.error); break;
        case 'shutdown':          console.log('SDK shut down'); break;
      }
      return () => {}; // unsubscribe
    },
    emit: () => {},
    removeAllListeners: () => {},
  },
});
```

| Event             | When                                  |
| ----------------- | ------------------------------------- |
| `ready`           | `initialize()` completed successfully |
| `refresh:start`   | `refresh()` or a poll cycle began     |
| `refresh:success` | Config fetch completed (200 or 304)   |
| `refresh:error`   | Config fetch failed after all retries |
| `shutdown`        | `shutdown()` called                   |

## Cache behavior

The SDK uses a three-level cache:

| Level | Layer               | TTL                                  |
| ----- | ------------------- | ------------------------------------ |
| 1     | SDK (in-memory)     | `cacheTtlSeconds` (default 60s)      |
| 2     | Redis (server-side) | 60s (`EVALUATION_CACHE_TTL_SECONDS`) |
| 3     | PostgreSQL          | — (source of truth)                  |

### Offline / failure behavior

| Scenario             | Behavior                                                            |
| -------------------- | ------------------------------------------------------------------- |
| API is down          | Serve cached values until cache TTL expires                         |
| Cold start, no cache | `initialize()` throws; catch and fall back to a default             |
| Network timeout      | Retry with exponential backoff, then serve cache if available       |
| Flag not found       | `isEnabled()` throws `FeatureFlagsError` with code `FLAG_NOT_FOUND` |

### Per-flag TTL override

When configuring a flag per environment via `PUT /flags/:flagId/environments/:environmentId`, you can set `ttlOverrideSeconds` to override the default SDK cache TTL for that specific flag/environment pair:

```http theme={null}
PUT /flags/{flagId}/environments/{environmentId}
Content-Type: application/json

{
  "isEnabled": true,
  "ttlOverrideSeconds": 5
}
```

This is useful for kill-switch flags that need near-instant propagation.

## Logger interface

```typescript theme={null}
interface Logger {
  debug(message: string, meta?: Record<string, unknown>): void;
  info(message: string, meta?: Record<string, unknown>): void;
  warn(message: string, meta?: Record<string, unknown>): void;
  error(message: string, meta?: Record<string, unknown>): void;
}
```

* `ConsoleLogger` — writes structured timestamps to stdout/stderr. `debug()` is a no-op in production (`NODE_ENV=production`).
* `NoOpLogger` — silently discards all output (the default).

```typescript theme={null}
import { ConsoleLogger } from '@clubedge/feature-flags-sdk-core';

const client = new FeatureFlagsClient({
  sdkKey: process.env.FF_SDK_KEY!,
  logger: new ConsoleLogger('workspace-api'),
});
```
