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

@velajs/feature-flags

v1.33.0

Published

Edge-first, driver-based feature flags for the Vela framework

Readme

@velajs/feature-flags

Edge-first, driver-based feature flags for the Vela framework.

Runs on Cloudflare Workers, Deno, Bun, Node 20+, and Vercel Edge — the package source is edge-pure (no node:*, Buffer, or process). Backends plug in through one small FeatureFlagDriver contract; an in-memory driver ships in the box and doubles as the test fake.

Install

pnpm add @velajs/feature-flags

Quick start

import { APP_GUARD } from '@velajs/vela';
import { FeatureFlagGuard, FeatureFlagsModule, FeatureFlagsService, memoryFlagDriver } from '@velajs/feature-flags';

@Module({
  imports: [
    FeatureFlagsModule.forRoot({
      drivers: [memoryFlagDriver({ values: { 'new-checkout': false } })],
      manifest: { 'new-checkout': false, layout: 'v1' },
      context: (ctx) => ({ userId: ctx.get('userId') }), // merged into every evaluation
    }),
  ],
  providers: [{ provide: APP_GUARD, useExisting: FeatureFlagGuard }],
})
class AppModule {}

// Inject and evaluate — evaluation never throws.
class CheckoutService {
  constructor(@Inject(FEATURE_FLAG_TOKENS.Service) private readonly flags: FeatureFlagsService) {}

  async run() {
    if (await this.flags.getBooleanValue('new-checkout')) { /* … */ }
    const layout = await this.flags.getStringValue('layout'); // manifest default when unset
  }
}

forRootAsync({ inject, useFactory }) is also generated for you.

Hiding routes behind a flag

import { FeatureFlag } from '@velajs/feature-flags';

@Controller('/checkout')
class CheckoutController {
  @FeatureFlag('new-checkout')                       // 404 when off (route looks hidden)
  @Get('/v2') v2() { /* … */ }

  @FeatureFlag('beta', { onDisabled: 'forbidden' })  // 403 when off
  @Get('/beta') beta() { /* … */ }
}

FeatureFlagsModule exports FeatureFlagGuard; the quick start explicitly aliases it to APP_GUARD. Importing the module alone does not enforce @FeatureFlag(). For selected routes, use @UseGuards(FeatureFlagGuard) on the controller or handler. Avoid attaching the same guard globally and locally, which evaluates it twice. isGlobal: true only makes exported providers visible to every module.

The route guard opens only when the driver returns the literal boolean true and evaluation completed without error. Non-boolean driver output, a missing request context, a throwing context resolver, or an unavailable evaluator all deny with the decorator's configured 404/403 behavior. Manifest keys use own-property lookup, and per-call targeting cannot replace trusted identity fields supplied by the request-context resolver.

Feature flags control rollout and presentation; they do not grant authority. Keep authentication, tenant membership, ownership checks, and permission guards on every flagged operation. A client able to guess or observe a flag value must still be unable to perform an unauthorized action.

Drivers

The in-memory driver ships here; runtime-specific drivers live in their platform packages (@velajs/cloudflare ships Flagship-binding and KV drivers), all implementing the same contract:

export interface FeatureFlagDriver {
  readonly name: string;
  getBoolean(key: string, fallback: boolean, ctx?: FlagContext): Promise<boolean>;
  getString(key: string, fallback: string, ctx?: FlagContext): Promise<string>;
  getNumber(key: string, fallback: number, ctx?: FlagContext): Promise<number>;
  getObject(key: string, fallback: object, ctx?: FlagContext): Promise<unknown>;
}

A driver returns the caller's fallback for unresolved keys. Unexpected failures may reject; the service absorbs them into the same fallback and logs a warning.

Validating object flags

Object drivers return unknown. Pass a parser and an explicit typed fallback to the service; its result type is inferred from the parser. A failed read or parse returns the fallback, and getObjectDetails reports parser failures as ERROR.

const parseLayout = (value: unknown): { columns: number } => {
  if (
    typeof value !== 'object' || value === null ||
    !('columns' in value) || typeof value.columns !== 'number'
  ) {
    throw new TypeError('layout.columns must be a number');
  }
  return { columns: value.columns };
};

const layout = await flags.getObjectValue('layout', parseLayout, { columns: 2 });
const details = await flags.getObjectDetails('layout', parseLayout, { columns: 2 });

A schema's .parse function can be passed directly. Object evaluations require the fallback because an absent flag cannot produce an arbitrary application type. Primitive reads still use matching manifest defaults. all() evaluates manifest objects as broad object values; use a parsed object method for domain-specific fields.

Typed flag keys

Augment FeatureFlagRegistry to type the service methods and the @FeatureFlag() decorator:

declare module '@velajs/feature-flags' {
  interface FeatureFlagRegistry {
    'new-checkout': boolean;
    beta: boolean;
    layout: string;
  }
}

Testing

@velajs/feature-flags/testing re-exports the memory driver (the fake) plus a zero-DI helper:

import { createTestFeatureFlags } from '@velajs/feature-flags/testing';

const { service, driver } = createTestFeatureFlags({ 'new-checkout': true });
expect(await service.getBooleanValue('new-checkout')).toBe(true);
driver.set('new-checkout', false);

Evaluation metadata

A driver can implement getBooleanDetails, getStringDetails, getNumberDetails and getObjectDetails alongside its value methods. The service calls the matching method once and preserves reason, variant, errorCode, and errorMessage. Reasons are provider-defined strings. Providers without details report UNKNOWN; a value equal to the fallback is not evidence of either a hit or a miss. Missing native reasons also become UNKNOWN. Malformed values or metadata produce the caller fallback with reason: 'ERROR' and errorCode: 'GENERAL'.

A provider result with an error code or reason: 'ERROR' always returns the caller's fallback, including object fallbacks without parsing them again. Route guards explicitly use false as the fallback and deny any error code, even when its reason is DEFAULT. Value-only providers must honor the supplied fallback; the service cannot reconstruct errors they conceal. Feature flags are rollout controls and do not replace application authorization.

The Cloudflare Flagship driver validates targeting context as strings, finite numbers, and booleans. Nested objects, arrays, null, undefined attributes and non-finite numbers fail with INVALID_CONTEXT on detail reads before contacting the binding. Trusted request identity still wins when contexts are merged.

const result = await flags.getBooleanDetails('new-navigation', false, { plan: 'trial' });
// Native details may include TARGETING_MATCH, a variant, or FLAG_NOT_FOUND.
if (result.errorCode !== undefined) {
  // The value is the supplied fallback; report the code through application telemetry.
}

Migration: detail reads from drivers without metadata now return UNKNOWN instead of the previously synthesized STATIC. Accept provider-defined reason strings and use errorCode as well as reason === 'ERROR' when checking failure. See the native Flagship contract and failure behavior.