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

@wluwd/variations

v1.0.0

Published

Generate all possible variations of object properties using cartesian products

Readme

@wluwd/variations

Generate all possible variations of object properties using cartesian products

A lightweight, type-safe utility for generating all combinations of object properties. Perfect for testing component variants, creating configuration matrices, or any scenario where you need exhaustive combinations.

Features

  • 📦 Type-safety: full TypeScript support with precise type inference
  • 🪶 Lightweight: zero dependencies, ESM-only, minimal bundle size
  • 🏎️ Efficient: memory-efficient lazy evaluation for large datasets
  • 🌐 Universal: works in LTS Node.js (20+) and modern browsers

Installation

[!WARNING] This package is ESM only. Ensure your project uses "type": "module" in package.json.

npm install @wluwd/variations
pnpm add @wluwd/variations
yarn add @wluwd/variations

The Problem

When testing UI components with multiple props, manually writing test cases becomes unsustainable.

// Manual approach - error-prone and difficult and exhausting to maintain
it('renders primary small button', () => { /* ... */ })
it('renders primary normal button', () => { /* ... */ })
it('renders primary large button', () => { /* ... */ })
it('renders secondary small button', () => { /* ... */ })
// ... many more tests to write by hand

What if there's a better way though? That's where this library comes into play:

// Automated approach - declarative and maintainable
const testCases = eagerVariations({
  variant: ['primary', 'secondary', 'destructive'],
  size: ['small', 'normal', 'large'],
  status: ['idle', 'loading', 'disabled']
});

it.for(testCases)('Button: %o', (props) => { /* ... */ });
// ✨ all tests generated automatically

Quick Start

Basic Usage

import { eagerVariations } from '@wluwd/variations';

const configs = eagerVariations({
  color: ['red', 'blue', 'green'],
  size: ['small', 'large']
});

console.log(configs);
// [
//   { color: 'red', size: 'small' },
//   { color: 'red', size: 'large' },
//   { color: 'blue', size: 'small' },
//   { color: 'blue', size: 'large' },
//   { color: 'green', size: 'small' },
//   { color: 'green', size: 'large' }
// ]

Type-Safe Factory

For better autocomplete and output type inference, use defineVariations:

import { defineVariations } from '@wluwd/variations';

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size: 'small' | 'large';
}

const { eager, lazy } = defineVariations<ButtonProps>();

// 🪄 Full autocomplete with inferred output type:
//  { variant: 'primary'; size: 'small' | 'large' }
const variations = eager({
  variant: ['primary'],
  size: ['small', 'large']
});

With Filtering

const validConfigs = eagerVariations(
  {
    variant: ['primary', 'link'],
    size: ['small', 'large']
  },
  {
    // Skip invalid combinations
    filter: v => !(v.variant === 'link' && v.size === 'large')
  }
);

Memory-Efficient Processing

For large datasets, use lazyVariations to process one variation at a time:

import { lazyVariations } from '@wluwd/variations';

for (const config of lazyVariations({
  variant: ['primary', 'secondary', 'destructive'],
  size: ['small', 'normal', 'large'],
  status: ['idle', 'loading', 'disabled']
})) {
  // Process each of 27 variations without loading all into memory
  await processConfig(config);
}

API

eagerVariations(base, options?)

Generates all variations and returns them as an array.

Parameters:

  • base - Object where each property is an array of possible values
  • options? - Optional configuration object
    • filter? - Function to filter which variations to include
    • safe? - If true, returns empty array for invalid input instead of throwing

Returns: Array of all variation objects

lazyVariations(base, options?)

Generates variations one at a time using a generator.

Parameters: Same as eagerVariations

Yields: Individual variation objects

defineVariations<T>()

Creates a type-safe factory bound to a specific interface. It returns an object with two methods: eager and lazy. These work the same way as their stand-alone counterparts.

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size: 'small' | 'large';
}

const { eager } = defineVariations<ButtonProps>();

// 🪄 `eager` provides autocomplete for properties
const variations = eager({
  variant: ['primary'],
  size: ['small', 'large']
});
// typeof variations → Array<{ variant: 'primary', size: 'small' | 'large' }>

VariationsInput<T>

Type helper for explicitly typing variation inputs.

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size: 'small' | 'large';
}

const input = {
  variant: ['primary', 'secondary'],
  size: ['small']
} satisfies VariationsInput<ButtonProps>;

This type is useful when directly using eagerVariations or lazyVariations as it allows to get the same output type as the helpers bound by defineVariations:

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size: 'small' | 'large';
}

const variations = eagerVariations({
  variant: ['primary'],
  size: ['small', 'large']
} satisfies VariationsInput<ButtonProps>);

// typeof variations → Array<{ variant: 'primary', size: 'small' | 'large' }>

Real-World Example

Visual regression testing for a design system:

import { eagerVariations } from '@wluwd/variations';
import { render } from '@testing-library/react';

interface ButtonProps {
  variant: 'primary' | 'secondary' | 'destructive' | 'link';
  size: 'small' | 'normal' | 'large';
  status?: 'loading' | 'disabled';
}

describe('Button visual regression', () => {
  const cases = eagerVariations({
    variant: ['primary', 'secondary', 'destructive', 'link'],
    size: ['small', 'normal', 'large'],
    status: [undefined, 'loading', 'disabled']
  } satisfies VariationsInput<ButtonProps>);

  it.for(cases)('matches snapshot: %o', async (props) => {
    const screen = render(<Button {...props}>Click me</Button>);

    // Test default state
    expect(screen.getButton()).toMatchSnapshot();

    // Test hover state
    await userEvent.hover(screen.getButton());
    expect(screen.getButton()).toMatchSnapshot();

    // Test active state
    await userEvent.click(screen.getButton());
    expect(screen.getButton()).toMatchSnapshot();
  });
});

// ✨ Generates 108 comprehensive tests automatically

Background

This library was born from the need to test design system components exhaustively without manual overhead. What started as checking a handful of button variants eventually grew to 120+ combinations that needed verification for every CSS change.

Read the full story: When Manual Testing Becomes Unsustainable.

Performance

The algorithm uses an odometer/mixed-radix counter approach that:

  • Only updates changed dimensions between iterations
  • Maintains stable object shapes for optimization
  • Supports lazy evaluation to avoid loading all combinations into memory

Keys are processed left-to-right, with leftmost keys being most stable (changing least frequently).

License

MIT