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

@anilkumarthakur/match

v0.1.3

Published

PHP-style match expressions for JavaScript/TypeScript

Readme

@anilkumarthakur/match

npm version license tests coverage

PHP-style match expressions for JavaScript/TypeScript with 100% type safety and comprehensive test coverage.

@anilkumarthakur/match brings the power and elegance of PHP's match expression to the JavaScript/TypeScript world. It provides a clean, type-safe alternative to complex switch statements and nested if-else logic.

Features

Type-Safe: Full TypeScript support with generic types for subject and result
🎯 Readable: Clean, expressive syntax inspired by PHP match expressions
🚀 Fast: Efficient equality-based matching using JavaScript's Map
📦 Lightweight: Zero dependencies, only ~0.5KB gzipped (ES module)
🧪 Well-Tested: 127 comprehensive tests with 100% code coverage
🔗 Chainable: Fluent API for method chaining
🌍 Cross-Platform: Works in Node.js and browsers (ESM + UMD)

Installation

npm

npm install @anilkumarthakur/match

yarn

yarn add @anilkumarthakur/match

bun

bun add @anilkumarthakur/match

Quick Start

import { match } from '@anilkumarthakur/match'

const result = match('success')
  .on('success', () => 'Operation successful!')
  .on('error', () => 'Something went wrong')
  .otherwise(() => 'Unknown status')

console.log(result) // "Operation successful!"

API Reference

match<TSubject, TResult>(subject: TSubject): Matcher

Creates a new match expression for the given subject value.

Parameters:

  • subject - The value to match against (any type)

Returns: A Matcher instance for method chaining

Example:

const matcher = match(statusCode)

on(value: TSubject, handler: () => TResult): Matcher

Adds a case to match against. Uses strict equality (===) for comparison.

Parameters:

  • value - The value to match
  • handler - Function returning the result if matched

Returns: The matcher instance (for chaining)

Example:

match(status)
  .on(200, () => 'Success')
  .on(404, () => 'Not Found')

onAny(values: readonly TSubject[], handler: () => TResult): Matcher

Adds multiple values that all map to the same handler (simulates PHP's comma-separated cases).

Parameters:

  • values - Array of values to match
  • handler - Function to execute if any value matches

Returns: The matcher instance (for chaining)

Example:

match(status)
  .onAny([200, 201, 202], () => 'Success')
  .onAny([400, 401, 403], () => 'Client Error')

otherwise(handler: () => TResult): TResult

Sets the default handler and executes the match. Returns immediately with the result.

Parameters:

  • handler - Function to execute if no cases match

Returns: The result from matched handler or default handler

Throws: UnhandledMatchError if no match found and no default provided

Example:

const result = match(value)
  .on('expected', () => 'matched')
  .otherwise(() => 'default')

default(handler: () => TResult): TResult

PHP-compatible alias for otherwise(). Identical behavior.

Example:

const result = match(value)
  .on('expected', () => 'matched')
  .default(() => 'default')

valueOf(): TResult

Executes the match without a default handler. Throws if no match found.

Returns: The result from matched handler

Throws: UnhandledMatchError if no match found

Example:

const result = match('test')
  .on('test', () => 'matched')
  .valueOf() // Must have matched something

UnhandledMatchError

Custom error thrown when no case matches and no default handler is provided.

Properties:

  • name - "UnhandledMatchError"
  • message - Contains the unmatched value

Example:

try {
  match('foo')
    .on('bar', () => 'bar')
    .valueOf()
} catch (error) {
  if (error instanceof UnhandledMatchError) {
    console.error('No match found for:', error.message)
  }
}

Usage Examples

Basic String Matching

import { match } from '@anilkumarthakur/match'

const getRole = (role: string) => {
  return match(role)
    .on('admin', () => 'Full access')
    .on('user', () => 'Limited access')
    .on('guest', () => 'Read-only access')
    .otherwise(() => 'Unknown role')
}

console.log(getRole('admin')) // "Full access"

Number Matching (HTTP Status Codes)

const handleResponse = (statusCode: number) => {
  return match(statusCode)
    .on(200, () => 'OK')
    .onAny([201, 202, 204], () => 'Created/Accepted')
    .on(400, () => 'Bad Request')
    .on(401, () => 'Unauthorized')
    .on(404, () => 'Not Found')
    .on(500, () => 'Server Error')
    .otherwise(() => 'Unknown Status')
}

console.log(handleResponse(200)) // "OK"
console.log(handleResponse(201)) // "Created/Accepted"
console.log(handleResponse(999)) // "Unknown Status"

Complex Notifications

const showNotification = (type: string, message: string) => {
  const styling = match(type)
    .on('success', () => ({ color: 'green', icon: '✓' }))
    .on('error', () => ({ color: 'red', icon: '✗' }))
    .on('warning', () => ({ color: 'orange', icon: '⚠' }))
    .on('info', () => ({ color: 'blue', icon: 'ℹ' }))
    .otherwise(() => ({ color: 'gray', icon: '•' }))

  return `[${styling.icon}] ${message}`
}

console.log(showNotification('success', 'Saved!')) // "[✓] Saved!"
console.log(showNotification('error', 'Failed!')) // "[✗] Failed!"

Nested Match Expressions

const getUserStatus = (userId: string, status: string) => {
  return match(userId)
    .on('admin', () => {
      return match(status)
        .on('active', () => 'Admin is active')
        .on('inactive', () => 'Admin is inactive')
        .otherwise(() => 'Admin status unknown')
    })
    .on('user', () => {
      return match(status)
        .on('active', () => 'User is active')
        .otherwise(() => 'User is inactive')
    })
    .otherwise(() => 'User not found')
}

console.log(getUserStatus('admin', 'active')) // "Admin is active"
console.log(getUserStatus('user', 'active')) // "User is active"
console.log(getUserStatus('guest', 'active')) // "User not found"

Type-Safe Unions

type LogLevel = 'debug' | 'info' | 'warn' | 'error'

const getLogColor = (level: LogLevel): string => {
  return match(level)
    .on('debug', () => 'gray')
    .on('info', () => 'blue')
    .on('warn', () => 'yellow')
    .on('error', () => 'red')
    .otherwise(() => 'white')
}

console.log(getLogColor('info')) // "blue"

Conditional Logic with match(true)

const getUserMessage = (age: number, isPremium: boolean) => {
  return match(true)
    .on(age < 13, () => 'Not eligible')
    .on(age >= 13 && age < 18, () => 'Teen user')
    .on(age >= 18 && !isPremium, () => 'Free user')
    .on(age >= 18 && isPremium, () => 'Premium user')
    .otherwise(() => 'Unknown')
}

console.log(getUserMessage(25, true)) // "Premium user"
console.log(getUserMessage(16, false)) // "Teen user"

Days in Month (Real-World Example)

const daysInMonth = (month: string, year: number): number => {
  const isLeap = (y: number) => y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0)

  return match(month.toLowerCase().slice(0, 3))
    .on('jan', () => 31)
    .on('feb', () => (isLeap(year) ? 29 : 28))
    .on('mar', () => 31)
    .on('apr', () => 30)
    .on('may', () => 31)
    .on('jun', () => 30)
    .on('jul', () => 31)
    .on('aug', () => 31)
    .on('sep', () => 30)
    .on('oct', () => 31)
    .on('nov', () => 30)
    .on('dec', () => 31)
    .otherwise(() => {
      throw new Error('Invalid month')
    })
}

console.log(daysInMonth('February', 2024)) // 29 (leap year)
console.log(daysInMonth('February', 2025)) // 28

Comparison with PHP match()

PHP

$result = match($status) {
    'success', 'ok' => 'All good',
    'error', 'fail' => 'Something went wrong',
    default => 'Unknown'
};

JavaScript (this library)

const result = match(status)
  .onAny(['success', 'ok'], () => 'All good')
  .onAny(['error', 'fail'], () => 'Something went wrong')
  .otherwise(() => 'Unknown')

Supported Types

The library supports matching on any JavaScript type using strict equality (===):

  • ✅ Strings
  • ✅ Numbers (including Infinity, -Infinity)
  • ✅ Booleans
  • ✅ null / undefined
  • ✅ Symbols
  • ✅ BigInt
  • ✅ Objects (by reference)
  • ✅ Arrays (by reference)
  • ✅ Functions (by reference)
  • ✅ Enums
  • ✅ Class instances (by reference)

Type Safety

Full TypeScript support with automatic type inference:

// Explicit types
const result = match<string, number>('test')
  .on('test', () => 123)
  .otherwise(() => 456)

// Inferred types
const result2 = match('test')
  .on('test', () => 'result') // Inferred as string result
  .otherwise(() => 'default')

// Union types
type Status = 'success' | 'pending' | 'error'
const result3 = match<Status, string>('success')
  .on('success', () => 'Done')
  .on('pending', () => 'In progress')
  .on('error', () => 'Failed')
  .otherwise(() => 'Unknown')

Performance

  • ⚡ Uses JavaScript Map for O(1) lookup time
  • 💾 Lazy evaluation - only matched handler executes
  • 📦 Bundle sizes: ES (1.03 kB / 0.46 kB gzipped), UMD (0.99 kB / 0.51 kB gzipped)

Testing

The library includes 127 comprehensive tests with 100% code coverage:

# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Watch mode
npm test -- --watch

Test categories:

  • Basic functionality
  • Type matching (strings, numbers, booleans, objects, arrays, etc.)
  • All API methods (on, onAny, otherwise, default, valueOf)
  • Error handling
  • Type safety
  • Real-world examples
  • Edge cases and performance

Browser Support

Works in all modern browsers and Node.js 14+:

  • Chrome/Edge (latest)
  • Firefox (latest)
  • Safari (latest)
  • Node.js 14+

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

Development

# Install dependencies
npm install

# Run tests
npm test

# Build
npm run build

# Lint and format
npm run lint-format

License

MIT - See LICENSE for details

Author

Anil Kumar Thakur

Related

Changelog

See CHANGELOG.md for release history and updates.