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

# Foff TypeScript SDK: integrate feature flags in Node.js

> Install the Foff TypeScript SDK, configure a client with polling, and resolve feature flag values by hierarchy in any Node.js application.

The Foff TypeScript SDK integrates feature flag resolution into any Node.js application. It polls the Foff API in the background, caches configs in memory, and lets you resolve the right flag value for any hierarchy with a single synchronous call. The package ships with full TypeScript types.

## Requirements

Node.js 18 or later.

## Installation

```bash theme={null}
npm install @twospoon/foff-feature-config-typescript-sdk
```

## Quick start

```ts theme={null}
import { Client, Config } from "@twospoon/foff-feature-config-typescript-sdk";

// STEP 1: Create appropriate config
const config = new Config({
  APIKey: process.env.FOFF_API_KEY!,
  BaseURL: "https://foff.twospoon.ai/live",
  Scope: "name-of-your-scope",
  PollingInterval: 30, // refresh configs every 30 seconds
});

// STEP 2: Create the client with the config
const client = await Client.newClient(config);

// STEP 3: Retrieve configs for your created features for given hierarchies
const value = client.getFeatureConfig("my-feature", ["org-1", "team-a", "user-123"]);
console.log(value);

// On shutdown
client.close();
```

<Note>
  Store your API key in an environment variable such as `FOFF_API_KEY` and read it with `process.env.FOFF_API_KEY`. Never hard-code it in source files.
</Note>

## Configuration

Create a `Config` instance with the fields below and pass it to `Client.newClient`.

| Field             | Type     | Required | Default | Description                                                                    |
| ----------------- | -------- | -------- | ------- | ------------------------------------------------------------------------------ |
| `APIKey`          | `string` | Yes      |         | Your Foff API key.                                                             |
| `BaseURL`         | `string` | Yes      |         | Base URL of the Foff API. Use `https://foff.twospoon.ai/live`.                 |
| `Scope`           | `string` | Yes      |         | The scope to fetch configs for, such as `production` or `staging`.             |
| `PollingInterval` | `number` | No       | `30`    | How often, in seconds, to poll for config updates. Use `0` to disable polling. |

### Validation

`Client.newClient` validates the config before creating the client. It throws an error if `APIKey`, `BaseURL`, or `Scope` is missing or empty.

### Polling interval normalization

The polling interval is clamped to safe bounds automatically before the client starts.

<Warning>
  If you set `PollingInterval` below 10 seconds, the SDK raises it to 10. If you set it above 3600 seconds (1 hour), the SDK lowers it to 3600. Set it to `0` to disable polling entirely.
</Warning>

## Creating a client

```ts theme={null}
const client = await Client.newClient(config);
```

`Client.newClient` performs the following steps in order:

<Steps>
  <Step title="Validate the config">
    Checks that `APIKey`, `BaseURL`, and `Scope` are non-empty. Throws if any required field is missing.
  </Step>

  <Step title="Normalize the polling interval">
    Clamps `PollingInterval` to the range 10–3600 seconds (unless it is `0`).
  </Step>

  <Step title="Fetch all configs (blocking)">
    Makes an HTTP `GET` request to `{BaseURL}/api/v1/scopes/{scope}/configs` and populates the in-memory cache.
  </Step>

  <Step title="Start background polling">
    If `PollingInterval > 0`, starts a timer that re-fetches configs on the configured interval.
  </Step>
</Steps>

<Note>
  The initial fetch is blocking. If the Foff API is unreachable or returns an error at startup, `newClient` throws that error immediately. Handle it at startup so your process fails fast rather than serving stale or empty configs.
</Note>

## Resolving feature configs

### `getFeatureConfig(featureName, orderedHierarchy)`

```ts theme={null}
getFeatureConfig(featureName: string, orderedHierarchy: string[]): unknown
```

Returns the config value for a feature, resolved against a hierarchy. The call is synchronous and reads from the in-memory cache — no network request is made.

```ts theme={null}
const value = client.getFeatureConfig("dark-mode", ["org-1", "team-a", "user-123"]);
```

### Hierarchy resolution algorithm

The SDK resolves from most-specific to least-specific:

1. It checks the full combination first — `org-1 + team-a + user-123`.
2. It then tries shorter prefixes — `org-1 + team-a`, then `org-1`.
3. If no override matches, it returns the feature's `"default"` value.
4. If the feature does not exist at all, it returns `null`.

This means you can define overrides at any level of your hierarchy — organisation, team, user, environment, region, service — and the SDK finds the most specific match automatically.

## Polling

When `PollingInterval` is greater than `0`, the SDK polls `GET {BaseURL}/api/v1/scopes/{scope}/configs` in the background and replaces the in-memory cache on each successful response.

* Polling errors are silently ignored. The SDK continues serving the last successfully fetched config.
* Calling `client.close()` stops the background timer and releases all resources.

### Recommended polling intervals

| Use case            | Interval |
| ------------------- | -------- |
| Near-real-time      | 10s      |
| Standard            | 30–60s   |
| Low-traffic / batch | 300–600s |

## Client lifecycle

For short-lived scripts, use a `try/finally` block to guarantee the client is closed:

```ts theme={null}
const client = await Client.newClient(config);

try {
  const value = client.getFeatureConfig("dark-mode", ["org-1", "team-a", "user-123"]);
  console.log(value);
} finally {
  // Always close to stop background polling
  client.close();
}
```

For long-lived processes such as an Express server, create the client once at startup and close it on process exit:

```ts theme={null}
const client = await Client.newClient(config);

process.on("SIGTERM", () => {
  client.close();
  process.exit(0);
});
```
