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

@astrapi69/feature-strategy

v0.1.2

Published

Framework-agnostic feature gating and component authorization strategies with three states: active, disabled, hidden

Readme

@astrapi69/feature-strategy

Framework-agnostic feature gating with three states: active, disabled, hidden. Zero runtime dependencies. ESM and CJS.

Install

npm install @astrapi69/feature-strategy

Usage

import {
    FeatureRegistry,
    CompositeStrategy,
    StaticFeatureStrategy,
    RoleBasedFeatureStrategy
} from '@astrapi69/feature-strategy';

interface AppContext {
    user?: { roles: readonly string[] };
}

const registry = new FeatureRegistry<AppContext>();

registry.registerAll([
    { id: 'export', defaultState: 'active' },
    { id: 'admin-panel', defaultState: 'hidden' },
    { id: 'tts', defaultState: 'active' }
]);

registry.setStrategy(
    new CompositeStrategy<AppContext>([
        new StaticFeatureStrategy({ tts: 'hidden' }, { tts: 'Requires a TTS engine' }),
        new RoleBasedFeatureStrategy<AppContext>(
            { 'admin-panel': { roles: ['admin'], reason: 'Administrators only' } },
            (context) => context?.user?.roles ?? []
        )
    ])
);

registry.getState('admin-panel', { user: { roles: ['admin'] } });
registry.getState('tts');
registry.getReason('tts');

The design principle: defaults plus deviations

This is the part worth internalizing before writing your first strategy. Descriptors carry the normal state of a feature. Strategies contain ONLY the deviation rules and abstain from everything else by returning undefined. The registry resolves in this order:

  1. the strategy verdict, when it returns one
  2. the descriptor defaultState, when the strategy abstains
  3. hidden for unknown feature ids (fail closed)

Do not write a strategy as a total function that returns a verdict for every feature. If most of your features are always active, they should appear only as descriptors with defaultState: 'active' and never in a strategy. A strategy with a catch-all return 'active' branch duplicates your feature list, turns the descriptor defaults into dead code, and silently disables the fail-closed behavior for unknown ids.

When several strategies are combined through CompositeStrategy, the most restrictive non-abstaining verdict wins: hidden beats disabled beats active. Abstention is what makes this composition work, since each strategy only speaks up about the features it actually governs. Note that per-feature outcomes configured inside a single strategy, such as the missingState of a role requirement, are that strategy's verdict only: composition can escalate the result to a more restrictive state when another strategy demands it, but it can never soften it.

Conditions must be cheap and pure

Evaluation is lazy and happens on demand: every getState call evaluates the strategy, and consumers such as the React adapter call it on render. CompositeStrategy.getReason additionally re-evaluates getState per inner strategy to find the winning verdict.

For static maps this is a lookup and costs nothing. For ConditionalFeatureStrategy, write conditions as synchronous, pure lookups on the context object: no async work, no DOM access, no storage reads, no computation. Anything dynamic belongs in the context, which the application builds once and passes in, not in the condition.

Strategies

StaticFeatureStrategy maps feature ids to fixed states. ConditionalFeatureStrategy evaluates rules against a context. RoleBasedFeatureStrategy resolves states from role requirements through an application supplied role extractor, without imposing a user model. CompositeStrategy combines any of the above.

License

MIT