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

@loic001/experiments

v0.4.0

Published

Experiment kernel: a stable arm per subject, exposure as the denominator, and a funnel verdict that refuses to speak too early. The registry serves; the data judges.

Readme

experiments

A stable arm per subject, exposure as the denominator, and a verdict that refuses to speak too early. No server round-trip, no flash, no vendor.

Laws: PRINCIPLES.md.

import { createExperiments, posthogSink } from '@loic001/experiments/browser';

export const experiments = createExperiments({
  experiments: [
    {
      key: 'apply_landing',
      enabled: true,
      rollout: 0.5,                  // half the traffic enters the test
      control: 'control',
      variants: [{ id: 'control' }, { id: 'v2' }],
    },
  ],
  sinks: [posthogSink()],
});

Serve it — React, or anything:

import { useExperiment } from '@loic001/experiments/react';

const arm = useExperiment(experiments, 'apply_landing');
if (arm === 'v2') return <Variant />;

Carry it — the arm rides with whatever you already send:

payload.exp = experiments.wire();     // "apply_landing:v2", or undefined

Judge it — registry-free, from whatever your warehouse counted:

import { compareArms } from '@loic001/experiments';

compareArms([
  { id: 'control', n: 4_012, converted: 402 },
  { id: 'v2', n: 3_988, converted: 518 },
]);
// { verdict: 'winner', winner: 'v2',
//   arms: [ …, { id: 'v2', ratePct: 13, deltaPts: 3, liftPct: 29.9, pValue: 0, significant: true } ] }

Check the registry before it ships

import { validateExperiments } from '@loic001/experiments'
validateExperiments(EXPERIMENTS)   // build only — throws, listing every problem

The wire is tolerant: it drops what it does not recognise. A variant named "big blue" renders perfectly and never travels, so the experiment runs for nobody and you find out by waiting for a result that cannot come. Validate at build, where saying no costs a second; never during a render, where it would cost the page (law 10).

Look at a variant without launching it

https://tagadapay.io/?exp=apply_landing:v2

A forced arm wins even on a stopped experiment, and while it is stopped nothing is recorded, nothing travels, nobody else is moved. That is how you review a variant in production before turning it on.

The whole chain, not one rate

A change that doubles clicks can halve what a click is worth. One rate never shows it; a chain does.

import { compareFunnel } from '@loic001/experiments';

compareFunnel(
  [
    { id: 'view',  source: 'pixel' },
    { id: 'click', source: 'pixel' },
    { id: 'lead',  source: 'pixel' },   // counted by both…
    { id: 'lead',  source: 'ledger' },  // …so the two halves are pinned together
    { id: 'live',  source: 'ledger' },
  ],
  counts,   // [{ step, arm, n }] — from a pixel, a warehouse, a CSV, anything
);
// click: v2 wins (+10 pts) · lead: v2 loses · live: same 250 merchants either way

The kernel fetches nothing. You bring the counts; it judges them. Plugging a new source in is writing one function that returns { step, arm, n }[].

Sources never divide into each other (law 8). A step counted by both is a bridge, and it is what makes the chain readable end to end. Without one, the ratio across the seam is still reported — hiding it would be worse — but flagged as crossSource, never called a conversion.

Three entries

| Import | Contains | Needs | |---|---|---| | @loic001/experiments | assignment, wire format, verdict — pure | nothing | | @loic001/experiments/browser | storage, URL overrides, exposure sinks | a browser (or injected fakes) | | @loic001/experiments/react | the hook | React ≥ 18 |

The pure entry runs in a browser, a worker, a Node job or a SQL-fed dashboard alike. Everything in /browser is injectable (storage, search, now, sinks), so the whole adapter is testable without a DOM.

What it does not do

  • No funnels. The arm is a dimension on your subject; your funnel tool slices on it. Pairs naturally with @loic001/funnels.
  • No identity. The subject id is a local random, 30 days, never sent anywhere. Only "key:variant" travels.
  • No registry on the reading side. A dashboard discovers arms from the data. Two registries to keep in sync is a bug waiting for a Friday.
  • No sequential testing. compareArms is a two-proportion z test at a fixed horizon. Peeking daily and stopping at the first green inflates false positives — fix the sample size before you start.

Overrides

?exp=apply_landing:v2 forces an arm for QA and previews, and only accepts a variant that actually exists. Change the parameter with overrideParam.