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

@0disoft/openfeature-local-provider

v0.15.0

Published

Local OpenFeature provider for JSON/YAML flag snapshots, CLI validation, file reload/watch, typed evaluation, environment overrides, bucketing, replay fixtures, redacted audit events, and lockable rotating file audit sinks.

Readme

@0disoft/openfeature-local-provider

Local OpenFeature provider for JSON/YAML flag snapshots, typed evaluation, explicit CLI validation, environment overrides, deterministic rollout bucketing, replay fixtures, and redacted audit logs.

Install

npm install @0disoft/openfeature-local-provider @openfeature/server-sdk

Quick Start

import { OpenFeature } from "@openfeature/server-sdk";
import { createLocalProvider, parseJsonFlagSnapshot } from "@0disoft/openfeature-local-provider";

const snapshot = parseJsonFlagSnapshot(
  JSON.stringify({
    schemaVersion: 1,
    flags: {
      "checkout.enabled": {
        type: "boolean",
        defaultVariant: "on",
        variants: {
          on: true,
          off: false
        }
      }
    }
  })
);

await OpenFeature.setProviderAndWait(createLocalProvider({ snapshot }));

const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue("checkout.enabled", false);

Percentage Rollout

import { OpenFeature } from "@openfeature/server-sdk";
import { createLocalProvider, parseJsonFlagSnapshot } from "@0disoft/openfeature-local-provider";

const snapshot = parseJsonFlagSnapshot(
  JSON.stringify({
    schemaVersion: 1,
    flags: {
      "checkout.rollout": {
        type: "boolean",
        defaultVariant: "off",
        variants: {
          on: true,
          off: false
        },
        rollout: [
          {
            variant: "on",
            percentage: 25,
            seed: "checkout-rollout-v1"
          }
        ]
      }
    }
  })
);

await OpenFeature.setProviderAndWait(createLocalProvider({ snapshot }));

const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue("checkout.rollout", false, {
  targetingKey: "account-123"
});

Rollout selection is deterministic for the same flag key, targeting key, and seed.

YAML Snapshots

import { OpenFeature } from "@openfeature/server-sdk";
import { createLocalProvider, parseYamlFlagSnapshot } from "@0disoft/openfeature-local-provider";

const snapshot = parseYamlFlagSnapshot(`
schemaVersion: 1
flags:
  checkout.enabled:
    type: boolean
    defaultVariant: "on"
    variants:
      "on": true
      "off": false
`);

await OpenFeature.setProviderAndWait(createLocalProvider({ snapshot }));

YAML input must parse into the same schema v1 snapshot contract used by JSON input. Snapshot objects, flag definitions, and rollout rules reject unknown fields; flag names, variant names, and metadata keys remain caller-defined.

CLI Validation

npx -p @0disoft/openfeature-local-provider openfeature-local-provider validate ./flags.yaml
npx -p @0disoft/openfeature-local-provider openfeature-local-provider validate ./flags.json --json

The CLI is read-only. It validates local JSON/YAML snapshots, prints a small summary, and exits with 0 for success, 1 for snapshot validation failure, or 2 for usage errors.

File Loading And Reload

import { OpenFeature } from "@openfeature/server-sdk";
import {
  createReloadableLocalProvider,
  loadFlagSnapshotFile,
  watchFlagSnapshotFile
} from "@0disoft/openfeature-local-provider";

const path = new URL("./flags.yaml", import.meta.url);
const provider = createReloadableLocalProvider({
  snapshot: await loadFlagSnapshotFile(path)
});

await OpenFeature.setProviderAndWait(provider);

const watcher = await watchFlagSnapshotFile({
  path,
  onSnapshot(snapshot) {
    provider.updateSnapshot(snapshot);
  },
  onError(error) {
    console.warn("Flag reload failed", error);
  }
});

loadFlagSnapshotFile supports .json, .yaml, and .yml files by extension. Snapshot files are limited to 10 MiB by default; pass maxBytes when a local process needs a different limit. The loader uses a bounded read from one open file handle, so a replacement or file growth cannot bypass that limit. Watcher reload failures are reported through onError and do not replace the last valid snapshot. Evaluation never reads from disk on the flag resolution path. macOS combines native file and directory watchers so direct writes and atomic replacements use their strongest event source, and rearms direct file watching after replacement. Windows file watching uses path polling internally to avoid unstable native file-event behavior on Node.js 24. Native watcher and reload errors are reported through onError. Event-driven reloads suppress callbacks for an unchanged parsed snapshot; explicit reload() calls still invoke onSnapshot after successful validation.

Call watcher.close() during process shutdown when the file watcher is no longer needed.

Environment Overrides

import { OpenFeature } from "@openfeature/server-sdk";
import { createLocalProvider, parseJsonFlagSnapshot } from "@0disoft/openfeature-local-provider";

const snapshot = parseJsonFlagSnapshot(
  JSON.stringify({
    schemaVersion: 1,
    flags: {
      "checkout.enabled": {
        type: "boolean",
        envVar: "CHECKOUT_ENABLED",
        defaultVariant: "on",
        variants: {
          on: true,
          off: false
        }
      }
    }
  })
);

await OpenFeature.setProviderAndWait(
  createLocalProvider({
    snapshot,
    env: {
      CHECKOUT_ENABLED: "false"
    }
  })
);

Environment variables are explicit. The provider does not invent variable names from flag keys. Explicit JSON overrides are limited to 10 MiB by default; pass maxOverridesJsonBytes when creating overrides or providers that need a different local limit.

File Audit Sink

import { join } from "node:path";
import {
  createFileAuditSink,
  createLocalProvider,
  parseJsonFlagSnapshot
} from "@0disoft/openfeature-local-provider";

const snapshot = parseJsonFlagSnapshot(snapshotJson);
const auditSink = createFileAuditSink({
  path: join(process.cwd(), "var", "audit", "openfeature.jsonl"),
  maxBytes: 10 * 1024 * 1024,
  maxFiles: 5,
  maxQueueSize: 1000,
  queueOverflowPolicy: "reject",
  lock: true
});
const provider = createLocalProvider({
  snapshot,
  auditSink,
  auditRedaction: {
    contextKeys: "none"
  }
});

await auditSink.flush?.();

Audit events are redacted before they reach the sink. Provider audit writes are non-blocking by default, and file write failures are reported through the OpenFeature logger without changing the evaluated flag value.

New audit directories and files use private 0700 and 0600 modes on POSIX systems. The final audit path must be a regular, current-user-owned file with one hard link and must not be a symbolic link. Keep the whole path inside an application-owned directory; on Windows, enforce this with directory ACLs because no-follow open is not available.

Context keys are counted by default without including their names. Set auditRedaction.contextKeys to "names" only for fixed schema-like keys when names are required, or to "none" to omit both names and count. These modes never enable raw context values; targetingKeyPresent remains a boolean presence signal.

Use auditSink.flush?.() before process exit when a short-lived script must wait for pending non-blocking audit writes. Use auditWriteMode: "blocking" when each evaluation promise must wait for its audit write. OpenFeature.close() invokes the provider close hook, which flushes the configured sink without taking ownership of or closing a sink that another provider may share.

The queue is bounded to 5,000 pending writes by default. Set a positive maxQueueSize to choose another limit, or set maxQueueSize: null only when preserving the pre-0.15 unbounded behavior is worth the process-memory risk. The default overflow policy is reject; set queueOverflowPolicy: "dropNewest" to resolve the overflowing write without appending that event. auditSink.getStats?.() returns pending, dropped, and rejected write counters plus the effective queue limit. Counters are cumulative for the sink lifetime.

Set maxBytes to rotate the active audit file by size. maxFiles controls how many rotated files are retained as .1, .2, and so on.

Treat the audit file path as trusted local configuration. Do not pass tenant, request, or unvalidated user input directly into path.

Set lock: true when multiple local processes may write the same audit file. The lock is advisory and uses a sibling .lock file. lockTimeoutMs controls acquisition timeout, and staleLockMs can remove stale lock files left by crashed processes. Lock ownership is checked before release so an old writer does not remove a replacement owner's lock. This is best-effort local-filesystem coordination, not a distributed lock; an aggressive stale timeout can still allow concurrent live writers.

Multiple sinks loaded from the same package instance are serialized by audit path within one process. Separate processes or duplicate installed package instances still require lock: true when they share a rotating audit path.

Supported Runtime

  • Node.js 22 LTS
  • Node.js 24 LTS

Browser, Bun, Deno, hosted flag services, control-plane APIs, and remote streaming are outside the current package boundary.