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

@application-kit/feature-flags

v0.3.0

Published

Фичефлаги из окружения: набор флагов параметром, порт с адаптером окружения, SSR-состояние для Vue и фейк для спек

Readme

@application-kit/feature-flags

Фичефлаги из окружения: набор флагов объявляет приложение, пакет даёт порт чтения с адаптером окружения, SSR-состояние для Vue и фейк для спек. Флагов продукта в пакете нет.

Два правила держат всё остальное:

  1. Имя флага объявляется ровно один раз — в наборе приложения. Из него выводится тип имени, из него же адаптер узнаёт, какие переменные окружения существуют.
  2. Состояния «по умолчанию» у флага нет. Значение каждого флага приходит из окружения, а пропущенная переменная роняет старт процесса. Дефолт в коде сделал бы забытую настройку окружения неотличимой от сознательного выбора — а ищут как раз её, когда стенд ведёт себя не так, как договаривались.

Установка

yarn add @application-kit/feature-flags @libs-for-dev/nestjs-config-module

Peer: @libs-for-dev/nestjs-config-module (подпуть /parse-boolean — разбор булевых переменных, без зависимостей); vue — только для входа /vue, @nestjs/common — только для входа /nest.

Входы

| Вход | Что внутри | Зависимости | |---|---|---| | @application-kit/feature-flags | defineFeatureFlags, реестр FeatureFlagRegistryInterface, порт FeatureFlagsInterface, токен FEATURE_FLAGS, адаптер EnvironmentFeatureFlags, ошибки | изоморфно, без Nest и DOM | | @application-kit/feature-flags/nest | FeatureFlagsRootModule | @nestjs/common | | @application-kit/feature-flags/vue | useFeatureFlag, ключ FEATURE_FLAG_STATE, parseFeatureFlagState, createFeatureFlagChannel | vue | | @application-kit/feature-flags/testing | FakeFeatureFlags | — |

Корень грузится и в клиентском бандле (набор нужен разбору SSR-состояния), поэтому связка с Nest — отдельный вход.

Набор флагов

Тип и значение объявляются рядом, в одном файле приложения:

import { defineFeatureFlags } from '@application-kit/feature-flags'

declare module '@application-kit/feature-flags' {
  interface FeatureFlagRegistryInterface {
    NEW_CHECKOUT: true
  }
}

export const featureFlags = defineFeatureFlags({
  NEW_CHECKOUT: { isPublic: true },
})

Ключи записи проверяются против реестра в обе стороны: флаг реестра без записи и запись без флага в реестре не компилируются.

Единственное решение в записи — isPublic: уезжает ли флаг на клиент в SSR-состоянии. Наружу идёт явный список, а не всё состояние: серверный флаг (доступ к интеграции, аварийный рубильник) в разметке страницы — это утечка плана работ.

Префикс переменных — опция, по умолчанию FEATURE_: defineFeatureFlags({ … }, { environmentPrefix: 'FLAG_' }). Пустой префикс — RangeError.

Окружение

Имя переменной — префикс плюс имя флага: FEATURE_NEW_CHECKOUT. featureFlags.readEnvironment(source) разбирает окружение в состояние:

| Значение | Результат | |---|---| | false, 0, пустая строка (регистр и пробелы по краям не важны) | выключен | | любая другая непустая строка — true, 1, on, yes | включён | | переменной нет | отказ — MissingFeatureFlagError со списком недостающих переменных | | переменная с префиксом, которой нет в наборе | отказ — UnknownFeatureFlagError со списком |

Разбор значения — parseBoolean конфиг-пакета: список выключающих строк один на все булевы переменные. Обе проверки собирают полный список до броска, чтобы отказ назвал все проблемы разом; незнакомая переменная проверяется первой — опечатка даёт сразу и лишнее имя, и недостающее.

Связка с Nest

Корневой модуль процесса подключает порт одной строкой:

import { FeatureFlagsRootModule } from '@application-kit/feature-flags/nest'

@Module({ imports: [FeatureFlagsRootModule.forRoot(featureFlags)] })
export class CommonModule {}

forRoot разбирает process.env сразу, на импорте модуля-носителя: отказ приходит до сборки графа, а не на первом запросе. Поэтому окружение обязано быть готово до импорта: правка из beforeAll спеки опоздает. Модуль глобальный: FEATURE_FLAGS виден всему графу без своей регистрации.

Чтение на сервере

Через порт по токену, а не чтением process.env:

import type { FeatureFlagsInterface } from '@application-kit/feature-flags'
import { FEATURE_FLAGS } from '@application-kit/feature-flags'

export class SomeHandler {
  public constructor(@Inject(FEATURE_FLAGS) private readonly featureFlags: FeatureFlagsInterface) {}

  public execute(): void {
    if (this.featureFlags.isEnabled('NEW_CHECKOUT')) { /* … */ }
  }
}

Чтение синхронное, и это решение: асинхронный isEnabled заставил бы ждать каждого потребителя, включая шаблон Vue, где await в разметке невозможен. Адаптер с хранением в БД остаётся возможным — он держит обновляемый снимок в памяти и встаёт под тот же токен без правок потребителей.

Клиент (Vue)

Публичный срез уезжает на страницу SSR-состоянием:

С @application-kit/vue-ssr состояние едет каналом — ставится до рендера и уезжает на страницу одним значением:

const featureFlagChannel = createFeatureFlagChannel(featureFlags)

// сервер, опция createSsrEntry
channels: () => [featureFlagChannel.withValue(featureFlagsPort.publicState())],

// клиент, опция hydrateSsrApplication
channels: [featureFlagChannel],

Без него — те же шаги руками:

// сервер, перед renderToString
const state = featureFlagsPort.publicState()
app.provide(FEATURE_FLAG_STATE, state)
// … состояние сериализуется в страницу строкой (JSON.stringify)

// клиент, до app.mount()
app.provide(FEATURE_FLAG_STATE, parseFeatureFlagState(featureFlags, window.ssrFeatureFlags))

В компоненте:

<script setup lang="ts">
import { useFeatureFlag } from '@application-kit/feature-flags/vue'

const isNewCheckoutEnabled = useFeatureFlag('NEW_CHECKOUT')
</script>

<template>
  <NewCheckout v-if="isNewCheckoutEnabled" />
</template>
  • Без provide всё выключено: до прихода SSR-состояния помеченного фрагмента на странице быть не должно — показать лишнее и убрать при гидрации хуже, чем не показать.
  • Имя, которого нет в предоставленном состоянии, — UnknownFeatureFlagNameError со списком известных: имя из данных без проверки дало бы undefined, и фрагмент молча исчез бы со страницы.
  • parseFeatureFlagState не бросает ни на какой форме испорченного провода: разбор стоит до app.mount(), и бросок убил бы весь бутстрап клиента. Незнакомое имя на проводе игнорируется, пропущенное и не-булево — выключено: страница старой версии не роняет клиент новой во время выкатки.

Тесты потребителя

Через фейк порта, без process.env и vi.stubEnv. База фейка — «всё выключено», поэтому спека, которой флаг нужен включённым, говорит это явно:

import { FakeFeatureFlags } from '@application-kit/feature-flags/testing'

const port = new FakeFeatureFlags(featureFlags, { NEW_CHECKOUT: true })

Снимок publicState() фейка строится как у адаптера: непубличные флаги в нём выключены. Для компонента — provide состояния:

mount(SomeComponent, {
  global: { provide: { [FEATURE_FLAG_STATE]: featureFlags.stateOf((name) => name === 'NEW_CHECKOUT') } },
})

Типовые отказы

| Симптом | Причина | Что делать | |---|---|---| | MissingFeatureFlagError: Missing feature flags in environment: FEATURE_X на старте | флаг есть в наборе, переменная в окружении не задана | дописать переменную в окружение каждого процесса | | UnknownFeatureFlagError: Unknown feature flags in environment: FEATURE_X на старте | переменная есть, флага в наборе нет — опечатка или флаг снят из кода и забыт в окружении | поправить имя или убрать переменную | | UnknownFeatureFlagNameError: Unknown feature flag requested: X. Known flags: … при рендере | опечатка в имени у useFeatureFlag | поправить имя по списку в тексте ошибки | | Переменная выставлена, а на клиенте флаг выключен | флаг непубличный (isPublic: false) | проверить isPublic в наборе |

Чего здесь нет

Адаптера с хранением в БД, процента раскатки, таргетинга по пользователю, UI управления флагами.