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

@meetkai/flags

v0.2.1

Published

Evaluation client for mka1-feature-flags: per-identity cached flag lookups with isOn/stringValue helpers.

Readme

@meetkai/flags

Evaluation client for mka1-feature-flags. Your service asks "what do the flags say for this user?" once, the answer is cached per user for a configurable TTL, and isOn / stringValue read from it. No admin operations: creating flags and editing rollouts stay with the API (or the @meetkai/mka1 SDK once its flags group is published).

Server-side only. Runs on Bun and Node 18+, has no dependencies, and uses the global fetch.

Install

Install from the public npm registry; no package-download token is required:

bun add @meetkai/flags        # or: npm install @meetkai/flags

When migrating from GitHub Packages, remove the old @meetkai registry override from your project and user .npmrc or bunfig.toml, or point it to https://registry.npmjs.org. Regenerate the consumer lockfile so it resolves @meetkai/flags from npm rather than retaining a GitHub Packages tarball URL. Other private packages in the same scope may still need their existing registry configuration.

Quick start

import { FlagsClient } from "@meetkai/flags";

const flags = new FlagsClient({
  baseUrl: "https://apigw.mka1.com",
  environment: "production",
  apiKey: process.env.FLAGS_EVALUATION_KEY!, // the evaluation key generated for this environment
  ttlMs: 30_000,
  onError: (err) => console.warn("flags:", err.message),
});

// One request per user, then everything is read from the cached answer.
const ev = await flags.evaluate(userId);

if (ev.isOn("new-endpoint")) {
  // 50% rollout, with overrides pinning specific users on or off
}

const provider = ev.stringValue("auth-provider", "google"); // "google" | "facebook"
console.log(ev.reason("auth-provider"));                    // "rule:0" | "rule:1" | "default" — which variant this user landed in

apiKey is the environment-scoped bearer returned by the feature flags API's evaluation-key generation endpoint. It is not a platform or cluster-admin credential. The client requires it for every evaluation, including calls to a local server running in insecure-local mode. Keep it server-side and rotate or revoke it through the environment's admin API.

One-liners when you only need a single flag (they never throw; on failure they return the fallback):

await flags.isOn(userId, "new-endpoint");                  // false on failure
await flags.stringValue(userId, "auth-provider", "google");
await flags.numberValue(userId, "search-limit", 50);
await flags.booleanValue(userId, "beta", false);
await flags.jsonValue(userId, "retry-policy", { retries: 3 });

Rules that look at traits (plan, country, ...) need the traits sent along; each distinct trait set is cached separately:

await flags.evaluate(userId, { plan: "pro", country: "BR" });

Always send a stable identity. An empty identity is evaluated as anonymous: no overrides, and every rollout below 100% answers false.

What the helpers return

| Helper | Returns the value when | Otherwise | |---|---|---| | isOn(key) | enabled is true | false | | stringValue(key, fallback) | enabled and the value is a string | fallback | | numberValue(key, fallback) | enabled and the value is a finite number | fallback | | booleanValue(key, fallback) | enabled and the value is a boolean | fallback | | jsonValue(key, fallback) | enabled and a value is present (no shape check) | fallback | | reason(key) | always | undefined for an unknown flag |

A disabled flag returns the fallback even though the server still sends its value, so switching a flag off really switches the feature off.

Options

| Option | Default | Meaning | |---|---|---| | baseUrl | required | https://apigw.mka1.com, or http://localhost:8080 locally | | environment | required | Which environment's flags to evaluate | | apiKey | required | Environment-scoped evaluation key sent as Authorization: Bearer; required in every mode and kept server-side | | ttlMs | 30000 | How long a cached evaluation is fresh. 0 refetches every call | | maxStaleMs | 86400000 (24 h) | How long an expired entry may still be served when a refresh fails. 0 disables | | timeoutMs | 2000 | Request timeout | | cache | new MemoryCache() | Any FlagsCache (see below) | | onError | none | Called once per failed request with a FlagsError (status, code, requestId, cause) | | headers | none | Extra headers on every request; Authorization always comes from apiKey | | fetch | globalThis.fetch | Replacement transport, mainly for tests |

MemoryCache takes { maxEntries } (default 10 000; the oldest entry is evicted first).

When the server is unreachable

evaluate serves the cache while it is fresh. When it is not, one request is made (concurrent callers for the same user share it). If that request fails — network error, timeout, non-2xx, malformed body — onError is called, then:

  1. if an expired entry exists and is younger than ttlMs + maxStaleMs, it is returned with stale: true;
  2. otherwise evaluate throws a FlagsError, and the one-liners return their fallback.

So a short outage keeps every user on their last known answer instead of flipping everyone to the fallback at once.

Rotating or revoking an evaluation key makes future requests with the old key fail, but it does not remove answers already stored in this client's cache. Fresh entries remain available until ttlMs; after a failed refresh, expired entries may remain available until ttlMs + maxStaleMs under the stale-on-error policy above. Clear or replace the cache if the application must discard those answers immediately.

Custom cache

Implement FlagsCache to share evaluations across processes. Every method may be sync or async. The client decides freshness from entry.storedAt and its own ttlMs; the store only needs to keep the entry around for ttlMs + maxStaleMs so stale-on-error still works.

import { cacheKey, type CacheEntry, type CacheSetOptions, type FlagsCache } from "@meetkai/flags";

class RedisCache implements FlagsCache {
  constructor(private readonly redis: { get(k: string): Promise<string | null>; set(k: string, v: string, mode: "PX", ms: number): Promise<unknown>; del(k: string): Promise<unknown> }) {}

  async get(key: string): Promise<CacheEntry | undefined> {
    const raw = await this.redis.get("flags:" + key);
    return raw === null ? undefined : (JSON.parse(raw) as CacheEntry);
  }

  async set(key: string, entry: CacheEntry, options: CacheSetOptions): Promise<void> {
    await this.redis.set("flags:" + key, JSON.stringify(entry), "PX", options.ttlMs + options.maxStaleMs);
  }

  async delete(key: string): Promise<void> {
    await this.redis.del("flags:" + key);
  }

  async clear(): Promise<void> {
    // Scan and delete "flags:*", or use a key prefix per deployment.
  }
}

cacheKey(environment, identity, traits?) is exported so a custom store can compute the same keys the client uses (production:abc_123, or with traits production:abc_123:{"plan":"pro"}).

Releasing

Version 0.2.1 starts publication to public npm. Existing GitHub Packages releases are retained, but new releases go to https://registry.npmjs.org.

Publishing uses GitHub OIDC with Node 24 and npm trusted publishing. No NPM_TOKEN Actions secret is needed. In the npm package's trusted-publisher settings, authorize these exact values:

| Field | Value | | --- | --- | | Provider | GitHub Actions | | Organization | MeetKai | | Repository | mka1-feature-flags | | Workflow filename | publish-typescript-client.yml | | Environment | Leave empty (the job does not use a GitHub environment) | | Allowed actions | Enable direct publishing with npm publish |

Configure this before the workflow's first npm release. If the package does not exist on npm yet, an npm maintainer must first bootstrap it with an authenticated manual publish, then configure the trusted publisher. If that bootstrap uses 0.2.1, bump the package version before the next workflow release; the workflow will not overwrite it. The GitHub Packages publisher configuration does not transfer to npm. See npm trusted publishing for setup details. A local dry run checks packaging, but cannot validate the GitHub OIDC exchange or the npm-side trust configuration.

Versions on npm are immutable. To ship a change, bump version in package.json and merge to main. The Publish TypeScript client workflow tests, builds, and publishes with public access; it refuses to overwrite an existing version. The workflow can also be dispatched manually. Never put publishing credentials in package files or commit them to the repository.

Developing

cd clients/typescript
bun install
bun test
bun run typecheck
bun run build      # dist/