Runnable examples in this document are verified with texample (npm run test:md). See Examples for more.
Executable DMN definitions — the top-level element that walks the decision requirement graph (DRG) and evaluates decisions.
context: aContextwrapping the parsed dmn-moddle definitions and anEnvironment— the host parses DMN XML with dmn-moddle and passes the root elementoptions: optional environment options — when passed, the definition clones the context environment with the overrides
import { DmnModdle } from 'dmn-moddle';
import { Context, Definition, Environment } from 'dmn-elements';
const { rootElement } = await new DmnModdle().fromXML(source);
const definition = new Definition(new Context(rootElement, new Environment()));Evaluate a decision and, recursively, its required decisions. Requirements bind under each element's variable name: required decisions evaluate first (memoized per run), input data values pull from input, and business knowledge models and decision services bind as FEEL-invocable functions.
decisionId: id of admn:Decision— or admn:DecisionService, whose input decision values must then be provided ininputbound by variable nameinput: input data values by variable name, merged over environment variablescallback: optional node style callback(err, result)— without it a promise is returned
Returns the bare decision result: a single decision table output yields the value, multiple outputs an object keyed by output name; a multi-output decision service yields an object keyed by output decision variable name. Evaluation failures — hit policy violations, failed type coercions, FEEL errors — reject with a DecisionError.
Evaluation is stateless: every call is a fresh run, nothing survives between calls.
Evaluate like evaluate, resolving with { result, trace } — the evaluated elements in completion order (dependencies before dependents), each with its requirement bindings, and hit policy resolution for decision tables. See the trace example.
Returns the dmn-moddle decision definition, or undefined.
new Environment(options) carries the pluggable seams shared by an evaluation:
| Option | Purpose |
|---|---|
variables |
base FEEL context — evaluation input takes precedence |
settings |
engine configuration, see Settings |
expressions |
FEEL engine, feelin by default — a replacement must implement resolveExpression(expression, context) and unaryTest(test, context) |
services |
named host functions, available through getServiceByName |
output |
shared output object |
extensions |
named extension functions decorating elements beyond the DMN schema, see Extensions |
Logger |
logger factory (scope) => ({ debug, error, warn }), silent by default — see Debug |
The element behaviours only understand the DMN 1.3 schema, but models often carry more — vendor attributes like camunda:versionTag, or extensionElements. Extensions decorate elements with such semantics.
An extension is a function extension(element, context), registered by name on the environment extensions option. It is called once per DRG element, when the element is minted — read vendor attributes from element.behaviour.$attrs and extension elements from element.behaviour.extensionElements, and decorate the element as needed. Optionally return hooks that run around each evaluation:
activate(executeMessage): runs before the element evaluates —executeMessage.inputholds the evaluation input, andexecuteMessage.tracethe element's trace entry when the element is traced, open for annotationdeactivate(completeMessage): runs when the evaluation completes — the execute message plusresult, orerrorwhen the evaluation failed
Return nothing to only decorate at mint time.
import { DmnModdle } from 'dmn-moddle';
import { Context, Definition, Environment } from 'dmn-elements';
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:camunda="http://camunda.org/schema/1.0/dmn" id="approvalDefinitions" name="Approval" namespace="https://example.com/dmn/approval">
<decision id="approval" name="Approval" camunda:versionTag="2.1.0">
<variable id="approvalVariable" name="Approval" typeRef="string" />
<literalExpression id="approvalExpression"><text>if Amount < 1000 then "auto" else "manual"</text></literalExpression>
</decision>
</definitions>`;
const environment = new Environment({
extensions: {
versionTag(element, { environment }) {
const versionTag = element.behaviour.$attrs?.['camunda:versionTag'];
if (!versionTag) return;
return {
deactivate(completeMessage) {
environment.output[element.id] = { versionTag, result: completeMessage.result };
},
};
},
},
});
const { rootElement } = await new DmnModdle().fromXML(source);
const definition = new Definition(new Context(rootElement, environment));
console.log(await definition.evaluate('approval', { Amount: 250 }));
// auto
console.log(environment.output);
// { approval: { versionTag: '2.1.0', result: 'auto' } }Extensions read $attrs for vendor attributes unknown to dmn-moddle. To parse a vendor schema into first-class moddle properties instead, pass the vendor's moddle descriptor to new DmnModdle({ camunda: ... }) — the extension then reads the typed properties off element.behaviour.
Settings are plain values on environment.settings.
Per-typeRef coercion overrides: a map of typeRef (exact match) to coercion function (value, typeRef, element) => coerced. An override takes precedence over the builtin FEEL types and item definitions, and owns its validation — throw a DecisionError to fail the evaluation.
import { DmnModdle } from 'dmn-moddle';
import { Context, Definition, Environment } from 'dmn-elements';
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" id="gradeDefinitions" name="Grade" namespace="https://example.com/dmn/grade">
<decision id="grade" name="Grade">
<variable id="gradeVariable" name="Grade" typeRef="string" />
<decisionTable id="gradeTable" hitPolicy="FIRST">
<input id="scoreInput">
<inputExpression id="scoreInputExpression" typeRef="tScore"><text>Score</text></inputExpression>
</input>
<output id="gradeOutput" name="grade" />
<rule id="goldRule">
<inputEntry id="goldEntry"><text>> 80</text></inputEntry>
<outputEntry id="goldGrade"><text>"gold"</text></outputEntry>
</rule>
<rule id="silverRule">
<inputEntry id="silverEntry"><text>-</text></inputEntry>
<outputEntry id="silverGrade"><text>"silver"</text></outputEntry>
</rule>
</decisionTable>
</decision>
</definitions>`;
const environment = new Environment({
settings: {
types: {
tScore(value) {
return Number(String(value).replace(' pts', ''));
},
},
},
});
const { rootElement } = await new DmnModdle().fromXML(source);
const definition = new Definition(new Context(rootElement, environment));
console.log(await definition.evaluate('grade', { Score: '85 pts' }));
// goldOff by default. When true, every decision result is coerced and validated against the decision variable's typeRef before the decision completes — a result outside an item definition's allowed values fails the evaluation instead of flowing downstream.
import { DmnModdle } from 'dmn-moddle';
import { Context, Definition, Environment } from 'dmn-elements';
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" id="riskDefinitions" name="Risk" namespace="https://example.com/dmn/risk">
<itemDefinition id="tRiskClass" name="tRiskClass">
<typeRef>string</typeRef>
<allowedValues id="riskClassValues"><text>"low", "medium", "high"</text></allowedValues>
</itemDefinition>
<decision id="riskClass" name="Risk class">
<variable id="riskClassVariable" name="Risk class" typeRef="tRiskClass" />
<literalExpression id="riskClassExpression"><text>if Score < 40 then "low" else "unmapped"</text></literalExpression>
</decision>
</definitions>`;
const { rootElement } = await new DmnModdle().fromXML(source);
const definition = new Definition(new Context(rootElement, new Environment({ settings: { validateResult: true } })));
console.log(await definition.evaluate('riskClass', { Score: 30 }));
// low
console.log(await definition.evaluate('riskClass', { Score: 95 }).catch((err) => err.message));
// <riskClass> value "unmapped" violates allowed values of tRiskClassDeclares how imported models load — the engine never touches the file system. resolveImport(importDef) receives the declared dmn:Import (name, namespace, locationURI, importType) and returns the imported model's parsed dmn-moddle definitions, or a promise thereof. Each import resolves once per definition; imports of imports resolve recursively.
A typeRef qualified by the import name (common.tLevel) then coerces and validates through the imported item definitions, and requirement hrefs qualified with an imported namespace (https://…/common-types#someDecision) resolve into the imported model — the imported element evaluates in its own model and binds under its qualified name (common.Some Decision). Referencing a declared import without this setting fails the evaluation; an unused import declaration needs no resolver.
import { DmnModdle } from 'dmn-moddle';
import { Context, Definition, Environment } from 'dmn-elements';
const typesSource = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" id="commonTypes" name="Common types" namespace="https://example.com/dmn/common-types">
<itemDefinition id="tLevel" name="tLevel">
<typeRef>string</typeRef>
<allowedValues id="levelValues"><text>"gold", "silver", "bronze"</text></allowedValues>
</itemDefinition>
</definitions>`;
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" id="membershipDefinitions" name="Membership" namespace="https://example.com/dmn/membership">
<import name="common" namespace="https://example.com/dmn/common-types" locationURI="common-types.dmn" importType="https://www.omg.org/spec/DMN/20191111/MODEL/" />
<inputData id="levelInput" name="Level">
<variable id="levelVariable" name="Level" typeRef="common.tLevel" />
</inputData>
<decision id="fee" name="Fee">
<variable id="feeVariable" name="Fee" typeRef="number" />
<informationRequirement id="feeRequiresLevel">
<requiredInput href="#levelInput" />
</informationRequirement>
<literalExpression id="feeExpression"><text>if Level = "gold" then 0 else 10</text></literalExpression>
</decision>
</definitions>`;
const environment = new Environment({
settings: {
async resolveImport(importDef) {
const { rootElement } = await new DmnModdle().fromXML(typesSource);
return rootElement;
},
},
});
const { rootElement } = await new DmnModdle().fromXML(source);
const definition = new Definition(new Context(rootElement, environment));
console.log(await definition.evaluate('fee', { Level: 'gold' }));
// 0
console.log(await definition.evaluate('fee', { Level: 'platinum' }).catch((err) => err.message));
// <levelInput> value "platinum" violates allowed values of tLevelserializeDefinitions(definitions) serializes parsed dmn-moddle definitions to lean JSON — moddle internals never serialize and diagram interchange is stripped, while vendor extension attributes ($attrs) are kept so extensions decorate revived trees like the source. The engine reads only plain data, so the revived JSON evaluates like the source tree. Parse once at build time, ship the JSON, and evaluate without dmn-moddle in the runtime bundle:
import { DmnModdle } from 'dmn-moddle';
import { Context, Definition, Environment, serializeDefinitions } from 'dmn-elements';
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" id="dinnerDefinitions" name="Dinner" namespace="https://example.com/dmn/dinner">
<inputData id="seasonInput" name="Season">
<variable id="seasonVariable" name="Season" typeRef="string" />
</inputData>
<decision id="dish" name="Dish">
<variable id="dishVariable" name="Dish" typeRef="string" />
<informationRequirement id="dishRequiresSeason">
<requiredInput href="#seasonInput" />
</informationRequirement>
<literalExpression id="dishExpression"><text>if Season = "Winter" then "Roast beef" else "Light salad"</text></literalExpression>
</decision>
</definitions>`;
// build time: parse and serialize, e.g. to a file next to the bundle
const { rootElement } = await new DmnModdle().fromXML(source);
const precompiled = serializeDefinitions(rootElement);
// runtime: revive — no dmn-moddle needed from here on
const definition = new Definition(new Context(JSON.parse(precompiled), new Environment()));
console.log(await definition.evaluate('dish', { Season: 'Winter' }));
// Roast beefThe serialization is one-way: a revived tree evaluates, but cannot be written back to DMN XML — that needs the moddle instances.