@isdk/match-ex
【English|中文】
A declarative, weight-aware matching/validation engine: match an actual value against an expectation tree using operators ($and, $or, $contains, $each, ...), JSON Schema, semantic diffs and templates.
Framework-agnostic — usable in plain fixture-driven integration tests, response contract checks and the browser.
Looking for the full AI test runner (executor, fixtures, YAML, scoring reports)? See
@isdk/ai-test-runner, which builds on this engine. Its expectation syntax is identical to this one (and it registers the plugins for you), so it doubles as a source of usage examples.
Table of Contents
- Why match-ex?
- Architecture
- Installation
- Quick Start
- Core Concepts
- Operators
- Scoring & Metadata
- Custom Operators
- API Reference
Why match-ex?
- Declarative expectations — regexes, subsets, operators and custom functions compose into one expectation tree; plain objects mean partial matching by default.
- Weighted scoring, not just pass/fail — every node can carry a weight, dimensions and red-line (
critical) rules; the engine returns a gradedMatchResultwith per-path details. - Zero heavy dependencies — the core only depends on
diff,lodash-esandutil-ex. JSON Schema validation and template interpolation live in tiny opt-in plugins. - Isomorphic — no Node builtins; bundles cleanly for the browser.
- Semantic failure feedback — a failed string comparison automatically falls back to a structured diff (chars/words/lines/JSON) instead of just telling you "not equal".
Architecture
@isdk/match-ex core engine: operators, scoring strategies, diff,
template/schema registries (no implementations)
@isdk/match-ex-schema plugin: Ajv-backed JSON Schema (registers on import)
@isdk/match-ex-template plugin: {{placeholder}} interpolation (registers on import)
The core ships no template engine and no schema validator. Both are pluggable, and the engine degrades gracefully when a plugin is missing:
- Without
@isdk/match-ex-template, expectations are matched literally:{{...}}is not interpolated and no error is raised. Plain matching (strings, RegExp, operators, diff, …) keeps working. (getStringTemplate()still throws if you ask for the implementation explicitly.) - Without
@isdk/match-ex-schema, a heuristically detected JSON Schema expectation falls back to plain object matching. Only an explicit$schemaoperator throws with a hint to install the plugin.
The registry lives on globalThis under Symbol.for('@isdk/match-ex/registry'), so a plugin registers correctly even when duplicate copies of the core end up in the dependency tree.
Importing a plugin registers itself immediately:
import '@isdk/match-ex-schema'
import '@isdk/match-ex-template'
(@isdk/ai-test-runner already does this for you, so its behavior is unchanged out of the box.)
Installation
# core only
npm install @isdk/match-ex # or: pnpm add @isdk/match-ex
# with plugins
npm install @isdk/match-ex @isdk/match-ex-schema @isdk/match-ex-template
Quick Start
import { validate, ValidationContext } from '@isdk/match-ex'
import '@isdk/match-ex-schema' // optional: JSON Schema support
import '@isdk/match-ex-template' // optional: {{template}} support
const ctx = new ValidationContext({
data: { name: 'Alice' }, // template variables
input: {}, // fixture-level config (e.g. diff, diffPermissive)
maxScore: 100, // score ceiling (default 100)
scoring: true, // enable scoring mode
})
// Regex + partial object matching (undeclared keys are ignored)
let r = await validate({ role: 'assistant', content: 'Hello Alice!' },
{ content: /Hello {{name}}/ }, ctx)
// r.pass === true, r.score === 1
// Operators compose freely: sort, then assert on the first element
r = await validate([3, 1, 2],
{ $sort: { $by: (n: number) => n, $first: { $lte: 1 } } }, ctx)
// Weighted scoring with a red-line penalty
r = await validate('Hello World',
{ $or: [
{ $expect: /Hello/, $meta: { score: 80, critical: true } },
{ $expect: /BadWord/, $meta: { score: { value: -20, critical: true } } },
] }, ctx)
// r.score, r.pass, r.details (per-path scores), r.failures
validate() returns a MatchResult:
| Field | Description |
|---|---|
score |
Normalized score 0.0 ~ 1.0 (relative to the score allocated by the parent). |
pass |
Whether it passed (affected by critical red lines and threshold). |
failures |
MatchFailure[]: each entry carries key (path), message, expected, actual, plus diff for string failures. |
details |
MatchResultDetail[]: the scoring tree (key/title/dimension/weight/score/pass/critical). |
title / dimension / critical |
Metadata of the current node (injected via $meta / $ shorthands). |
Entry Points
| Export | Description |
|---|---|
validate(actual, expected, ctx) |
Main entry. Returns a graded MatchResult. |
validateMatch(actual, expected, options) |
Legacy wrapper returning only failures. Prefer validate. |
ValidationContext |
Carries matching options (data, input, strict, operators, scoring) through the recursion. |
OPERATORS |
Registry of built-in operators ($and, $contains, …). |
@isdk/match-ex/loader |
Subpath with loadOperators() / wrapCustomOperator() for dynamic operator loading. |
Core Concepts
Matching Semantics
Dispatch priority for each expected value:
- Operators — a plain object whose (only non-metadata) key is an operator such as
$and. - RegExp — tests the stringified actual value.
- String —
actual.includes(expected)when no diff requirement is set; otherwise falls back to semantic diff with character/word/line strategies. - Array — per-element recursive matching (length check in strict mode).
- Function —
(actual, input) => boolean | ValidationResult | score. - JSON Schema — instances of the registered
JsonSchemaType, or plain objects that pass theisJsonSchemaheuristic (disable viactx.disableHeuristicSchema). - Object — per-property recursive matching; keys support dot-paths (
user.name), and/regex/keys match any actual key. - Primitive — strict equality.
Additional rules:
- Partial matching: by default only the properties declared in the expectation are validated — extra keys in the actual object do not fail (
strictaside). - Auto-trim: string actual values are
trim()ed before each recursion level. - Metadata keys are not validated:
$meta, plus the$score/$title/ … shorthands when there is no$meta, are stripped; operator keys inside an object are likewise never treated as business fields.
// dot-path + regex key
await validate(
{ user_42: { profile: { name: 'Alice' } } },
{ '/^user_\\d+$/': { 'profile.name': 'Alice' } },
ctx
)
Strict Mode
Strict mode is configured through the strict option of ValidationContext (i.e. ctx.strict), which accepts true | false | 'object' | 'diff' | 'array' | string[]:
object: keys of the actual object that are not matched by the expectation cause a failure.array: array lengths must match.diff: diff is non-permissive (changes not listed in the whitelist fail).true: all of the above; the array form combines them, e.g.['object', 'array'].
const ctx = new ValidationContext({ strict: ['object', 'array'] })
await validate({ a: 1, b: 2 }, { a: 1 }, ctx) // pass === false (extra key b)
Templates
Expectations are interpolated before matching — including a RegExp's source, object key names and array items. Variables come from ctx.data (plus ctx.input, which takes precedence).
- Pure placeholder substitution: if a template string consists of a single variable (e.g.
{{user}}) and the value is an object/array/boolean, the whole template is replaced by that raw value rather than its stringified form. - Deep recursive resolution: a substituted value that is itself a template is rendered recursively.
- Dialects:
ctx.data.templateFormatacceptsdefault | fstring | golang | env | hf. - Plugin dependency: the implementation is provided by
@isdk/match-ex-template; without it,{{...}}throws.
const ctx = new ValidationContext({ data: { user: { id: 1, name: 'Alice' } } })
// "{{user}}" → { id: 1, name: 'Alice' } (the object itself, ready for deep matching)
await validate({ id: 1, name: 'Alice' }, '{{user}}', ctx)
// regex template → /Alice/
await validate('Hi Alice', '/{{user.name}}/', ctx)
JSON Schema Validation
Two ways to trigger it (both need the @isdk/match-ex-schema plugin):
- Heuristic detection: a plain object with a valid
type(string/number/integer/boolean/object/array/null) and no$contains/$all/$sequencekey is validated as JSON Schema. Turn it off withctx.disableHeuristicSchema = true. - Explicit
$schemaoperator: always validated as JSON Schema.
All ajv-formats and ajv-keywords keywords are available.
# explicit
profile:
$schema:
type: object
properties:
age: { type: number, minimum: 18 }
required: [age]
# heuristic (a top-level type is enough)
age: { type: number, minimum: 18 }
Semantic Diffing
Every string comparison in the engine falls back to the diff engine on failure — even without an explicit $diff: when actual.includes(expected) fails (or a diff requirement is configured), a structured diff runs and per-item changes are reported in MatchFailure.diff instead of a vague "mismatch".
Auto Detection
When no type is given (or diff: true / diff: 'auto'), the strategy is detected heuristically:
- Both sides parse as JSON object/array →
json(path-based, ignoring indentation and key order). - Either side contains a newline →
lines. - Either side is ≥ 20 chars and contains a space →
words. - Otherwise →
chars.
Strategy Types
auto | chars (default fallback) | words (whitespace ignored) | wordsWithSpace | lines | sentences | json
Whitelist & Permissive Mode
Configured through DiffOptions (either as the $diff operand or via ctx.input.diff):
| Option | Description |
|---|---|
type |
Diff strategy. |
items |
Whitelist of expected changes: { value?, path?, val?, added?, removed?, required?, score? }; value may be a regex string. |
permissive |
Ignore undeclared changes; only required: true items must appear as expected (same as ctx.diffPermissive). |
ignoreCase / ignoreWhitespace / ignoreNewlineAtEof / newlineIsToken / stripTrailingCr / intlSegmenter |
Fine-grained options forwarded to the diff library. |
# only make sure "User" was added — ignore everything else
output: "Hello"
diff:
permissive: true
items:
- { value: "User", added: true, required: true }
# structured JSON whitelist
diff:
type: json
items:
- path: "user.id"
val: 123
added: true
strict: 'diff'(orstrict: true) means non-permissive: any change missing from the whitelist fails.
Operators
Overview
An object may contain only one operator (besides metadata keys such as
$meta/$shorthands) — combine multiple conditions with$and/$or.
| Operator | Meaning | Default strategy |
|---|---|---|
$and |
All conditions must pass. | weighted |
$or |
At least one condition passes (highest score wins). | max |
$not |
Negates the nested expectation. | — |
$contains |
Array: contains a matching item · String: substring · Object: partial match. | max |
$all |
Array contains all expected items, unordered. | weighted |
$each |
Every element matches the single given rule. | weighted |
$sequence |
Array contains the items in order (noise allowed in between). | — |
$sort |
Sorts by $by first, then validates the rest. |
— |
$nth / $first / $last |
Extract element by index (negatives supported) and validate it. | — |
$eq $ne $gt $gte $lt $lte $in $nin |
Comparisons and membership. | — |
$expr |
Evaluate a JS expression string. | — |
$exists |
Key/value existence. | — |
$expect |
Transparent wrapper for attaching metadata to any node. | — |
$schema |
Explicit JSON Schema validation (needs the schema plugin). | — |
$diff |
Force semantic diff with whitelists/strategies. | — |
Logic & Collections
$and: an array of conditions; the actual value must satisfy all of them. The score is the weighted sum.$or: passes when any branch passes; the score is the highest weighted branch.$not: fails if the nested expectation matches, passes otherwise. Handy for blacklists:$not: /badword/.$contains: adapts to the target type — array "at least one item matches", string "substring", object "partial match".$all: the array must contain all expected items, in any order; extra elements are allowed.$each: every element must match the given single rule; an empty array passes; weights are spread evenly across elements.$sequence: the items must appear in order; unrelated items may be interleaved. Ideal for multi-step call chains.$exists: existence check (see below).
$and:
- "/^Hello/" # must start with Hello
- { $not: "/World/" } # and must not contain World
# "or" over a list of messages
$or:
- $contains: { role: 'assistant', tools: [{ name: 'get_user' }] }
- $contains: { role: 'assistant', tools: [{ name: 'find_person' }] }
# ordered: first get_user, then send_email (other steps allowed in between)
$sequence:
- { role: 'assistant', tools: [{ name: 'get_user' }] }
- { role: 'assistant', tools: [{ name: 'send_email' }] }
$exists has three modes:
| Form | Meaning |
|---|---|
$exists: true / false |
Shorthand: value is not undefined (null counts as absent). |
$exists: { $value: true, nullAsAbsent: true } |
null counts as present — only undefined is absent. |
$exists: { $value: false, strict: true } |
Strict: the key must be physically absent from the parent object (combinable with nullAsAbsent). |
Array Processing & Transformation
$sort: sorts the actual array first, then applies the remaining expectations to the sorted array (streaming wrapper).$byaccepts four formats:- string: property name, with a
-prefix for descending (e.g."-score"). - function:
(item, index, array) => value(async supported). - expression object:
{ "$expr": "item.score * 1.2", "order": "desc" }withitem(actualis an alias),index,array,data,inputandctxin scope. - array: any combination of the above for multi-level sorting.
- string: property name, with a
$nth: validates only the element at$index(default0, negatives like-1supported); out-of-range or empty arrays fail immediately.$first/$last: sugar for$nthwith$index: 0/-1.
$sort:
$by:
- { $expr: "item.score * 1.2", order: "desc" }
- "-createdAt"
$first: { status: "success" }
$nth: { $index: 1, status: "success" }
Comparisons
$eq/$ne: strict equality / inequality.$gt/$gte/$lt/$lte: ordering — handy for thresholds, scores and length constraints.$in/$nin: the actual value is / is not one of the given array values.
# only one operator per level — combine conditions with $and
score: { $and: [{ $gte: 60 }, { $lt: 100 }] }
status: { $in: ['active', 'pending'] }
The $expr Operator
Write a JavaScript expression string for cross-field, arithmetic or async decisions.
Scope variables:
| Variable | Description |
|---|---|
actual |
The actual value being validated. |
expected |
The current expectation (i.e. the expression string itself). |
data |
ctx.data (template variables). |
fixture |
ctx.input (fixture-level config). |
ctx |
The current ValidationContext; ctx.actual is the root actual value. |
loop |
Only inside array operators ($each, $contains, $all, $sequence): { index, first, last, length }. |
Return value semantics: boolean → 1/0; number → confidence (judged against threshold); { pass?, score?, ... } → used as-is; anything else is coerced to boolean. A thrown expression is reported as the failure reason.
# stand-ins for the missing $length / $keys / $values operators
$expr: "actual.length === 5"
# multi-property condition
$expr: "actual.price * actual.quantity >= 500 && data.userRole === 'admin'"
# use `loop` for first/rest assertions
$each: { $expr: "loop.first ? actual.status === 'init' : actual.status === 'done'" }
Special Operators
$expect: transparent container that delegates to its value — used purely to attach scoring metadata (weight, red line, title, dimension) to any node.$schema: explicit JSON Schema validation.$diff: force semantic diffing with a given strategy/whitelist; the operand may be a string or{ value | expected, type, items, permissive, ... }.
Scoring & Metadata
Core Options (ValidationContext)
| Option | Description |
|---|---|
scoring |
`true |
maxScore |
Score ceiling, default 100; also the scale used for weight normalization. |
passScore |
Passing threshold, defaults to maxScore. The engine only carries it through — whether a run passes is decided by the caller (e.g. ai-test-runner). |
unassignedWeight |
Alias of totalUnassignedWeight: budget reserved for all items without an explicit score. Defaults to 0.1. |
autoConfidence |
Defaults to true: weights between 0 and 1 are treated as confidence (percentages); 'force' skips the heuristic. |
threshold |
Confidence threshold for fuzzy matching (leaf nodes) — a lower score fails. |
allocatedScore |
Score budget allocated by the parent to this node; defaults to maxScore. |
strategy |
The active ScoringStrategy (defaults to weighted when unset). |
Hierarchical Relative Weights
Scoring follows a "allocate top-down, aggregate bottom-up" model:
- Weight normalization: siblings at the same level compete for the parent's
allocatedScore. - Automatic scale:
0~1is read as a percentage,>= 1as absolute points, converted withscale = max(maxScore, largest explicit weight). - Unassigned items: share the
totalUnassignedWeightbudget (when space is short, explicit items are compressed to keep that budget). - Negative scores (penalties): do not compete for the
1.0budget of positive items — they are converted to a negative weight directly.
$meta and $ Shorthands
Note:
score,title,critical,description,dimensionand friends are business data; all metadata must be declared through the$metacontainer or$-prefixed shorthand keys. The two are mutually exclusive: once$metais present,$shorthand keys are no longer metadata (they are ignored), which keeps business field names safe.
Recommended: the explicit $meta container (keeps the top-level namespace clean so you can still validate same-named fields)
someField:
score: 100 # business data: the output must contain a `score` field
$meta:
score: 80 # metadata: the weight of this check
title: "Core field"
Shorthand: $ prefixes (only when the object has no $meta)
$score: 80 # number: weight (0~1 = percentage, >= 1 = absolute points)
$title: "Core field"
$score: { value: -20, critical: true } # object: penalty + red line
$title: "Security penalty"
$dimension: 'security' # (optional) dimension tag (may also live inside $score)
$critical: true # (optional) red line
$strategy: 'weighted' # (optional) aggregation strategy for children
$threshold: 0.75 # (optional) confidence threshold for fuzzy matching
$description: '...' # (optional) description
Penalties & Red Lines
- Negative values are penalty offsets: a penalty matching means points are deducted.
- When a matched penalty is marked
critical, the whole match fails (red line) and a virtualcriticalfailure is emitted so you can tell which red line was hit. - A reward item (
weight >= 0) that fails makes the aggregate fail.
Scoring Strategies
| Strategy | Aggregation | Used by default in |
|---|---|---|
weighted (alias and) |
score = Σ(child.score × child.weight), every reward must pass |
$and, objects, arrays, $each, $all |
max (alias or) |
score = max(child.score × child.weight), any pass is a pass |
$or, $contains |
- Priority: operator-declared
strategy(e.g.$or.strategy = 'max', which keeps operator semantics intact) > user$strategy(or$meta.strategy) > inheritedctx.strategy. - Strategies are referenced by name:
weighted/and,max/or; unknown names fall back toweighted(thestrategiesregistry andgetStrategy()are currently internal and not exported from the core entry).
$expect: The Scoring Wrapper
$expect is a virtual container you can use anywhere to inject scoring metadata:
output:
$and:
- $expect: /spring/
$meta: { score: 80, dimension: 'accuracy', critical: true, title: "Core keyword" }
- $expect: /flower/
$score: 20
$threshold: 0.5 # fails if the match confidence is below 50%
- $expect: /badword/
$score: { value: -50, dimension: 'security' }
$title: "Security penalty"
$diff: Scoring Each Change
Score each item of a diff whitelist (requires scoring):
$diff:
items:
- value: "core conclusion"
added: true
$score: { value: 90, critical: true }
- value: "filler word"
added: true
$score: 10
permissive: true # score only by the whitelist; ignore other changes
Custom Operators
Inline Operators
Register them via ctx.operators; keys are conventionally $-prefixed (consistent with the built-ins):
import { ValidationContext, validate } from '@isdk/match-ex'
import { wrapCustomOperator } from '@isdk/match-ex/loader'
const ctx = new ValidationContext({
operators: {
$length: wrapCustomOperator((actual, expected) => actual?.length === expected),
},
})
await validate('abc', { $length: 3 }, ctx)
Like
loadOperators,wrapCustomOperatorlives behind the@isdk/match-ex/loadersubpath (the core entry deliberately does not export it).
wrapCustomOperator adapts the simplified signature (actual, expected, fixture), where fixture exposes:
| Property | Description |
|---|---|
$data |
The ctx.data object. |
$options |
Auxiliary parameters coming from the expected.$value wrapper (see below). |
$validate |
(act, exp) => validate(act, exp, ctx) for recursive reuse of the engine. |
| rest | ctx.input is spread in (placed under input when it is not an object). |
Return Value Mapping
| Return type | Mapping | Use case |
|---|---|---|
boolean |
true → score 1.0, passed; false → 0.0, failed. |
Simple true/false assertions. |
string |
Treated as the failure message: score 0.0, failed. | Assertions needing a concrete reason. |
number |
0.0 ~ 1.0 confidence; pass/fail determined by threshold. |
Fuzzy / similarity matching. |
Object |
Must contain score or pass (or failures); may add message, dimension, details. |
Dimension info or a custom failure list. |
The $value Convention: Value vs. Options
When a $value structure is used, $value becomes expected and every sibling property is collected into fixture.$options:
output:
$checkCode:
$value: "function sum" # main expectation → expected
timeout: 1000 # auxiliary options → fixture.$options
strict: true
export function checkCode(actual, expected, fixture) {
const { timeout, strict } = fixture.$options
// expected === "function sum"
}
Logic Container Operators
Operators that validate several children (a custom $and, say) use the full signature (actual, expected, ctx, validate) and the ValidationContext helpers:
export async function myContainer(actual, expectedList, ctx, validate) {
const weights = ctx.distribute(expectedList) // automatic weight allocation
const results = []
for (let i = 0; i < expectedList.length; i++) {
const subCtx = ctx.createChildContext(i, expectedList.length) // automatic paths
results.push(await validate(actual[i], expectedList[i], subCtx))
}
return ctx.aggregate(results, weights) // applies myContainer.strategy
}
myContainer.virtual = true
myContainer.strategy = 'weighted' // lock to the weighted-sum strategy
- The full signature (4 parameters) is used as-is; the simplified one (≤ 3 parameters) is wrapped by
wrapCustomOperator. ctx.distribute(items): normalizes a list ofScoreConfiginto weights using the active strategy.ctx.aggregate(results, weights): aggregates with the active strategy and fills indetails.ctx.createChildContext(keyOrIndex, count, options): handles path levels automatically.
Virtual Path Strategy (virtual)
An operator declares how it appears in the scoring tree through its virtual property, following a "default virtual" rule:
| Value | Behavior |
|---|---|
true (default) |
Virtual: fully transparent for a single child (it inherits the parent path); readable branches such as $and[0] are generated for multiple children. |
false |
Physical: keeps the operator level (e.g. output.$myOp), children land under [index]. |
string |
Custom path template with $key, $index, $count and $operator variables (e.g. $operator[$key]). |
Dynamic Loading with loadOperators
String-based operator modules load through the @isdk/match-ex/loader subpath (the core entry deliberately omits it, since the runtime-computed dynamic import() cannot be analyzed by bundlers):
import { loadOperators } from '@isdk/match-ex/loader'
const operators = await loadOperators({
$len: 'js://./my-operators.js#checkLength', // path#exportName
checkCode: './checkers.js#checkCode', // the $ prefix is added automatically
}, baseDir)
// array form: names are inferred (export name first, otherwise the camelCased filename)
const ops = await loadOperators(['./checkers.js#checkCode', 'my-test-utils#validator'], baseDir)
- The
js://protocol prefix is optional (it is the default). - Relative paths resolve against the second argument,
baseDir(passed through untouched when omitted). - Absolute paths and bare npm specifiers (e.g.
lodash-es#isEqual) work too; the part after#is the export name, defaulting todefault. - Loaded functions are again classified by parameter count (full vs. simplified signature).
Related Options
allowOperatorOverride: defaults tofalse(built-ins win). Set totrueto let custom operators override built-ins such as$contains.ctx.operators: the inline operator map; keys should start with$—loadOperators()adds the missing prefix automatically.
API Reference
Exports
| Export | Description |
|---|---|
validate(actual, expected, ctx) |
Main entry; returns a MatchResult. |
validateMatch(actual, expected, options) |
Legacy wrapper returning failures (also pushed into options.failures). |
ValidationContext |
Context class: createSubContext(subKey, options), createChildContext(keyOrIndex, count, options), distribute(items), aggregate(results, weights). |
OPERATORS |
Registry of built-in operators. |
isStrict(type, strict) |
Strict-mode helper. |
getDiff(expected, actual, options) |
Produces DiffItem[]. |
validateStringDiff(actual, expected, ctx, options) |
String diff validation (whitelist / scoring). |
hasDiffChanges(diff) |
Whether the diff contains additions/removals. |
formatTemplate(value, options) / formatObject(input, options) |
Template interpolation (single value / recursive over objects and keys). |
setStringTemplate(impl) / getStringTemplate() |
Register / read the interpolation implementation (contract: { formatIf(options) }). |
setJsonSchemaType(ctor) / getJsonSchemaType() / resolveJsonSchemaType() |
Register / read the JSON Schema implementation. |
isJsonSchema(obj) |
Heuristic detection of plain-object JSON Schemas. |
JsonSchemaType |
Abstract base class for custom validators (create(schema) / validate(value) / getErrors()). |
calculateNormalizedWeights(weights, options) |
Core weight normalization (mixing percentages and absolute points). |
getScoreConfig(item) / isMetadataKey(key, item) |
Metadata helpers. |
@isdk/match-ex/loader |
loadOperators(), wrapCustomOperator(), resolveModuleUrl(). |
ValidationContext Options
| Option | Default | Description |
|---|---|---|
key |
'' |
Current path (maintained by the engine). |
data |
{} |
Template variables (may contain templateFormat). |
input |
— | Fixture-level config; merged into the template variables. Its diff / diffPermissive are read by the diff engine. |
strict |
— | Strict mode: true|false|'object'|'diff'|'array'|string[]. |
diffPermissive |
— | Global permissive diff. |
disableHeuristicSchema |
false |
Disable JSON Schema heuristic detection. |
operators |
— | Custom operator map. |
allowOperatorOverride |
false |
Allow overriding built-in operators. |
scoring |
— | Scoring mode switch. |
maxScore / passScore |
100 / maxScore |
Score ceiling and passing score. |
unassignedWeight |
0.1 |
Weight budget for unassigned items. |
allocatedScore |
maxScore |
Score budget of the current node. |
strategy |
weighted |
Scoring strategy instance. |
threshold / autoConfidence |
— | Confidence threshold / automatic percentage-weight detection. |
loop |
— | Array iteration info ({ index, first, last, length }). |
actual |
— | Root actual value (for $expr / custom operators). |
Core Types
interface MatchResult {
score: number // 0.0 - 1.0
pass: boolean
failures: MatchFailure[] // { key?, message?, expected?, actual?, diff?, critical? }
details?: MatchResultDetail[]
title?: string
dimension?: string
critical?: boolean
}
interface MatchResultDetail {
key: string // path
title?: string
dimension?: string
score: number // score relative to the allocated weight
weight: number // normalized weight
pass: boolean
critical?: boolean
details?: MatchResultDetail[]
}
Development
pnpm test # vitest
pnpm build # tsup (ESM + CJS + dts)
License
MIT Riceball Lee