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

@tehw0lf/yaft

v0.0.22

Published

YaFT - Feature Toggles using Go&PostgreSQL or any source!

Downloads

1,645

Readme

YaFT for TypeScript


This provides a client for YaFT which aims to bring simple feature toggles for Methods and Classes to Typescript.


Installation

npm install @tehw0lf/yaft

The example API providers use axios and declare it as an optional peer dependency, so install it too if you use them:

npm install @tehw0lf/yaft axios

Initialization

To be able to use YaFT, implement and set a FeatureProvider on the abstract Base Class, or copy and adapt one of the example providers.

In this example a local json file with the Feature data type is used, but in theory any data type can be used since the Provider implements the isEnabled function which can be overridden as necessary.

import { FeatureToggleBase } from "@tehw0lf/yaft";
import { LocalStorageFeatureProvider } from "./provider";

FeatureToggleBase.featureProvider = new LocalStorageFeatureProvider(
  "./test-feature.json"
);

This repo contains examples for two provider types: API Providers for boolean and Feature and LocalStorage Providers for boolean and Feature. The APi providers are designed to work with the default Go implementation of YaFT, but can be implemented through their interface:

export interface FeatureProvider<T> {
  apiUrl?: string;
  baseUUID?: string;
  data: Record<string, T>;
  initConfig(configPathOrUrl: string): void;
  isEnabled(key: string): boolean;
}

Usage

To manage anything with a feature toggle, decorate it with the feature's key:

import { FeatureToggle } from "@tehw0lf/yaft";

@FeatureToggle("myKey")
class MyClass {}
import { FeatureToggle } from "@tehw0lf/yaft";

@FeatureToggle("myKey")
myMethod() {}

If the feature is enabled, the original class/method will be used.

If the feature is disabled, a fallback can be provided:

import { FeatureToggle } from "@tehw0lf/yaft";

@FeatureToggle("myKey", MyFallbackClass)
class MyClass {}
import { FeatureToggle } from "@tehw0lf/yaft";

@FeatureToggle("myKey", myFallbackMethod)
myMethod() {}

Otherwise, or if the feature does not exist, the class/method gets replaced with an empty object/an empty function.

Collection Hash

The default YaFT API provides a collection hash to efficiently check whether the locally cached features are still up to date. See API provider examples for details. This can be scheduled to automatically update feature data in the background.

Since 0.0.21 the API providers have two ways to refresh:

  • refresh() reports the outcome. It resolves to true when new data was loaded and false when the group was unchanged, and rejects when the backend cannot be reached or answers with something that is not a hash or not a toggle group. The previous data stays in place either way.
  • getCollectionHash(url) is the quiet variant for timers: it logs a failure instead of rejecting.

Refreshes run one after another. The constructor already starts one, so a refresh() right after it waits for that one and then usually resolves to false: the group is loaded and unchanged. If the constructor's refresh failed, this one tries again and rejects if it fails too.

const provider = new ApiServiceFeatureProvider(apiUrl, groupUuid);

// At startup, or wherever a failure should be seen:
await provider.refresh();

// In the background:
setInterval(() => provider.getCollectionHash(`${apiUrl}/collectionHash/${groupUuid}`), 60_000);

A /collectionHash answer without a hash now fails the refresh. Before, it was recorded as undefined, which matched every later answer without one, so the provider stopped refreshing without saying so.

Feature Data Type

The default data type for YaFT is Feature. A feature has a key string, a value string representing a boolean and optional activeAt and disabledAt date strings.

export type Feature = {
  key: string;
  value: string;
  activeAt: string;
  disabledAt: string;
  tags?: string[];
};

Changed in 0.0.22. A response whose key or value is not a string, such as "value": true, no longer has it converted with String(): the field is not set, so the feature is off and an entry without a string key is skipped. JSON booleans belong in the boolean providers.

Evaluation rules

These rules are not this library's own: they are yaft-conformance, the shared specification every YaFT implementation is checked against. This port passes the whole suite (see Conformance below), so a feature evaluates identically here and in any other port.

A feature is on when all of the following hold. evaluate is exported, so the rules can be applied directly to a feature without going through a provider.

  • The value is exactly "true". "TRUE", "1" and "" are off -- the value is stored as a string, and anything else would be a silent disagreement between backend and client.
  • activeAt has passed, if set. The bound is inclusive: at exactly activeAt the feature is on.
  • disabledAt has not been reached, if set. This bound is exclusive: at exactly disabledAt the feature is off.

A missing feature is off. Unset, null or unparseable dates are ignored rather than treated as an error, and never throw.

API providers and a failing backend

ApiServiceFeatureProvider and ApiServiceBooleanProvider keep their data when a refresh fails — and, since 0.0.18, also when /features answers 200 with something that is not a toggle group (null, an array, a proxy's error page). Before, that replaced the data with nothing and switched every feature off. A failed fetch is now also retried on the next getCollectionHash; before, one failure stopped refreshing until the backend changed again. normaliseGroup is exported for providers of your own.

Boolean shape

The boolean providers hold { "myToggle": true } and have no time logic. Only the JSON boolean true is on. A JSON false is kept in the data and reads as off. Any value that is not a boolean -- "true", "false", 1, null -- is dropped when the data loads, so its key is missing from the data and reads as off. normaliseBooleans is exported and applies the same rule.

Changed in 0.0.17. Before, isEnabled returned whatever was stored, and a caller testing it with if turned the string "false" on, because a non-empty string is truthy. A configuration that relied on string or number values has to switch to real booleans.

Date format

Dates must be RFC 3339 with an offset (2026-09-18T15:00:00Z or 2026-09-18T15:00:00+02:00). Anything else -- a bare date such as 2026-09-18, or a timestamp without an offset -- is ignored and logged as a warning.

This is stricter than Date.parse, on purpose: JavaScript reads a bare date as UTC midnight and an offset-less timestamp as local time, while most other languages read both as local. Accepting them would make a feature flip at a different instant depending on which client evaluated it.

Testing with a fixed time

Both Feature providers take an optional clock, so a test can evaluate against a fixed instant instead of the current time:

import { LocalStorageFeatureProvider } from "./provider";

const provider = new LocalStorageFeatureProvider(
  "./test-feature.json",
  () => Date.parse("2026-09-18T12:00:00Z")
);

The clock defaults to the system time, so existing code needs no change.

Conformance

The rules above are specified once, language-neutrally, in yaft-conformance, and this port is tested against that suite rather than only against its own expectations.

The version is pinned in conformance.lock:

version=v4.0.0
sha256=8e9dd45e897d0527b28f29a31d35736e20b4429b7280d21e5755aeaaaf4bf3c7

npm test fetches that release, verifies the checksum and unpacks it before Jest runs, so there is no separate step to forget and no way to get a green run against stale cases. Upgrading the suite is a one-line change to that file, visible in review.

The checksum is not decoration: a Git tag can be moved, and without verifying the asset a port's tests could change with no diff at all.

The adapter lives in src/test/conformance-adapter/. It fails loudly on a case whose target, toggle or expected it does not implement, rather than skipping it -- a silently skipped case is a rule that nothing enforces.

Licenses

  • Code: MIT License
  • Logo/Branding: All rights reserved