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

@endo/patterns

v1.9.1

Published

Pattern matching for Passable objects, expressed as Passable data

Readme

@endo/patterns

Pattern matching and validation for passable data, with copy-collections and interface guards.

Overview

The @endo/patterns package provides the M namespace for creating pattern matchers that validate passable data and describe behavioral contracts. This is the validation layer above @endo/pass-style, enabling you to check that data matches expected shapes before using it.

Patterns enable:

  • Data validation: Check that values match expected types and structures
  • Interface contracts: Describe method signatures with InterfaceGuards
  • Copy collections: CopySet, CopyBag, CopyMap for passable data structures
  • Key comparison: Distributed equality for comparing values across vats

Quick Start

import { M, mustMatch } from '@endo/patterns';

const specimen = harden({ foo: 3, bar: 4 });

const pattern = M.splitRecord(
  { foo: M.number() },                        // required properties
  { bar: M.string(), baz: M.number() }       // optional properties
);

mustMatch(specimen, pattern);
// throws: 'bar?: number 4 - Must be a string'

For best rendering, use the Endo reference docs site.

The M Namespace

The M object provides methods for creating pattern matchers organized into several categories:

Primitive Matchers

Match specific JavaScript types:

M.any()           // Matches any passable
M.undefined()     // Matches undefined
M.null()          // Matches null
M.boolean()       // Matches true or false
M.number()        // Matches any number (including NaN, Infinity)
M.bigint()        // Matches any bigint
M.string()        // Matches any string
M.symbol()        // Matches registered/well-known symbols

// Constrained primitives
M.nat()           // Non-negative bigint
M.gte(5)          // Number >= 5
M.lte(100)        // Number <= 100

Container Matchers

Match copyArray, copyRecord, and other structures:

M.array()         // Any CopyArray
M.record()        // Any CopyRecord
M.set()           // Any CopySet
M.bag()           // Any CopyBag
M.map()           // Any CopyMap

// With constraints
M.array({ maxSize: 10 })          // Array with at most 10 elements
M.string({ maxSize: 100 })        // String with at most 100 characters

// Structured content
M.arrayOf(M.number())             // Array of numbers only
M.recordOf(M.string(), M.number()) // Record with string keys, number values
M.setOf(M.string())               // Set of strings only

Structured Matchers

Match specific shapes:

// Split patterns: required, optional, rest
M.splitArray(
  [M.string(), M.number()],      // required elements
  [M.boolean()],                  // optional elements
  M.any()                         // rest elements
)

M.splitRecord(
  { name: M.string() },           // required properties
  { age: M.number() },            // optional properties
  M.any()                         // rest properties
)

// Partial matches
M.partial({ name: M.string() })  // Has at least 'name' property

// Split auto-detects array vs record
M.split({ x: M.number() }, M.any())

Logical Operators

Combine matchers:

M.and(M.number(), M.gte(0), M.lte(100))   // 0 <= n <= 100
M.or(M.string(), M.number())               // String or number
M.not(M.undefined())                       // Anything except undefined
M.opt(M.string())                          // undefined or string (optional)

Comparison Matchers

Match values relative to a key:

M.eq('hello')     // Equal to 'hello'
M.neq(0)          // Not equal to 0
M.lt(10)          // Less than 10
M.lte(100)        // Less than or equal to 100
M.gte(0)          // Greater than or equal to 0
M.gt(-1)          // Greater than -1

Special Matchers

M.remotable()           // Any remotable object
M.remotable('Counter')  // Remotable with specific label
M.error()               // Any error
M.promise()             // Any promise
M.eref(M.number())      // Number or promise for number (eventual reference)
M.kind('copyArray')     // Specific pass style
M.pattern()             // Any valid pattern
M.key()                 // Any valid Key
M.scalar()              // Any primitive or remotable

Pattern Matching

matches(specimen, pattern)

Returns true if the specimen matches the pattern, false otherwise:

import { M, matches } from '@endo/patterns';

matches(42, M.number());                    // true
matches('hello', M.number());               // false
matches([1, 2, 3], M.arrayOf(M.number())); // true

mustMatch(specimen, pattern, label?)

Throws with a descriptive error if the specimen doesn't match:

import { mustMatch } from '@endo/patterns';

mustMatch(42, M.string());
// throws: "number 42 - Must be a string"

mustMatch(-5, M.and(M.number(), M.gte(0)), 'count');
// throws: "count: number -5 - Must be >= 0"

The error messages are designed to help you understand exactly what was wrong with the data.

Copy Collections

Patterns introduces three passable collection types built on makeTagged():

CopySet

A set of unique Keys (primitives or remotables):

import { makeCopySet } from '@endo/patterns';

const colors = makeCopySet(['red', 'blue', 'green']);

// Elements are sorted in rank order
// Duplicates are removed
// Can be passed between vats

// Pattern for sets
const ColorSet = M.setOf(M.string());
mustMatch(colors, ColorSet);  // passes

Why not use JavaScript Set? JavaScript Sets aren't passable. CopySet is frozen, comparable via keyEQ, and can be efficiently serialized.

CopyBag

A multiset (elements with counts):

import { makeCopyBag } from '@endo/patterns';

const inventory = makeCopyBag([
  ['apples', 5n],
  ['oranges', 3n],
  ['apples', 2n]   // counts are combined
]);

// Result: [['apples', 7n], ['oranges', 3n]]

const InventoryPattern = M.bagOf(M.string(), M.bigint());
mustMatch(inventory, InventoryPattern);

CopyMap

A map from Keys to Passable values:

import { makeCopyMap } from '@endo/patterns';

const balances = makeCopyMap([
  ['alice', 100],
  ['bob', 50]
]);

// Keys are sorted in rank order
// Can use any Key as a key (not just strings!)

const remotableKey = Far('Key', {});
const map = makeCopyMap([[remotableKey, 'value']]);

const BalancesPattern = M.mapOf(M.string(), M.number());
mustMatch(balances, BalancesPattern);

Why not use plain objects? CopyMap supports:

  • Any Key as a key (objects, remotables, not just strings)
  • Efficient key comparison using compareKeys()
  • Subset relationships for partial ordering

Interface Guards

InterfaceGuards describe behavioral contracts for objects, particularly useful with @endo/exo:

Creating Interface Guards

import { M } from '@endo/patterns';

const CounterI = M.interface('Counter', {
  // Synchronous method
  increment: M.call(M.number()).returns(M.number()),

  // Method with optional arguments
  reset: M.call().optional(M.number()).returns(),

  // Method with rest arguments
  add: M.call(M.number()).rest(M.number()).returns(M.number()),

  // Async method (awaits arguments)
  asyncOp: M.callWhen(M.string()).returns(M.string())
});

Method Guard Structure

// Basic call: call(required args...)
M.call(M.string(), M.number())

// With optional args
M.call(M.string()).optional(M.number())

// With rest args
M.call(M.string()).rest(M.any())

// Specify return type
M.call(M.string()).returns(M.number())

// Async method (awaits promise args)
M.callWhen(M.remotable()).returns(M.string())

Integration with Exo

InterfaceGuards are enforced automatically by exos:

import { makeExo } from '@endo/exo';
import { M } from '@endo/patterns';

const CounterI = M.interface('Counter', {
  increment: M.call(M.number()).returns(M.number())
});

const counter = makeExo('Counter', CounterI, {
  increment(n) {
    // n is guaranteed to be a number by the guard
    return count += n;
  }
});

counter.increment(5);      // OK
counter.increment('5');    // throws: Must be a number

This is the foundation of defensive programming in Endo: guards validate inputs automatically, so your methods can focus on business logic.

Key Comparison

Keys can be compared for equality and ordering:

keyEQ(key1, key2)

Tests if two Keys are equal using distributed equality semantics:

import { keyEQ } from '@endo/patterns';

keyEQ('hello', 'hello');        // true
keyEQ(42, 42);                  // true
keyEQ([1, 2], [1, 2]);          // true (compares content)

const r1 = Far('Obj', {});
const r2 = Far('Obj', {});
keyEQ(r1, r1);                  // true (same remotable)
keyEQ(r1, r2);                  // false (different remotables)

compareKeys(key1, key2)

Returns a comparison result implementing a partial order:

  • 0: Keys are equal
  • -1: key1 < key2
  • 1: key1 > key2
  • NaN: Keys are incomparable
import { compareKeys, keyLT, keyGT } from '@endo/patterns';

compareKeys('a', 'b');          // -1
compareKeys(5, 5);              // 0
compareKeys(10, 3);             // 1

// Convenience functions
keyLT('a', 'b');                // true
keyGT(10, 3);                   // true

// Incomparable keys
const r1 = Far('A', {});
const r2 = Far('B', {});
compareKeys(r1, r2);            // NaN (different remotables)

Why partial order? Not all Keys can be compared. For example, different remotables have no defined ordering, and CopySets use subset relationships.

Key, Pattern, and Passable Hierarchy

Understanding the type hierarchy:

Passable (everything that can pass)
├── Error
├── Promise
├── Key (stable, comparable)
│   ├── Primitives (null, undefined, boolean, number, bigint, string, symbol)
│   ├── Remotable
│   ├── CopyArray<Key>
│   ├── CopyRecord<Key>
│   ├── CopySet<Key>
│   ├── CopyBag<Key>
│   └── CopyMap<Key, Passable>
└── Pattern (describes a set of Passables)
    ├── Key (matches itself)
    └── Key-like with Matcher leaves
  • Passable: Can cross vat boundaries (from @endo/pass-style)
  • Key: Stable and comparable subset of Passable
  • Pattern: Describes a subset of Passables for matching

Integration with Endo Packages

Complete Tutorial: See Message Passing for a comprehensive guide showing how patterns work with pass-style, exo, and eventual-send.

Deep Dives

For implementation details: