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

@traffical/openfeature-server

v0.2.0

Published

OpenFeature server (dynamic-context) provider backed by the Traffical Node SDK

Readme

@traffical/openfeature-server

OpenFeature server (dynamic-context) provider backed by the Traffical Node SDK.

Each flag resolution runs one Traffical decide() (the intent-to-treat / ITT decision), and exposure is a separate, explicit render-time signal — so your treatment-on-the-treated (ToT) metrics, SRM health gate, and bandit optimization measure what users actually saw, not merely what was decided.

Install

npm install @traffical/openfeature-server @openfeature/server-sdk @traffical/node

@openfeature/server-sdk and @traffical/node are peer dependencies.

Register the provider

Construct your Traffical Node client yourself, wrap it in the provider, and set it on OpenFeature. The caller owns the client lifecycle; the provider is a thin translation layer over it.

import { OpenFeature } from "@openfeature/server-sdk";
import { TrafficalClient } from "@traffical/node";
import { TrafficalServerProvider } from "@traffical/openfeature-server";

const client = new TrafficalClient({
  orgId: "org_123",
  projectId: "proj_456",
  env: "production",
  apiKey: process.env.TRAFFICAL_API_KEY!,
});
await OpenFeature.setProviderAndWait(new TrafficalServerProvider(client));

const of = OpenFeature.getClient();

Wrap each request

Wrap every request in provider.runInRequest(...) so resolve, exposure, and reward calls share one request-scoped decision store. This is what lets the exposure/reward paths find the exact decision the caller saw — without ever re-deciding — and prevents cross-unit bleed under concurrency.

const provider = new TrafficalServerProvider(client);
await OpenFeature.setProviderAndWait(provider);

app.use((req, res, next) => provider.runInRequest(next));

Without it, the provider falls back to a bounded, TTL'd per-key cache and warns once (exposure/reward may miss under concurrency).

Resolve a flag

const ctx = { targetingKey: user.id, plan: user.plan };

const enabled = await of.getBooleanValue("checkout.newFlow", false, ctx);
const color = await of.getStringValue("ui.color", "blue", ctx);

The resolution variant, reason (SPLIT when a variant was assigned, else DEFAULT), and scalar traffical.* flagMetadata (decision id, policy/allocation keys, bucket, propensity, config version) come straight from the decision.

Exposure — the $traffical.exposure convention

Exposure (ToT) is explicit: fire it at your render site, once you know the user actually saw the treatment. Use the reserved event name $traffical.exposure (exported as EXPOSURE_EVENT_NAME) and echo the flagKey in the event details:

import { EXPOSURE_EVENT_NAME } from "@traffical/openfeature-server";

of.track(EXPOSURE_EVENT_NAME, ctx, { flagKey: "checkout.newFlow" });

The provider stitches this to the decision already made for that flag in the same request and calls the native trackExposure(). It never re-decides in the exposure path — if no matching decision is found (e.g. the flag wasn't resolved earlier in the request), it warns once and no-ops.

Every other track(name, ctx, details) is treated as a business/reward event and forwarded to the client's track(), joined on the unit key from ctx.targetingKey. A numeric details.value is lifted out as the reward value.

No-exposure alarm

If the provider records many decisions but zero exposures, it fires a one-shot warning and a non-fatal provider Error event — because ToT metrics, SRM health checks, and bandit optimization would all be silently empty. Either instrument $traffical.exposure at your render sites, or set exposureOnResolve (below).

Options

new TrafficalServerProvider(client, options):

| Option | Description | | --- | --- | | exposureOnResolve | When true, the resolver fires trackExposure() on the just-made decision (collapsing ToT toward ITT). Escape hatch for teams that can't instrument explicit render-time exposures. | | unitKey | Override the context field the bundle buckets on. Defaults to client.getUnitKeyField() (the bundle's hashing.unitKey). | | exposureEventName | Override the reserved exposure event name. Defaults to $traffical.exposure. Change only to avoid a collision with a real business event. | | gatePropensity | When true, omit traffical.propensity from flagMetadata. |

targetingKey → unit-key mapping

The OpenFeature targetingKey is written under the bundle's actual bucketing field (hashing.unitKey), not a literal "targetingKey" field — otherwise the client would mis-bucket the unit. A missing/empty targeting key throws TargetingKeyMissingError, which the OpenFeature SDK maps to the default with reason: ERROR.