Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

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:

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:

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:

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

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:

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:

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:

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:

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:

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:

FLAG_NEW_CHECKOUT=false bun dev

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

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.

About

A simple registry-based feature flag to stop your code env dependent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages