New:Socket for Asana Is Now Available.Learn more
Sign In

@0disoft/openfeature-local-provider

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@0disoft/openfeature-local-provider

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.

latest
Source
npmnpm
Version
1.0.0
Version published
Maintainers
1
Created
Source

@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, ProviderEvents } 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);
OpenFeature.getClient().addHandler(ProviderEvents.ConfigurationChanged, (details) => {
  console.log("Changed flags", details?.flagsChanged ?? []);
});

const watcher = await watchFlagSnapshotFile({
  path,
  consistencyPollIntervalMs: 1000,
  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.

consistencyPollIntervalMs is opt-in and must be an integer of at least 50 ms. It adds metadata polling for visible paths backed by projected-volume symlink swaps while retaining native events as the primary signal. Unchanged metadata does not read or parse the snapshot. Successful semantic provider updates emit OpenFeature PROVIDER_CONFIGURATION_CHANGED with code-unit-sorted added, removed, and changed flag keys.

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.

Keywords

openfeature

FAQs

Package last updated on 20 Jul 2026

Related posts