Endo
    Preparing search index...

    Module @endo/patterns - v1.7.0

    @endo/patterns

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

    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
    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 object provides methods for creating pattern matchers organized into several categories:

    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

    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

    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())

    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)

    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
    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

    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

    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.

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

    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.

    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);

    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

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

    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())
    });
    // 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())

    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.

    Keys can be compared for equality and ordering:

    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)

    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.

    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

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

    For implementation details:

    Type Aliases

    AllLimits
    ArgGuard
    AwaitArgGuard
    AwaitArgGuardPayload
    ConfirmPattern
    CopyBag
    CopyMap
    CopySet
    CopyTaggedMethodGuard
    DefaultGuardType
    FullCompare
    GetRankCover
    GuardMakers
    InterfaceGuard
    InterfaceGuardPayload
    Key
    KeyCollection
    KeyCompare
    KeyComparison
    KeyToDBKey
    Kind
    Limits
    MakeInterfaceGuard
    MakeInterfaceGuardGeneral
    MakeInterfaceGuardSloppy
    MakeInterfaceGuardStrict
    Matcher
    MatcherNamespace
    Method
    MethodGuard
    MethodGuardMaker
    MethodGuardOptional
    MethodGuardPayload
    MethodGuardRest
    MethodGuardRestReturns
    MethodGuardReturns
    Pattern
    PatternMatchers
    RawGuard
    RawGuardPayload
    ScalarKey
    SyncValueGuard

    Variables

    assertPattern
    bagCompare
    bagDisjointSubtract
    bagIntersection
    bagIsSuperbag
    bagUnion
    containerHasSplit
    elementsCompare
    elementsDisjointSubtract
    elementsDisjointUnion
    elementsIntersection
    elementsIsDisjoint
    elementsIsSuperset
    elementsUnion
    getRankCover
    isPattern
    kindOf
    M
    matches
    mustMatch
    setCompare
    setDisjointSubtract
    setDisjointUnion
    setIntersection
    setIsDisjoint
    setIsSuperset
    setUnion

    Functions

    assertAwaitArgGuard
    assertCopyBag
    assertCopyMap
    assertCopySet
    assertInterfaceGuard
    assertKey
    assertMethodGuard
    assertRawGuard
    assertScalarKey
    coerceToBagEntries
    coerceToElements
    compareKeys
    getAwaitArgGuardPayload
    getCopyBagEntries
    getCopyMapEntries
    getCopySetKeys
    getInterfaceGuardPayload
    getInterfaceMethodKeys
    getMethodGuardPayload
    getNamedMethodGuards
    isAwaitArgGuard
    isCopyBag
    isCopyMap
    isCopySet
    isKey
    isRawGuard
    keyEQ
    keyGT
    keyGTE
    keyLT
    keyLTE
    listDifference
    makeCopyBag
    makeCopyBagFromElements
    makeCopyMap
    makeCopySet
    objectMap