|

Mastering NestJS DMN: Hit Policies and Advanced Tricks (Part 2)

Welcome back! In Part 1, we introduced @oltionzefi/nestjs-dmn, a lightweight DMN 1.3 parser and evaluator for NestJS that doesn’t force you to spin up a JVM just to evaluate some rules.

Today, we’re diving into the more advanced features: configuration, async setup, and extending the library to bend it to your will.

Advanced Configuration

The module exposes a forRoot() method that takes an options object. This is where the magic happens if you want to be strict with your application.

DmnModule.forRoot({
  // Restrict accepted hit policies. 
  // If someone uploads a DMN with an unsupported policy, we throw a tantrum (an error) at parse time.
  supportedHitPolicies: ['FIRST', 'UNIQUE'],

  // How to decode output cell text:
  // 'json' (default) parses as JSON, falling back to raw text
  // 'raw' always returns the raw string
  outputCellParser: 'json',

  // How to derive the context key from a DMN input label.
  labelToVariableName: (label) => label.toLowerCase().replace(/\s+/g, '_'),
});

Need this to be dynamic? Use forRootAsync:

DmnModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    supportedHitPolicies: config.get('dmn.policies'),
  }),
});

Extending the Services

One of the core design philosophies of this package is extensibility. Every helper method on DmnParserService and DmnEvaluatorService is protected. This means you can subclass them and inject your own custom behavior without waiting for a pull request.

For example, let’s say you want to explicitly ban blank input cells in production because you’ve been burned by empty fallbacks before:

import { Injectable } from '@nestjs/common';
import { DmnParserService } from '@oltionzefi/nestjs-dmn';

@Injectable()
export class StrictDmnParserService extends DmnParserService {
  protected override parseInputCell(text: string | undefined): string {
    const value = super.parseInputCell(text);
    if (value === '-' && process.env.NODE_ENV === 'production') {
      throw new Error('Blank input cells are not allowed in production DMN files. We run a tight ship here!');
    }
    return value;
  }
}

Then, just provide it in your module:

@Module({
  imports: [DmnModule.forRoot()],
  providers: [{ provide: DmnParserService, useClass: StrictDmnParserService }],
  exports: [DmnParserService],
})
export class AppModule {}

Error Handling that Doesn’t Suck

Nobody likes swallowing errors. If something fails, all thrown errors include the original error as cause (the standard ES2022 Error.cause field). This means stack traces survive the wrapper, making debugging slightly less painful.

try {
  await parser.parseDmnXml(brokenXml);
} catch (err) {
  console.error(err.message, err.cause);
}

Wrapping Up

And there you have it! A professional, clean, and fully-typed way to evaluate DMN files in your NestJS applications.

It handles the logic, you handle the applause. Now go write some rules!