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

@infitx/match

v1.4.4

Published

Object pattern matching utility

Readme

Match Function

A flexible matching utility that compares values with advanced semantic rules, supporting nested structures, arrays, type coercion, and various matching strategies.

Installation

const match = require('./match');

Basic Usage

const match = require('./match');

// Exact match
match({ a: 1 }, { a: 1 }); // true
match({ a: 1 }, { a: 2 }); // false

// Partial object match
match({ a: 1, b: 2 }, { a: 1 }); // true

Referencing Fact Values with $ref

The $ref feature allows rules to reference values from other parts of the fact using JSON Pointer syntax. This is useful for dynamic comparisons where the expected value comes from the fact itself.

// Reference another property in the fact
match(
  { expectedStatus: 'active', order: { status: 'active' } },
  { order: { status: { $ref: '#/expectedStatus' } } }
); // true - order.status matches expectedStatus

// Use $ref in min/max range conditions
match(
  { offer: { dateCreated: '2024-01-01' }, order: { dateCreated: '2024-02-01' } },
  { order: { dateCreated: { min: { $ref: '#/offer/dateCreated' } } } }
); // true - order date is after offer date

// Multiple $ref in same rule
match(
  { source: 'Alice', destination: 'Bob', transfer: { from: 'Alice', to: 'Bob' } },
  { transfer: { from: { $ref: '#/source' }, to: { $ref: '#/destination' } } }
); // true - transfer from/to matches source/destination

// $ref with nested paths
match(
  { config: { pricing: { minPrice: 100 } }, order: { price: 150 } },
  { order: { price: { min: { $ref: '#/config/pricing/minPrice' } } } }
); // true - order price is above configured minimum

// Missing properties resolve to undefined
match(
  { order: { dateCreated: '2024-02-01' } },
  { order: { dateCreated: { min: { $ref: '#/offer/dateCreated' } } } }
); // true - missing reference is treated as no constraint

// $ref with type coercion
match(
  { targetAmount: '500', order: { amount: 500 } },
  { order: { amount: { $ref: '#/targetAmount' } } }
); // true - string '500' is coerced to number 500

// $ref with arrays (any-of semantics apply)
match(
  { allowedTag: 'vip', order: { tags: ['vip', 'premium'] } },
  { order: { tags: { $ref: '#/allowedTag' } } }
); // true - at least one tag matches the reference

JSON Pointer Syntax

The $ref value uses JSON Pointer syntax (RFC 6901):

  • Always starts with #/ to indicate the root of the fact object
  • Properties are separated by /
  • Examples:
    • #/offer/dateCreatedfact.offer.dateCreated
    • #/config/pricing/minPricefact.config.pricing.minPrice
    • #/expectedStatusfact.expectedStatus

When to Use $ref

Use $ref when you need to:

  • Compare related values within the same fact
  • Implement dynamic constraints based on other properties
  • Reference configuration values from the fact
  • Create rules that adapt to the data being matched

## Matching Against Nested Structures

The match function recursively compares nested objects:

```javascript
// Nested object matching
match(
  { user: { name: 'Alice', age: 30 } },
  { user: { name: 'Alice' } }
); // true - partial match on nested object

match(
  { user: { name: 'Alice', age: 30 } },
  { user: { name: 'Bob' } }
); // false - name doesn't match

// Deep nesting
match(
  { a: { b: { c: { d: 'value' } } } },
  { a: { b: { c: { d: 'value' } } } }
); // true

match(
  { a: { b: { c: { d: 'value', e: 'extra' } } } },
  { a: { b: { c: { d: 'value' } } } }
); // true - extra properties are ignored

Array Matching (Any-Of Semantics)

Arrays implement "any of" semantics - at least one element must match. The behavior varies depending on whether arrays appear in the value, the condition, or both.

Array in Condition (Rule)

When the condition is an array, the value must match at least one element in the array:

// Scalar value against array condition
match('hello', ['hello', 'world']); // true - matches first element
match('world', ['hello', 'world']); // true - matches second element
match('goodbye', ['hello', 'world']); // false - matches none

// With numbers
match(5, [1, 5, 10]); // true
match(7, [1, 5, 10]); // false

// With booleans
match(true, [true, false]); // true
match(false, [true]); // false

// In nested structures - scalar value against array condition
match(
  { status: 'active' },
  { status: ['active', 'pending', 'processing'] }
); // true - status matches one of the allowed values

match(
  { status: 'inactive' },
  { status: ['active', 'pending'] }
); // false - status doesn't match any allowed value

Array in Value

When the value is an array, at least one element must match the condition:

// Array value against scalar condition
match(['apple', 'banana'], 'apple'); // true - first element matches
match(['apple', 'banana'], 'banana'); // true - second element matches
match(['apple', 'banana'], 'orange'); // false - no element matches

// With numbers
match([1, 2, 3], 2); // true
match([1, 2, 3], 5); // false

// In nested structures - array value against scalar condition
match(
  { tags: ['javascript', 'node', 'async'] },
  { tags: 'javascript' }
); // true - at least one tag matches

match(
  { tags: ['python', 'django'] },
  { tags: 'javascript' }
); // false - no tag matches

// Array value against function condition
match(
  { scores: [85, 90, 78] },
  { scores: (score) => score >= 80 }
); // true - at least one score is >= 80

match(
  { scores: [65, 70, 75] },
  { scores: (score) => score >= 80 }
); // false - no score is >= 80

Array in Both Value and Condition

When both are arrays, a match occurs if any value element matches any condition element:

// Both arrays - cartesian "any of any" matching
match(['red', 'blue'], ['blue', 'green']); // true - 'blue' appears in both
match(['red', 'yellow'], ['blue', 'green']); // false - no common elements

match([1, 2, 3], [3, 4, 5]); // true - '3' is in both
match([1, 2], [3, 4]); // false - no overlap

// In nested structures
match(
  { tags: ['javascript', 'node'] },
  { tags: ['node', 'async', 'backend'] }
); // true - 'node' is in both arrays

match(
  { tags: ['python', 'django'] },
  { tags: ['javascript', 'react'] }
); // false - no common tags

// Complex: array value against array of conditions (including objects)
match(
  { priority: [1, 2, 3] },
  { priority: [{ min: 2, max: 5 }, 10] }
); // true - elements 2 and 3 match the range { min: 2, max: 5 }

match(
  { priority: [1] },
  { priority: [{ min: 2, max: 5 }, 10] }
); // false - 1 doesn't match range or 10

Array Value Against Object Condition

When an array value is matched against an object condition (like a range or complex object), each element is tested against the condition:

// Array value against range condition
match(
  [1, 5, 10],
  { min: 3, max: 7 }
); // true - element '5' falls within the range

match(
  [1, 2],
  { min: 3, max: 7 }
); // false - no element falls within the range

// In nested structures
match(
  { scores: [45, 67, 89, 92] },
  { scores: { min: 80 } }
); // true - elements 89 and 92 are >= 80

match(
  { scores: [45, 67, 75] },
  { scores: { min: 80 } }
); // false - no score is >= 80

// Array value against regex condition
match(
  { emails: ['[email protected]', 'invalid', '[email protected]'] },
  { emails: /@test\.com$/ }
); // true - at least one email matches the pattern

match(
  { emails: ['invalid1', 'invalid2'] },
  { emails: /@test\.com$/ }
); // false - no email matches

Combining Arrays with Null

Arrays can include null to make conditions optional:

// Array with null - matches if property is missing OR matches other values
match(
  { status: 'active' },
  { status: [null, 'active'] }
); // true - matches 'active'

match(
  { name: 'Alice' },
  { status: [null, 'active'] }
); // true - 'status' is missing, matches null

match(
  { status: false },
  { status: [null, 'active'] }
); // false - false is neither null nor 'active'

match(
  { status: undefined },
  { status: [null, 'active'] }
); // true - undefined treated as null

// Nested with arrays and null
match(
  { user: { role: 'admin' } },
  { user: { role: [null, 'admin', 'moderator'] } }
); // true - role matches 'admin'

match(
  { user: { name: 'Alice' } },
  { user: { role: [null, 'admin'], name: 'Alice' } }
); // true - role is missing (matches null), name matches

Array Behavior Summary

| Value Type | Condition Type | Behavior | | ---------- | -------------------------- | --------------------------------------------- | | Scalar | Array | Value must match at least one array element | | Array | Scalar | At least one value must match the scalar | | Array | Array | At least one value must match one condition | | Array | Object (range/complex) | At least one value must match the condition | | Array | Function | At least one value must satisfy the function |

Matching Against Non-Existing Properties

The match function has special handling for null values to match against non-existing properties:

// null matches undefined/missing properties
match({ b: 0 }, { a: null }); // true - 'a' is missing

match({ a: null }, { a: null }); // true - both null

match({ a: undefined }, { a: null }); // true - undefined treated as null

match({ a: false }, { a: null }); // false - false is not null

match({ a: 0 }, { a: null }); // false - 0 is not null

match({ a: '' }, { a: null }); // false - empty string is not null

// Nested non-existing properties
match(
  { a: {} },
  { a: { b: null } }
); // true - 'b' doesn't exist in nested object

match(
  { a: { b: false } },
  { a: { b: null } }
); // false - 'b' exists and is false

// Array with null for optional properties
match(
  { status: 'active' },
  { status: [null, 'active'] }
); // true - matches 'active'

match(
  { name: 'Alice' },
  { status: [null, 'active'] }
); // true - 'status' is missing, matches null

match(
  { status: false },
  { status: [null, 'active'] }
); // false - false doesn't match null or 'active'

Type Coercion

The function coerces values to match the type of the rule:

// Boolean coercion
match('hello', true); // true - truthy string
match('', false); // true - falsy empty string
match(0, false); // true - falsy zero
match(1, true); // true - truthy number

// String coercion
match(123, '123'); // true
match(true, 'true'); // true

// Number coercion
match('42', 42); // true
match('3.14', 3.14); // true

Range Matching

Use min and max for numeric and date range matching:

// Numeric ranges
match(5, { min: 1, max: 10 }); // true
match(15, { min: 1, max: 10 }); // false
match(1, { min: 1 }); // true - only minimum
match(10, { max: 10 }); // true - only maximum

// Date ranges
const now = new Date('2025-06-15');
const start = new Date('2025-01-01');
const end = new Date('2025-12-31');

match(now, { min: start, max: end }); // true
match(new Date('2026-01-01'), { min: start, max: end }); // false

// Nested range matching
match(
  { user: { age: 25 } },
  { user: { age: { min: 18, max: 65 } } }
); // true

Grafana-Style Time Intervals

The min and max properties support Grafana-style relative time intervals for convenient time-based matching:

// Current time
match(new Date(), { min: 'now-1h', max: 'now' });
// true - current time is within the last hour

// Future time check
match(new Date(), { max: 'now+1d' });
// true - current time is before tomorrow

// Past time check
match(new Date(), { min: 'now-1w' });
// true - current time is within the last week

// Time range
match(
  new Date(),
  { min: 'now-1d', max: 'now+1d' }
); // true - current time is within ±1 day

// In nested structures
match(
  { event: { timestamp: new Date() } },
  { event: { timestamp: { min: 'now-5m' } } }
); // true - event occurred within the last 5 minutes

match(
  { event: { timestamp: new Date(Date.now() - 10 * 60 * 1000) } },
  { event: { timestamp: { min: 'now-5m' } } }
); // false - event occurred more than 5 minutes ago

Supported Time Units

| Unit | Description | Example | | ---- | -------------- | ----------- | | ms | Milliseconds | now-500ms | | s | Seconds | now-30s | | m | Minutes | now-5m | | h | Hours | now-2h | | d | Days | now-7d | | w | Weeks | now-2w | | M | Months (30d) | now-3M | | y | Years (365d) | now-1y |

Rounding Units

When using the / operator, you can round to these time units:

| Unit | Rounds To | Example | | ---- | -------------------- | -------------------------------- | | s | Start of second | now/s = current second at .000 | | m | Start of minute | now/m = current minute at :00 | | h | Start of hour | now/h = current hour at :00:00 | | d | Start of day | now/d = today at 00:00:00 | | w | Start of week | now/w = Monday at 00:00:00 | | M | Start of month | now/M = 1st of month 00:00:00 | | y | Start of year | now/y = Jan 1st at 00:00:00 |

Note: Week rounding always rounds to Monday as the first day of the week.

Time Interval Format

  • now: Current time
  • now-<amount><unit>: Time in the past (e.g., now-5m = 5 minutes ago)
  • now+<amount><unit>: Time in the future (e.g., now+1h = 1 hour from now)
  • now/<unit>: Current time rounded to start of unit (e.g., now/d = start of today)
  • now[+-]<amount><unit>/<roundUnit>: Time with offset, rounded (e.g., now-5d/d = 5 days ago at midnight)

Time Rounding

The / operator rounds timestamps to the start of the specified time unit, following Grafana's approach:

// Round to start of current day (midnight)
match(new Date('2025-06-15T14:30:00'), { min: 'now/d', max: 'now' });
// Compares against start of current day

// Round to start of current hour
match(new Date(), { min: 'now/h' });
// Matches times from the start of the current hour

// Combine offset and rounding
// Example: 5 days ago, rounded to midnight of that day
match(new Date(), { min: 'now-5d/d' });

// Week rounding (rounds to Monday)
match(new Date(), { min: 'now/w' });
// Matches times from the start of the current week

// Month rounding
match(new Date(), { min: 'now-1M/M' });
// Matches times from the start of last month

// Rounding units: s (second), m (minute), h (hour), d (day),
// w (week), M (month), y (year)
// Examples with different units
match(new Date(), { min: 'now-500ms' }); // Last 500 milliseconds
match(new Date(), { min: 'now-30s' }); // Last 30 seconds
match(new Date(), { min: 'now-5m' }); // Last 5 minutes
match(new Date(), { min: 'now-2h' }); // Last 2 hours
match(new Date(), { min: 'now-7d' }); // Last 7 days
match(new Date(), { min: 'now-2w' }); // Last 2 weeks
match(new Date(), { min: 'now-3M' }); // Last 3 months (approx)
match(new Date(), { min: 'now-1y' }); // Last year (approx)

// Future times
match(new Date(), { max: 'now+1h' }); // Within next hour
match(new Date(), { max: 'now+7d' }); // Within next 7 days

// Examples with rounding
match(new Date(), { min: 'now/d' }); // Since midnight today
match(new Date(), { min: 'now-7d/d', max: 'now/d' }); // Last 7 full days
match(new Date(), { min: 'now/M' }); // Since start of current month
match(new Date(), { min: 'now-1y/y', max: 'now/y' }); // Last full year

// Practical examples
// Check if log entry is recent (last 15 minutes)
match(
  { log: { timestamp: new Date() } },
  { log: { timestamp: { min: 'now-15m' } } }
); // true

// Check if event occurred today (since midnight)
match(
  { event: { timestamp: new Date() } },
  { event: { timestamp: { min: 'now/d' } } }
); // true if event is from today

// Check if data is from the current month
match(
  { report: { date: new Date() } },
  { report: { date: { min: 'now/M', max: 'now' } } }
); // true if report is from current month

// Check if scheduled event is upcoming (next 24 hours)
match(
  { event: { scheduledAt: new Date(Date.now() + 12 * 60 * 60 * 1000) } },
  { event: { scheduledAt: { min: 'now', max: 'now+1d' } } }
); // true

// Check if user session is still valid (created within last 30 minutes)
match(
  { session: { createdAt: new Date(Date.now() - 10 * 60 * 1000) } },
  { session: { createdAt: { min: 'now-30m' } } }
); // true

// Daily reports: match events from start of day until now
match(
  { log: { timestamp: new Date() } },
  { log: { timestamp: { min: 'now/d', max: 'now' } } }
); // Matches anything that happened today

// Weekly reports: last 7 complete days
match(
  { metric: { recorded: new Date() } },
  { metric: { recorded: { min: 'now-7d/d', max: 'now/d' } } }
); // Matches data from last 7 full days (midnight to midnight)

// Business hours check: events during current hour
match(
  { transaction: { timestamp: new Date() } },
  { transaction: { timestamp: { min: 'now/h', max: 'now' } } }
); // Matches transactions from start of current hour

Function Predicates

Use functions for custom matching logic:

// Function as rule
match(10, (value) => value > 5); // true
match(3, (value) => value > 5); // false

// Complex predicate
match(
  '[email protected]',
  (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
); // true - valid email

// Function in nested structure
match(
  { user: { age: 25 } },
  { user: { age: (age) => age >= 18 } }
); // true

Negation with not

Use the not property in an object condition to negate any match:

// Basic negation
match(3, { not: 5 }); // true - 3 is not 5
match(5, { not: 5 }); // false - 5 is 5

// Negate string match
match('goodbye', { not: 'hello' }); // true
match('hello', { not: 'hello' }); // false

// Negate boolean
match(false, { not: true }); // true
match(true, { not: true }); // false
match(0, { not: true }); // true - 0 is falsy, not true
match(1, { not: true }); // false - 1 coerces to true

// Negate null
match(0, { not: null }); // true - 0 is not null
match(null, { not: null }); // false
match(undefined, { not: null }); // false - undefined treated as null

// In nested structures
match(
  { status: 'inactive' },
  { status: { not: 'active' } }
); // true

match(
  { status: 'active' },
  { status: { not: 'active' } }
); // false

Negating Complex Conditions

The not property works with all match types including arrays, ranges, functions, and regex:

// Negate regex
match('goodbye', { not: /hello/ }); // true
match('hello world', { not: /hello/ }); // false

match(
  { email: '[email protected]' },
  { email: { not: /@example\.com$/ } }
); // true - doesn't end with @example.com

// Negate array (none of)
match(5, { not: [1, 2, 3] }); // true - 5 is not in the list
match(2, { not: [1, 2, 3] }); // false - 2 is in the list

match(
  { role: 'guest' },
  { role: { not: ['admin', 'moderator'] } }
); // true - guest is neither admin nor moderator

match(
  { role: 'admin' },
  { role: { not: ['admin', 'moderator'] } }
); // false - admin is in the list

// Negate range
match(3, { not: { min: 5, max: 10 } }); // true - 3 is outside the range
match(7, { not: { min: 5, max: 10 } }); // false - 7 is in the range
match(15, { not: { min: 5, max: 10 } }); // true - 15 is outside the range

match(
  { age: 15 },
  { age: { not: { min: 18, max: 65 } } }
); // true - age is below minimum

match(
  { age: 25 },
  { age: { not: { min: 18, max: 65 } } }
); // false - age is in range

// Negate function predicate
match(3, { not: (v) => v > 5 }); // true - 3 is not > 5
match(10, { not: (v) => v > 5 }); // false - 10 is > 5

match(
  { price: 25 },
  { price: { not: (p) => p >= 100 } }
); // true - price is not >= 100

// Negate object match
match(
  { user: { role: 'guest' } },
  { user: { not: { role: 'admin' } } }
); // true - role is not admin

match(
  { user: { role: 'admin' } },
  { user: { not: { role: 'admin' } } }
); // false - role is admin

// Complex negation in nested structures
match(
  {
    user: {
      email: '[email protected]',
      role: 'user'
    }
  },
  {
    user: {
      email: { not: /@example\.com$/ },
      role: { not: ['admin', 'moderator'] }
    }
  }
); // true - email doesn't end with @example.com and role is not admin/moderator

Combining not with Other Conditions

You can combine not with other conditions in complex matching scenarios:

// Exclude certain values while checking other properties
match(
  { status: 'pending', priority: 2 },
  {
    status: { not: ['cancelled', 'completed'] },
    priority: { min: 1, max: 3 }
  }
); // true - status is not cancelled/completed and priority is in range

// Array values with negation
match(
  { tags: ['javascript', 'backend'] },
  { tags: { not: 'frontend' } }
); // true - none of the tags are 'frontend'

match(
  { tags: ['javascript', 'frontend'] },
  { tags: { not: 'frontend' } }
); // false - 'frontend' is in the tags

Regular Expression Matching

// Regex patterns
match('hello world', /hello/); // true
match('goodbye world', /hello/); // false

// Case-insensitive matching
match('Hello World', /hello/i); // true

// Nested regex
match(
  { email: '[email protected]' },
  { email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }
); // true

Complex Examples

Combining multiple matching strategies:

// Complex nested structure with arrays and nulls
match(
  {
    user: {
      name: 'Alice',
      email: '[email protected]',
      roles: ['admin', 'user']
    },
    status: 'active'
  },
  {
    user: {
      email: /@example\.com$/,
      roles: 'admin'
    },
    status: ['active', 'pending'],
    lastLogin: null // optional field
  }
); // true

// Multiple conditions
match(
  { price: 50, category: 'electronics', inStock: true },
  {
    price: { min: 0, max: 100 },
    category: ['electronics', 'computers'],
    inStock: true,
    discount: null // discount is optional
  }
); // true

API

match(factValue, ruleValue)

Compares a fact value against a rule value with flexible matching semantics.

Parameters:

  • factValue (any): The actual value to test
  • ruleValue (any): The pattern/rule to match against

Returns:

  • boolean: true if the fact matches the rule, false otherwise

Matching Rules:

  • Exact equality: Returns true if values are strictly equal
  • Null handling: null in rule matches null or undefined in fact
  • Arrays: "Any of" semantics - at least one element must match
  • Objects: Recursively matches properties (partial matching allowed)
  • Type coercion: Values are coerced to match rule type
  • Ranges: Objects with min/max properties enable range matching
  • Negation: Objects with not property negate any match condition
  • Functions: Rule functions are called with fact value as predicate
  • RegExp: Tests string values against regex patterns

License

See the main project license.