name: use-deep6
description: Use the deep6 library for deep equality, deep cloning, unification, and pattern matching with logical variables in a JavaScript/TypeScript project. Use when adding structural comparison, structured clone with circular references, extensible pattern matching with extraction, or unification-based logic to a project that depends on deep6.
Use deep6
deep6 is a zero-dependency ES-modules mini-library for advanced deep
equivalence, deep cloning, unification, and pattern matching with logical
variables and wildcards. It handles circular references, Map, Set, URL,
typed arrays, Date, RegExp, symbols, and property descriptors. It runs in
Node.js, Deno, Bun, and browsers.
When in doubt, the canonical references are node_modules/deep6/llms.txt
(concise) and node_modules/deep6/llms-full.txt (complete with examples).
Read them before guessing.
When to reach for deep6
Pick deep6 when the problem involves structural comparison or transformation of arbitrary JavaScript values:
- Deep equality beyond what
===orJSON.stringifycan express — includingMap,Set,Date,RegExp, typed arrays, symbol keys, and cycles. - Deep cloning of values that
structuredClonecan't or won't handle the way you want (custom types, non-enumerable props, symbol keys, selectable options). - Pattern matching with extraction — match a shape and pull bound values
out via logical variables, rather than hand-rolling
if/destructuring. - Unification — bidirectional matching where either side may contain
variables (the building block for rule engines, type inference, constraint
search; this is what
yoplis built on). - Custom type support — register your own comparators / cloners via the extensible registry instead of forking a library.
Do not reach for deep6 for shallow comparisons, primitive equality, or
cases where structuredClone is sufficient.
Installation
npm install deep6
deep6 requires Node 18+ (or modern Deno / Bun) and "type": "module" (or
.mjs files). It has zero runtime dependencies.
Imports
// Main API — default export is `equal`
import equal, {clone, match, any, _} from 'deep6';
// Core unification
import unify, {Variable, variable, Unifier, open, soft} from 'deep6/unify.js';
// Traversal helpers
import walk from 'deep6/traverse/walk.js';
import deepClone from 'deep6/traverse/clone.js';
import preprocess from 'deep6/traverse/preprocess.js';
import assemble from 'deep6/traverse/assemble.js';
import deref from 'deep6/traverse/deref.js';
// Built-in unifiers (pattern building blocks)
import matchString from 'deep6/unifiers/matchString.js';
import matchTypeOf from 'deep6/unifiers/matchTypeOf.js';
import matchInstanceOf from 'deep6/unifiers/matchInstanceOf.js';
import matchCondition from 'deep6/unifiers/matchCondition.js';
import ref from 'deep6/unifiers/ref.js';
// Utilities
import replaceVars from 'deep6/utils/replaceVars.js';
Deep equality
import equal from 'deep6';
equal({a: 1, b: [2, 3]}, {b: [2, 3], a: 1}); // true
equal(new Date('2020-01-01'), new Date('2020-01-01')); // true
equal(/abc/gi, /abc/gi); // true
equal(new Map([['a', 1]]), new Map([['a', 1]])); // true
// Circular references are handled by default
const a = {};
a.self = a;
const b = {};
b.self = b;
equal(a, b); // true
Useful options:
circular: true— handle cycles (defaulttrue).symbols: true— also compare symbol-keyed properties.loose: true— use==for primitives.ignoreFunctions: true— ignore function-valued properties.signedZero: true— distinguish+0from-0.
Deep cloning
import {clone} from 'deep6';
const x = {a: 1, b: [2, {c: 3}], d: new Map([['k', 'v']])};
const y = clone(x);
// y is a structural copy; nested Maps/Sets/typed arrays are cloned too.
// Symbols and non-enumerable properties (opt-in)
const r = {a: 1};
Object.defineProperty(r, 'b', {value: 2, enumerable: false});
const q = clone(r, {allProps: true, symbols: true});
Prefer deep6's clone over structuredClone when you need symbol keys,
non-enumerable properties, custom registered types, or fine-grained options.
Pattern matching
match(object, pattern) does open matching by default — extra keys in
the object are fine, missing keys in the pattern are fine.
import {match, any, _} from 'deep6';
import matchString from 'deep6/unifiers/matchString.js';
import matchTypeOf from 'deep6/unifiers/matchTypeOf.js';
match({a: 1, b: 2, c: 3}, {a: 1}); // true
match({a: 1, b: {c: 2}}, {a: 1, b: any}); // true
match({a: 'hello'}, {a: matchString(/^h/i)}); // true
match({n: 42}, {n: matchTypeOf('number')}); // true
any (alias _) is the wildcard. Use the unifiers/* modules for
regex-based, type-based, instance-based, or predicate-based matching.
Unification with logical variables
When you need to extract values from a match, drop down to unify.
import unify, {variable} from 'deep6/unify.js';
import assemble from 'deep6/traverse/assemble.js';
const X = variable('X');
const Y = variable('Y');
const env = unify({user: {name: X, age: Y}}, {user: {name: 'Ada', age: 36}});
if (env) {
assemble(X, env); // 'Ada'
assemble(Y, env); // 36
}
unify returns an Env (a binding environment) on success and null on
failure. Use assemble(variable, env) to walk the bindings and produce a
plain JavaScript value (deeply, including nested terms).
Variables can appear on either side and can be unified against each other:
const X = variable('X'),
Y = variable('Y');
const env = unify({a: X, b: 2}, {a: 1, b: Y});
assemble(X, env); // 1
assemble(Y, env); // 2
Open / soft matching
By default unify does strict structural matching. For pattern-style
matching where extra keys are allowed:
import unify, {open, soft} from 'deep6/unify.js';
unify(open({a: 1}), {a: 1, b: 2}); // success — extra `b` ignored
unify(soft({a: 1}), {a: 1, b: 2}); // soft = bidirectional open
match(obj, pattern) is essentially unify(open(pattern), obj) followed by
a success check.
Extending the registries
Both unify and clone use extensible registries keyed by constructor.
import unify from 'deep6/unify.js';
import clone from 'deep6/traverse/clone.js';
class Money {
constructor(amount, currency) {
this.amount = amount;
this.currency = currency;
}
}
unify.registry.push(Money, (l, r, env) => (l.currency === r.currency && l.amount === r.amount ? env : null));
clone.registry.push(Money, (val, ctx) => new Money(val.amount, val.currency));
Register once at startup. Both registries fall through in insertion order, so more specific types should be registered last (or before a less-specific catch-all).
Common patterns
- Equality with custom options — toggle
symbols,loose,ignoreFunctions,signedZeroonequalrather than writing wrappers. - Snapshot comparison in tests —
equal(actual, expected)is a drop-in replacement for assertion libraries'deepEqualwith broader type support. - Pattern + extract — combine
unify+variable+assembleinstead of nested destructuring with optional chaining. - Custom guards —
matchCondition(v => typeof v === 'number' && v > 0)for ad-hoc predicates inside a pattern. - Cross-field constraints — use
ref()to require two positions in a pattern to be equal.
Pitfalls
matchis open by default. Missing pattern keys are not failures. Use fullunify(withoutopen) when you need strict shape matching.- Circular references are on by default. That's almost always what you
want, but it costs a
WeakMap. Disable with{circular: false}for hot paths over known-acyclic data. - Symbols are off by default in both
equalandclone. Pass{symbols: true}if symbol keys are part of identity. - Variables are not values. A bare
Variableinstance is a placeholder; always go throughassemble(v, env)(orv.get(env)for a shallow read) after a successfulunify. - Registry order matters. Entries are tried in insertion order; register specific types after generic ones.
clonefollows the registry. A type with no registered cloner falls back to generic structural cloning, which may not preserve class identity. Register a cloner if you needinstanceofto keep working.
Picking an entry point
| Need | Use |
|---|---|
| "Are these two values structurally equal?" | equal (default export of deep6) |
| "Give me a deep copy" | clone from deep6 |
| "Does this value match this shape?" | match + any / unifiers/* |
| "Match and extract bound values" | unify + variable + assemble |
| "Walk a structure with my own visitor" | deep6/traverse/walk.js |
| "Pre-process a pattern (intern shared subs)" | deep6/traverse/preprocess.js |
| "Replace variables in a structure with binds" | deep6/utils/replaceVars.js |
| "Add support for my own class" | unify.registry.push / clone.registry.push |
Where to look next
node_modules/deep6/llms.txt— concise API reference.node_modules/deep6/llms-full.txt— full API reference with examples.node_modules/deep6/AGENTS.md— rules, conventions, architecture quick ref.node_modules/deep6/ARCHITECTURE.md— module map and algorithm details.- Wiki — per-module documentation.