npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zudojs/feature-flags

v1.0.0

Published

Feature flag system with deterministic rollouts, rule engine, providers, variants, snapshots, and evaluation context.

Readme

@zudojs/feature-flags

Feature flag system with deterministic rollouts, rule engine, providers, variants, snapshots, and evaluation context.

Installation

npm install @zudojs/feature-flags

Quick Start

Flags come from a provider; createFeatureFlags evaluates them against a context.

import {
  createFeatureFlags,
  createMemoryProvider,
} from "@zudojs/feature-flags";

const flags = createFeatureFlags({
  provider: createMemoryProvider([
    {
      key: "new-ui",
      enabled: true,
      defaultValue: false,
      rules: [{ type: "percentage", percentage: 10, value: true }],
    },
    {
      key: "beta-feature",
      enabled: true,
      defaultValue: false,
      rules: [{ type: "user", users: ["user-123"], value: true }],
    },
  ]),
});

await flags.isEnabled("beta-feature", { userId: "user-123" }); // true
await flags.isEnabled("new-ui", { userId: "user-456" }); // stable per user

isEnabled is strictly boolean: a flag whose value is a string, a number or an object reports false. Use get() for a typed value, or getBoolean(key, fallback) when a missing or unreachable flag should fall back to a value you choose.

Flag definition

interface FeatureFlag {
  key: string;
  enabled: boolean; // the global kill switch
  defaultValue: FeatureFlagValue; // used whenever no rule decides
  state?: "active" | "archived" | "draft";
  visibility?: "client" | "server";
  rules?: FeatureFlagRule[]; // evaluated in order, first match wins
  dependencies?: string[]; // other flags that must be enabled
  metadata?: { expiresAt?: Date /* … */ };
}

Rules

| Type | Matches when | | ------------ | ----------------------------------------------- | | static | always | | user | context.userId is in users | | tenant | context.tenantId is in tenants | | attribute | attribute compared to value with operator | | percentage | the subject's bucket falls inside percentage | | schedule | now is between startAt and endAt | | variant | always, assigning a variant by weight |

Rules are evaluated in declaration order and the first match wins. The subject for percentage and variant is userId, then tenantId, then sessionId, then "anonymous".

Operators: equals, not_equals, contains, starts_with, ends_with, in, not_in, greater_than, greater_than_or_equal, less_than, less_than_or_equal, exists, matches.

Attribute paths use dot notation and are resolved against the context first, then context.attributes. Only own properties are traversed: __proto__, constructor and prototype never resolve, so a rule cannot accidentally (or deliberately) target everyone through the prototype chain. A matches pattern that does not compile, or is longer than 512 characters, matches nothing instead of throwing.

Rollouts and variants

import { getBucket, isInRollout, hashString } from "@zudojs/feature-flags";

Bucketing is deterministic: hash(flagKey + ":" + subject) over 10,000 buckets, so the same subject always lands in the same bucket for the same flag, and raising a percentage never removes anyone already inside it. Variant weights are applied over those same buckets, so a 90/10 split really is 90/10.

Dependencies

A flag may declare dependencies. It evaluates normally only when every dependency — transitively — exists and is enabled; otherwise the result is dependency_disabled with the declared default. Cycles resolve to disabled; a shared dependency reached down two branches is not a cycle.

evaluateFlag() on its own has no registry and cannot resolve dependencies, so it reports dependency_disabled for any flag that declares them unless the caller passes { dependenciesSatisfied: true }.

Providers

import {
  createMemoryProvider,
  createEnvironmentProvider,
  createCompositeProvider,
  createCachedProvider,
} from "@zudojs/feature-flags";

const provider = createCachedProvider(
  createCompositeProvider([
    createEnvironmentProvider({ prefix: "FEATURE_" }),
    remoteProvider,
  ]),
  { ttl: 30_000 },
);

createMemoryProvider returns a typed provider with set, delete and setAll, and it announces every change to subscribers.

Change propagation

createFeatureFlags subscribes to the provider when it offers subscribe(), so a flag flipped at the source reaches an already-loaded instance without a manual refresh(). Call flags.close() to unsubscribe, and flags.refresh() to reload explicitly from a provider that cannot announce changes.

Failure behaviour

| Situation | Result | | -------------------------------- | ------------------------------------------------------ | | Flag not found | not_found, value undefined; isEnabled is false | | Flag not found, throwOnMissing | throws FeatureFlagNotFoundError | | Flag disabled or draft | disabled, the declared default | | Flag archived or expired | expired, the declared default | | Dependency not satisfied | dependency_disabled, the declared default | | Provider unreachable | error, reported to onError; never enabled |

An unreachable store never enables a flag. Set throwOnProviderError: true to own the failure yourself instead.

const flags = createFeatureFlags({
  provider,
  onError: (error, source) => logger.error({ error, source }, "flag store"),
});

Snapshots

const snapshot = await flags.snapshot({ userId });

Only flags explicitly marked visibility: "client" are included — a snapshot is shipped to a browser, so a flag that declares no visibility is withheld.

Errors

FeatureFlagError is the base: FeatureFlagNotFoundError · FeatureFlagProviderError · FeatureFlagEvaluationError · FeatureFlagRuleError · FeatureFlagDependencyError · FeatureFlagConfigurationError · FeatureFlagTypeError.

Use Cases

  • Gradual feature rollouts
  • A/B testing
  • Kill switches
  • Beta feature access