Skip to content

MichaelLeeHobbs/mmc

Repository files navigation

Contributors Forks Stargazers Issues MIT License


Mike's Mirth Code

Battle-tested JavaScript code templates, helpers, and channels for Mirth Connect (NextGen Connect).
Drop-in utilities pulled straight from production HL7/healthcare integrations.

Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. What's Inside
  3. Getting Started
  4. The Mirth $ Helpers
  5. Usage Examples
  6. Compatibility Notes
  7. Rhino Language Support
  8. Testing
  9. Roadmap
  10. Contributing
  11. License
  12. Contact
  13. Acknowledgments

About The Project

Mirth Connect is a healthcare integration engine whose channels are scripted in JavaScript, executed on the JVM by the Mozilla Rhino engine. That gives you a familiar JavaScript surface with full access to the underlying Java standard library and Mirth's own Java APIs — a powerful combination, but one with sharp edges (limited/older ECMAScript support, no npm, and a lot of Packages.* boilerplate).

Mike's Mirth Code (mmc) is a curated set of reusable building blocks that smooth over those edges: a fetch() polyfill, a database-connection base class, persistent maps, an HL7 message parser, batch handlers, time-zone conversion, and a grab-bag of small utilities — all written to be pasted into Mirth as Code Templates or Global Scripts. Everything here has run in real production deployments.

Most files are self-contained and authored so that their leading JSDoc block doubles as the Mirth code-template description. Read the source — each function documents its parameters, return value, and usually an @example.

(back to top)

What's Inside

🌐 Globals — src/codeTempaltes/Globals

General-purpose helpers meant to live in your Code Template Library (or, where noted, a Global Script).

Related functions are grouped into consolidated namespace files (e.g. everything string-related lives in stringUtils). Each function keeps its own JSDoc, so the per-function descriptions still show up in Mirth. A small utils bridge exposes them all under one object (utils.string, utils.date, …).

Template What it does
fetch.mirth.js A partial fetch() implementation over Apache HttpClient. Supports GET/HEAD/POST/PUT/PATCH/DELETE/OPTIONS, headers, UTF-8 bodies, redirects, connect/socket/connection-request timeouts (timeout), HTTP proxy (proxy), self-signed certs (ignoreSSLError), mutual TLS (clientCert), and .json() / .text() / .xml() / .byteArray() body readers.
stringUtils.js Text helpers: atob/btoa (Base-64 via java.util.Base64), word-wrap for length-limited fields (wrapText, wrapArray, limitElementLength), line-break encoding, and report parsing (splitReportText, splitFindingsAndImpression, filterLinesContaining).
arrayUtils.js Array helpers: fromArrayList (Java List/ArrayList → JS array) and toObject.
dateUtils.js Date/time helpers: HL7 ⇄ ISO ⇄ Date conversions, convertTimeZone (IANA zones via java.time), getAge, isOlderThan, and the cross-scope dateUtils.Timer.
jsonUtils.js JSON helpers: circular-safe stringify/stringifyCircular for Rhino, fromXml (Mirth E4X XML → JSON with optional per-node callback), and denormalizeSQL.
errorUtils.js Error helpers: toString (message + stack across JS/Java error types) and variadic combine (merges multiple errors into one).
validationUtils.js Validation helpers: parseInt with default/min/max bounds.
channelUtils.js Channel infrastructure: batchJson/batchText batch processors, getSourceMsg, mapMessageRoute, routeJsonMsg, the rule-driven responseHandler, and required (deploy-time dependency check).
hl7Utils.js HL7 v2 helpers: fixLineBreaks (repairs stray unescaped line breaks) and fromXml (Mirth XML → encoded HL7 v2).
pdfUtils.js extractText — pulls text out of a PDF byte array using Mirth's bundled iText (2.1.7).
mirthEventPoller.js Generic poller for Mirth's internal event log: poll({eventName, onEvent, …}) matches events by name, de-dupes by event id in $gc, and hands each new event to a pluggable onEvent handler as a parsed object (id, username, channelName, messageIds, attributes, …). Plus debugDumpEvents for discovery.
utils.js Bridge object exposing all the namespaces above as utils.string, utils.date, utils.json, utils.array, utils.error, utils.validation, and utils.channel.
$t.js Inline try/catch — $t(() => a.b.c) stands in for optional chaining (a?.b?.c), which Rhino lacks. Swallows the error and returns undefined.
tryCatch.js Synchronous Go-style result tuple — tryCatch(fn) returns [data, null] or [null, error], surfacing the error instead of swallowing it (the explicit counterpart to $t).
$retry.js Retries a callback with configurable attempts and backoff; optionally rethrows the last error.
$sleep.js Blocks the thread for N milliseconds via java.lang.Thread.sleep.
assert.js Throws if a condition is falsy; also assert.ok and assert.array for batches of [condition, message] pairs.
required.js Deprecated thin wrapper that delegates to channelUtils.required — fails fast at deploy time if expected functions/libraries aren't on the classpath.

🗄️ Database — src/codeTempaltes/DB

Template What it does
DBConnection.js Reusable base class wrapping Mirth's DatabaseConnectionFactory, with optional cross-channel connection caching. Extend it for your own DB helpers.
ChannelUtils.js DBConnection subclass with conveniences for querying Mirth's own database.
PersistentMap.js A DB-backed map that persists across restarts and is reachable from any channel, with per-entry expiration.
PersistentChannelMap.js Like PersistentMap, but scoped/keyed per channel.

📖 Full API + examples: docs/DBConnection.md — config, connection caching, statement execution, the Mirth-DB helpers on ChannelUtils, the persistent-map CRUD surface, error handling, and gotchas (every example verified against the live code).

A self-contained ES5 HL7 v2 message model in a single file: parse a message; read and modify segments, fields, components, subcomponents, and repetitions by path; validate against rules; diff two messages; build ACKs; and re-encode. The Encoding helper class (HL7 encoding characters, MSH-1/MSH-2) is bundled in the same file. Requires Mirth's rhinoLanguageVersion ≥ 180 (it uses let + object destructuring). Covered by tests in test/hl7message.test.js and test/encoding.test.js.

📖 Full API + examples: docs/HL7Message.md — path syntax, reading, writing, deleting, searching, validation, diffing, ACKs, encoding, and gotchas (every example verified against the live code).

💾 Standalone Mirth Backup — src/codeTempaltes/StandaloneMirthBackup

A channel-driven backup system that exports the full Mirth server configuration on an hourly/daily/weekly/monthly rotation via File Writer destinations. It's now a single mirthBackup.js containing both getMirthConfig (serializes the running server config to XML) and mirthBackup (drives the rotation). See the file's header for the full setup instructions.

A paginated text-document builder in a single file, four classes in dependency order: AdvanceString (word-wrap, centering, templating, character remapping), Template (header/footer line templates with per-line transformers), Page (header + word-wrapped body + footer with min/max line limits), and Document (splits body text across as many pages as needed, numbering them). Document.prototype.toHL7 can emit the rendered text as HL7 OBX segments.

📡 Channels — src/channels

Importable channel XML, including StandaloneMirthBackup.xml.

📚 Examples — src/Examples

  • customBatchProcessor.js — a custom CSV batch processor that treats the first row as headers and emits each subsequent row as a JSON object.
  • fetch.examples.js — 20 fetch recipes: GET JSON/XML/text, binary download → attachment, POST JSON/form, Bearer/Basic auth, query params, PUT/PATCH/DELETE, error-status vs network-error handling, reading response headers, self-signed certs, mutual TLS, disabling redirects, connect/socket timeouts, HTTP proxy, and a retry + tryCatch pattern.

(back to top)

Getting Started

There's nothing to build or install — these are source files you paste into Mirth Connect. Pick the integration style that matches the file.

Using a Code Template

Most files in Globals and DB are designed to be Code Templates:

  1. In the Mirth Administrator, open Channels → Code Templates (Edit Code Templates).
  2. Create a new code template, set its Type to Function, and paste in the contents of the file.
  3. Add the template to a Code Template Library and assign that library to the channels that need it.
  4. Call the function from any connector/transformer/filter script on those channels.

💡 The leading JSDoc comment in each file is written to double as the template's description field.

Using a Global Script

A few templates are meant for Global Scripts rather than the template library — for example channelUtils.mapMessageRoute (Preprocessor) and the StandaloneMirthBackup setup. Each file's header documents where it belongs.

Importing a Channel

For files under src/channels, use Channels → Import Channel in the Mirth Administrator and select the .xml file.

A Local Mirth for Testing

A docker-compose.yml is included to spin up a throwaway Mirth instance:

docker compose up -d

This starts NextGen Connect 4.5.2 with the Administrator/API exposed on https://localhost:10452. Adjust the image tag in docker-compose.yml to test against a different Mirth version.

(back to top)

The Mirth $ Helpers

Several templates use Mirth's built-in User API shorthand map accessors. They're not defined in this repo — Mirth provides them at runtime. For reference:

Helper Map
$c(key[, val]) Channel Map
$co(key[, val]) Connector Map
$s(key[, val]) Source Map
$gc(key[, val]) Global Channel Map
$g(key[, val]) Global Map
$cfg(key[, val]) Configuration Map
$r(key[, val]) Response Map

The full set of expected runtime globals (msg, connectorMessage, channelId, XML, etc.) is listed in .eslintrc, which lints this code as if those globals exist.

(back to top)

Usage Examples

A representative, copy-pasteable snippet for each template family. Every example is verified against the live code through the test suite; the dedicated API docs (HL7Message, DBConnection/ChannelUtils) go deeper.

HTTP — fetch()

// Simple GET
var data = fetch('https://example.com/api/patients').json();

// POST JSON
var res = fetch('https://example.com/api', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({mrn: '12345'})
}).json();

// Mutual TLS (client certificate)
var secure = fetch('https://secure.example.com/api', {
  clientCert: {path: '/opt/mirth/certs/client.p12', password: 'changeit'}
}).json();

// Timeouts (ms): a number bounds all phases; an object tunes each one
fetch('https://slow.example.com/api', {timeout: 5000});
fetch('https://slow.example.com/api', {
  timeout: {connect: 2000, socket: 30000, connectionRequest: 1000}
});

More recipes (auth, query params, binary download, headers, redirects, timeouts, retry) in src/Examples/fetch.examples.js.

Resilience — $retry, tryCatch, $t, $sleep

// Retry a flaky call with backoff
var result = $retry(function () {
  return fetch('https://example.com/sometimes-down').json();
}, {retries: 3, backoff: 2000, throwOnFail: true});

// Go-style result tuple — forces you to handle the failure path up front.
// (Array destructuring works in Mirth's Rhino at every language version.)
var pair = tryCatch(function () { return fetch('https://example.com/api').json(); });
var data = pair[0], error = pair[1];
if (error) { logger.error('fetch failed: ' + error); return; }

// $t — inline optional-chaining stand-in (Rhino has no `?.`). Returns undefined on throw.
var city = $t(function () { return order.patient.address.city; });

$sleep(250); // block this thread for 250ms

Strings & reports — stringUtils

stringUtils.wrapText('the quick brown fox', 10);   // ['the quick', 'brown fox']
stringUtils.btoa('Hello');                          // 'SGVsbG8='  (and atob() to decode)

// Split a radiology-style report into impression + findings
var parts = stringUtils.splitFindingsAndImpression(reportText);
var impression = parts[0], findings = parts[1];

// Strip lines containing any sensitive token
stringUtils.filterLinesContaining(reportText, ['SSN', 'MRN']);

Dates — dateUtils

dateUtils.hl7ToIso('20260206143045-0800');   // '2026-02-06T22:30:45.000Z'
dateUtils.isoToHl7('2026-02-06T22:30:45.000Z'); // '20260206223045+0000'
dateUtils.getAge('19850315');                 // age in years from a YYYYMMDD DOB

// Convert between IANA zones (returns HL7 datetime + offset). 3-arg = from -> to.
dateUtils.convertTimeZone('20260206143045', 'America/New_York', 'America/Los_Angeles');

// A per-message stopwatch backed by the channel map ($c)
var timer = new dateUtils.Timer();      // logs a 'start' event
timer.markTime('after-db-lookup');      // append an event with a ms diff
logger.info(timer.toString());

Arrays, JSON & validation

arrayUtils.fromArrayList(someJavaList);       // Java List/ArrayList -> JS array
arrayUtils.toObject(['a', 'b', 'c']);         // { 1: 'a', 2: 'b', 3: 'c' }  (1-based)

jsonUtils.stringify(objWithCycles);           // circular-safe (marks '[Circular Reference]')
jsonUtils.stringifyCircular(obj, 2);          // pretty, drops cycles instead of throwing

validationUtils.parseInt('100', {min: 5, max: 10}); // 10  (clamped)
validationUtils.parseInt('abc', {default: 99});      // 99  (NaN -> default)

Errors & assertions — errorUtils, assert

logger.error(errorUtils.toString(e));         // message + stack, across JS *and* Java errors
var merged = errorUtils.combine(err1, err2);  // one Error: 'Error 1: ...\nError 2: ...'

assert(msg['PID']['PID.3']['PID.3.1'].toString(), 'MRN is required');
assert.array([
  [patientId, 'patient id missing'],
  [function () { return order.total > 0; }, 'order total must be positive']
]); // throws a combined message listing every failed condition

HL7 v2 — hl7Utils

hl7Utils.fixLineBreaks(rawHl7);   // repair stray unescaped CR/LF inside field values
hl7Utils.fromXml(msg);            // Mirth E4X XML -> encoded HL7 v2 string

For full parse/read/write/validate/diff/ACK, use the HL7Message model — see docs/HL7Message.md.

PDF — pdfUtils

// Pull text out of a PDF byte array (Mirth's bundled iText)
var text = pdfUtils.extractText(attachmentBytes);

Channel infrastructure — channelUtils

// Batch Adapter (JavaScript) — emit one message per call from a JSON array payload
return channelUtils.batchJson();   // batchText() for line-delimited text

// Preprocessor / Global Script — build a $c('route') string from source channel/message ids
channelUtils.mapMessageRoute();

// Response transformer — rule-driven requeue/abort by matching the error message
channelUtils.responseHandler([
  {key: 'connection refused', responseStatus: 'QUEUED', maxAttempts: 5},
  {key: 'duplicate key',      responseStatus: 'SENT',   maxAttempts: 0}
]);

// Deploy/Preprocessor — fail fast if a dependency isn't on the classpath
required(['$t', '$retry', 'assert', 'fetch']);

Paginated documents — Document

// Splits body text across as many pages as needed; #pageNumber#/#totalPages# fill in per page.
var doc = new Document({
  text: reportBody,
  maxLines: 60, minLines: 60, maxLineLength: 78,
  header: new Template(['REPORT — page #pageNumber# of #totalPages#']),
  footer: new Template(['--- end of page #pageNumber# ---'])
});
var rendered = doc.toString();   // or doc.toHL7(...) to emit OBX segments

Databases & persistent maps

// Deploy Script — create the table once
var pm = new PersistentMap(JSON.parse($cfg('john_doe_memorial_persistent_map')));
pm.initialize();

// Anywhere later (any channel) — survives restarts, expires per entry
var $p = new PersistentMap(JSON.parse($cfg('john_doe_memorial_persistent_map')));
$p.set('lastSeenMRN', '12345');
var entry = $p.get('lastSeenMRN');   // { key: 'lastSeenMRN', value: '12345' }

Full DB API — DBConnection, ChannelUtils, PersistentMap, PersistentChannelMap — in docs/DBConnection.md.

The utils bridge

Every namespace is also reachable through one object, handy when you only import utils:

utils.string.wrapText(text, 80);
utils.date.getAge('19850315');
utils.json.stringify(obj);
// also: utils.array, utils.error, utils.validation, utils.channel

(back to top)

Compatibility Notes

  • Runtime: these scripts run on Rhino inside Mirth/NextGen Connect, not Node.js or a browser. Expect limited modern-ECMAScript support; some templates are deliberately written in ES5.
  • Version drift: Mirth's bundled libraries and APIs change between releases. Notable callouts in the source: fetch()'s Map-based headers need Mirth 4.0 (and maybe 3.12); the Standalone Backup works on 3.11 but not 3.7. When in doubt, test against your target version (see docker-compose).
  • Java interop: templates lean on Packages.* / java.* classes available on the Mirth classpath (Apache HttpClient, iText 2.1.7, java.time, java.util.Base64, etc.).

(back to top)

Rhino Language Support

Mirth 4.5.2 runs scripts on Rhino 1.7.13, and the JavaScript syntax you can use depends on the server setting rhinoLanguageVersion (which defaults to VERSION_DEFAULT = 0). To pin down exactly what works, this repo ships an empirical, regenerable report:

➡️ docs/RHINO-COMPATIBILITY.md — a feature × language-version matrix (v0 / v180 / v200) generated by running probes through Mirth's actual Rhino jar.

pnpm run rhino:compat   # extracts the jar from the Mirth container and regenerates the report

Highlights (Rhino 1.7.13):

  • const and array destructuring work at every version, including the default v0 — so const [data, error] = tryCatch(...) is safe.
  • let, object destructuring, and object param destructuring (function ({ a, b })) require v180+ — they fail on a default Mirth. (Array param destructuring function ([a, b]) works even at v0.)
  • No ES6 class, Promise, default/rest/spread params, or template-literal interpolation at any version — use string concatenation, not `${x}`.
  • for (let i…) captures the final value (no per-iteration binding); for-of / Map / Set / generators need v200 (ES6).

See the report for the full matrix and the Gotchas section.

(back to top)

Testing

Unit tests run under Vitest in Node — even though the templates target Rhino. The trick is a small harness (test/mirthHarness.js) that loads a template's source into a Node vm sandbox pre-seeded with mocked Mirth globals ($c/$gc/… as Map-backed functions, logger, and injectable java/Packages/XML), then reads back the declared globals and module.exports. The templates are tested as-is, with no changes to the source.

pnpm install
pnpm test            # run once
pnpm run test:watch  # watch mode

Tests cover ~all of the Globals plus Document, HL7Message, Encoding, and DB/* (~420 assertions). The Java/Mirth-coupled templates are exercised by mocking the relevant surface via the harness: fetch.mirth (a fake Apache HttpClient chain), channelUtils / mirthEventPoller (Mirth controllers + maps), pdfUtils (iText), dateUtils (java.time), errorUtils (java.io), and DB/* (a fake DatabaseConnectionFactory). A few deep Java passthroughs (e.g. convertTimeZone's real timezone engine, channelUtils.getSourceMsg) are it.skip'd with reasons rather than mocked into meaninglessness.

Note: because the harness loads template source through vm, V8 line-coverage reports 0% — the eval'd code isn't part of the instrumented module graph. The passing assertions are the signal, not the coverage number.

(back to top)

Roadmap

  • Expand usage examples for each template
  • Document the DBConnection / ChannelUtils API surface (docs/DBConnection.md)
  • Document HL7Message rules and validation
  • Vitest harness + tests for the pure, require()-able helpers
  • Vitest harness can mock the Java/Mirth surface — proven on HL7Message (java), Document (createSegment), and stringUtils (java.util.Base64)
  • Add tests for the remaining templates: channelUtils, mirthEventPoller, fetch.mirth, dateUtils, errorUtils, hl7Utils, pdfUtils, assert, utils, $retry/$sleep, required, StandaloneMirthBackup, and DB/* (~420 assertions; a few deep Java passthroughs it.skip'd with reasons)
  • Fix mutation issues — HL7Message.get(path, false) (and similar) return a live reference into the message, so mutating the result mutates the message. Return clones instead. (Deferred: touches several call sites.)
  • (breaking) Make Template per-line transformers 0-based to match getLine/setLine — currently 1-based (_transformers[i + 1]). Consumers that key transformers 1-based (e.g. the prod radiology report header) must be updated in lockstep.
  • (breaking) Fix Page min-line padding off-by-one so pages reach minLines instead of minLines - 1 — changes rendered page line counts (e.g. prod report pages would go 48 → 49 lines/page).

See the open issues for the full list of proposed features and known issues.

(back to top)

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag enhancement. Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Code is linted with the rules in .eslintrc (single quotes, 1TBS braces, no mixed tabs/spaces).

(back to top)

License

Distributed under the MIT License. See LICENSE for more information.

(back to top)

Contact

Michael Lee Hobbs — @MichaelLeeHobbs

Project Link: https://github.com/MichaelLeeHobbs/mmc

(back to top)

Acknowledgments

(back to top)

About

Mike's Mirth Code

Resources

License

Stars

17 stars

Watchers

4 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors