# Flagkeeper

Flagkeeper is a type-safe TypeScript feature flag library for Node.js and React. Define environment-aware defaults in an application-owned registry, resolve flags on the server, and pass typed snapshots to React.

## Install

```sh
bun add flagkeeper
```

## Runtime Compatibility

Flagkeeper has no runtime dependency beyond environment access during
resolution.

| Runtime | Compatibility |
| --- | --- |
| Node.js | Uses the default `process.env` lookup. |
| Bun | Uses the default `process.env` lookup. |
| Cloudflare Workers | Pass the Worker `env` object in the resolve options. |
| Vercel Edge Runtime, Deno Deploy, and other edge runtimes | Pass an `env` object in the resolve options. |
| Browser/client React | `FlagProvider`, `Flag`, and `useFlag` consume an already-resolved snapshot, so they do not need environment access. |

## Define a Registry

Define the flags owned by your application:

```ts
import { defineFlags } from "flagkeeper";

export const flags = defineFlags({
  NEW_CHECKOUT: {
    description: "Enable the new checkout experience.",
    defaults: { development: true, staging: true, production: false },
  },
  DASHBOARD_REDIRECT: {
    description: "Redirect users to the new dashboard.",
    defaults: { development: true, staging: true, production: false },
  },
});
```

Each flag requires a `description` plus `development` and `production` defaults.
Additional environment defaults, such as `staging`, are optional.

## Type Definitions

Flagkeeper exports the public types used by the registry and resolved snapshots:

```ts
import type {
  EnvSource,
  FlagDefinition,
  FlagDefaults,
  FlagEnvironment,
  FlagKey,
  FlagRegistry,
  FlagSnapshot,
  ResolveOptions,
} from "flagkeeper";
```

Use `FlagKey` and `FlagSnapshot` with your registry type when you want helpers
to stay in sync with the flags you declared:

```ts
import type { FlagKey, FlagSnapshot } from "flagkeeper";
import { flags } from "./flags";

type AppFlagKey = FlagKey<typeof flags>;
type AppFlagSnapshot = FlagSnapshot<typeof flags>;
```

`FlagDefaults` requires `development` and `production` values and allows extra
environment keys. `EnvSource` is the env-object shape accepted by
`resolveFlag`, `resolveAllFlags`, and `resolveEnvironment`.

## Server Usage

```ts
import { flags } from "./flags";
import { resolveAllFlags, resolveFlag } from "flagkeeper";

const enabled = resolveFlag(flags, "NEW_CHECKOUT");
const snapshot = resolveAllFlags(flags);
```

Unknown keys are rejected by TypeScript when using a typed registry.

## Cloudflare Workers

After `wrangler types` generates `Env` from your Worker bindings, pass the
Worker `env` object to resolution:

```ts
import { flags } from "./flags";
import { resolveAllFlags } from "flagkeeper";

export default {
  async fetch(request, env): Promise<Response> {
    const snapshot = resolveAllFlags(flags, { env });
    const pathname = new URL(request.url).pathname;

    return Response.json({ pathname, flags: snapshot });
  },
} satisfies ExportedHandler<Env>;
```

## React Usage

Create typed React bindings once from your application's registry, then resolve
on the server and pass its snapshot to the provider:

```tsx
import { flags } from "./flags";
import { resolveAllFlags } from "flagkeeper";
import { createFlagContext } from "flagkeeper/react";

export const { FlagProvider } = createFlagContext<typeof flags>();

<FlagProvider flags={resolveAllFlags(flags)}>
  {children}
</FlagProvider>;
```

`createFlagContext` also returns `Flag` and `useFlag` for client components:

```tsx
import { Flag, useFlag } from "./flag-context";

const enabled = useFlag("NEW_CHECKOUT");

<Flag flag="NEW_CHECKOUT">Enabled content</Flag>;
```

If you are new to React, wrap hooks and JSX in components:

```tsx
import type { ReactNode } from "react";
import { Flag, FlagProvider, useFlag } from "./flag-context";
import { flags } from "./flags";
import { resolveAllFlags } from "flagkeeper";

export function AppFlagsProvider({ children }: { readonly children: ReactNode }) {
  return <FlagProvider flags={resolveAllFlags(flags)}>{children}</FlagProvider>;
}

export function CheckoutStatus() {
  const enabled = useFlag("NEW_CHECKOUT");

  return (
    <section>
      <p>{enabled ? "New checkout is enabled." : "New checkout is disabled."}</p>
      <Flag flag="NEW_CHECKOUT">Enabled content</Flag>
    </section>
  );
}
```

If the provider is absent or the key is not present in the snapshot, client reads return disabled by default.

## Environment Resolution

Flag resolution reads NODE_ENV only:

- exactly `development` uses the `development` default
- a value matching an additional declared default, such as `staging`, uses that default
- every other value, including unset and `test`, uses the `production` default

For example, add a dedicated staging default when it should differ from production:

```ts
defaults: { development: true, staging: true, production: false }
```

## Local Overrides

Use local environment variables to force a flag on or off while developing
without changing the registry defaults. The variable name is `FLAG_` plus the
exact flag key:

```sh
FLAG_NEW_CHECKOUT=false bun dev
```

For repeated local work, put overrides in your app's ignored local env file,
such as `.env.local`:

```env
FLAG_NEW_CHECKOUT=true
FLAG_DASHBOARD_REDIRECT=false
```

Keep local override files out of git and remove overrides when validating
environment defaults. Override values are case-insensitive `true` or `false`.
Invalid override values fall back to the current environment default and warn
once.
