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

@veridtools/dmn-diff

v0.5.1

Published

Semantic diff for DMN files — compare decision models by meaning, not by XML. Supports DMN 1.0 to 1.5.

Readme

@veridtools/dmn-diff

npm license ci docs dependencies

Semantic diff for DMN files. Compares two .dmn files and produces a structured representation of their differences — ID-based identity, severity classification, multiple output formats, and native CI/CD integration.

DMN 1.0 → 1.5 · MIT licensed · 1 runtime dependency (@veridtools/dmn-parser)

Why

DMN files are XML, but diffing them as text is noisy. Reordering decision table rules, reformatting whitespace, moving DMNDI diagram coordinates — all of these produce textual diffs that carry zero semantic meaning. Code review tools can't tell which changes actually affect runtime behaviour and which are cosmetic edits.

dmn-diff understands the structure of DMN. It matches elements by their @id attribute, not position, so reordering rules produces zero diff. It classifies every field change by severity — breaking, non-breaking, or cosmetic — so reviewers can focus on what matters. It strips DMNDI, normalises encoding artefacts (BOM, CDATA, hitPolicy casing), and exposes a clean JSON result for tooling.

The result is a diff you can act on: block PRs on breaking changes, flag non-breaking changes for review, and auto-approve cosmetic-only commits.

At a glance

  • ID-based identity — elements matched by @id, not position; reordering rules produces zero diff
  • Severity classification — every field change is breaking, non-breaking, or cosmetic
  • Four output modes — semantic prose, row-diff table, JSON, raw XML diff
  • CI/CD native — exit code 1 on changes, 0 when clean; pipe-friendly output
  • Full normalization — CDATA vs entities, empty wildcards, hitPolicy defaults, BOM, DMNDI exclusion

Installation

pnpm add @veridtools/dmn-diff
# or
npm install @veridtools/dmn-diff

For global CLI use:

npm install -g @veridtools/dmn-diff

CLI

dmn-diff before.dmn after.dmn                       # semantic output (default)
dmn-diff before.dmn after.dmn --rows    # or -r     # row-diff table with +/-
dmn-diff before.dmn after.dmn --json    # or -j     # structured JSON
dmn-diff before.dmn after.dmn --xml     # or -x     # raw XML line diff (colored)
dmn-diff before.dmn after.dmn --no-color   # or -n  # disable ANSI colors
dmn-diff before.dmn after.dmn --skip-breaking # or -s  # show diff, always exit 0
dmn-diff --help                             # or -h     # show banner, version, options

Exit code 1 when changes are detected — works as a CI gate out of the box:

Try it locally

Clone the repo and run against the bundled examples:

git clone https://github.com/veridtools/dmn-diff
cd dmn-diff
npm install
npx tsx bin/dmn-diff.ts src/examples/a.dmn src/examples/b.dmn
npx tsx bin/dmn-diff.ts src/examples/a.dmn src/examples/b.dmn --rows
npx tsx bin/dmn-diff.ts src/examples/a.dmn src/examples/b.dmn --json
npx tsx bin/dmn-diff.ts src/examples/a.dmn src/examples/b.dmn --xml
npx tsx bin/dmn-diff.ts src/examples/a.dmn src/examples/b.dmn --skip-breaking
dmn-diff main.dmn feature.dmn || echo "DMN changes detected"

GitHub Actions example

- name: Check DMN changes
  run: |
    dmn-diff main.dmn feature.dmn --json > diff.json
    node -e "
      const r = require('./diff.json');
      const breaking = r.changes.flatMap(c => c.fieldChanges ?? []).filter(f => f.severity === 'breaking');
      if (breaking.length) { console.error('Breaking DMN changes detected'); process.exit(1); }
    "

Example output

before.dmn → after.dmn

⚠  1 breaking change(s) detected

~ outputClause "riskLevel" (oc1) modified
    @name: level → riskLevel  ⚠ BREAKING

~ inputEntry "r1ie1" (r1ie1) modified
    text: > 700 → >= 700

0 added · 2 modified · 0 removed · 12 unchanged

Programmatic API

import { diff, renderSemantic } from '@veridtools/dmn-diff'
import { readFileSync } from 'fs'

const from = readFileSync('before.dmn', 'utf-8')
const to   = readFileSync('after.dmn',  'utf-8')

const result = diff(from, to, { fromFile: 'before.dmn', toFile: 'after.dmn' })

// Check for breaking changes
const hasBreaking = result.changes.some(
  (c) => c.type === 'modified' && c.fieldChanges.some((f) => f.severity === 'breaking')
)

// Filter changes in a specific decision
const decisionChanges = result.changes.filter((c) => c.path.includes('decision[d1]'))

// Render
console.log(renderSemantic(result))

DiffResult shape

interface DiffResult {
  meta: {
    fromFile: string
    toFile: string
    timestamp: string    // ISO 8601
    hasChanges: boolean
  }
  summary: {
    added: number
    removed: number
    modified: number
    unchanged: number
  }
  changes: Change[]      // AddedChange | RemovedChange | ModifiedChange
}

interface ModifiedChange {
  type: 'modified'
  id: string
  name: string           // element name, falls back to id when the element has no name
  elementType: ElementType
  parentId?: string
  path: string           // e.g. "decision[d1].decisionTable[dt1].rule[r1].inputEntry[r1ie1]"
  fieldChanges: FieldChange[]
}

interface FieldChange {
  field: string          // e.g. "text", "@name", "inputExpression.@typeRef"
  from: unknown
  to: unknown
  severity: 'breaking' | 'non-breaking' | 'cosmetic'
}

Renderers

import { renderSemantic, renderRows, renderJson, renderXml, tokenize } from '@veridtools/dmn-diff'

renderSemantic(result)         // human-readable prose (default CLI output)
renderRows(result)             // row-diff table with +/- per field
renderJson(result)             // JSON.stringify of DiffResult
renderXml(fromXml, toXml)     // raw line diff of XML source (includes DMNDI)
tokenize(result)               // DiffToken[] — structured intermediate representation
detectFormat(output)           // 'json' | 'semantic' | 'rows' — identify a rendered string's format

tokenize accepts either a DiffResult or the string output of any renderer, and returns a flat DiffToken[] stream. Use it to build custom formatters (HTML, React, Monaco) without re-implementing diff logic, or to parse dmn-diff CLI output back into structured tokens for downstream tooling. JSON output round-trips losslessly; semantic and rows formats recover element types, ids, field changes, and severities, but not path or parentId.

Severity reference

Severity is assigned per field, not per element. A single modification can carry changes of mixed severity — the summary calls out the worst one.

| Severity | Meaning | Examples | |----------|---------|---------| | breaking | Changes runtime evaluation — expression, type, name used as reference | decision.@name, outputClause.@name, itemDefinition.@typeRef, functionDefinition.@kind | | non-breaking | Changes behaviour but not in a way that breaks existing consumers | inputEntry.text, outputEntry.text, decisionTable.@hitPolicy, itemDefinition.allowedValues | | cosmetic | No runtime impact | *.@label, *.@description, textAnnotation.text, decisionTable.@preferredOrientation |

What gets normalized away

The following differences produce a zero diff result — they are encoding or tooling artefacts, not semantic changes:

| Source difference | Normalized to | |-------------------|--------------| | hitPolicy absent | "UNIQUE" | | hitPolicy="unique" | "UNIQUE" (uppercase) | | preferredOrientation absent | "Rule-as-Row" | | isCollection="false" | treated same as absent | | isCollection="true" | true (boolean) | | Empty <inputEntry> | "-" (DMN wildcard) | | <![CDATA[> 5]]> | > 5 (via entity escaping) | | UTF-8 BOM | removed | | <DMNDI> section | removed entirely | | <extensionElements> | removed entirely | | <typeRef>string</typeRef> (child element) | @typeRef="string" (attribute form) | | <description>…</description> (child element) | @description="…" (attribute form) |

DMN version support

| Version | Namespace | |---------|-----------| | 1.1 | http://www.omg.org/spec/DMN/20151101/dmn.xsd | | 1.2 | http://www.omg.org/spec/DMN/20180521/MODEL/ | | 1.3 | https://www.omg.org/spec/DMN/20191111/MODEL/ | | 1.4 | https://www.omg.org/spec/DMN/20211108/MODEL/ | | 1.5 | https://www.omg.org/spec/DMN/20230324/MODEL/ |

DMN 1.4 boxed expressions (conditional, filter, iterator) and DMN 1.5 specifics (typeConstraint, optional import.namespace, DMN15-74 BKM variable.typeRef removal) are fully handled.

Testing

The test suite is backed by @veridtools/dmn-fixtures — a curated set of 300+ real-world DMN files covering every element type, all five DMN versions, and edge cases (BOM, CDATA, DMNDI-only changes, empty wildcards, duplicate namespace declarations). Fixture-driven tests verify both that known-equivalent variants produce zero diff and that known-different variants produce the expected change type and severity.

Contributing

See CONTRIBUTING.md.

License

MIT