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
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.
🌐 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).
🏥 HL7 Message — src/codeTempaltes/Globals/HL7Message.js
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.
📄 Document — src/codeTempaltes/Document.js
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— 20fetchrecipes: 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 +tryCatchpattern.
There's nothing to build or install — these are source files you paste into Mirth Connect. Pick the integration style that matches the file.
Most files in Globals and DB are designed to be Code Templates:
- In the Mirth Administrator, open Channels → Code Templates (Edit Code Templates).
- Create a new code template, set its Type to Function, and paste in the contents of the file.
- Add the template to a Code Template Library and assign that library to the channels that need it.
- 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.
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.
For files under src/channels, use Channels → Import Channel in the Mirth Administrator and
select the .xml file.
A docker-compose.yml is included to spin up a throwaway Mirth instance:
docker compose up -dThis 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.
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.
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.
// 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.
// 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 250msstringUtils.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']);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());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)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 conditionhl7Utils.fixLineBreaks(rawHl7); // repair stray unescaped CR/LF inside field values
hl7Utils.fromXml(msg); // Mirth E4X XML -> encoded HL7 v2 stringFor full parse/read/write/validate/diff/ACK, use the
HL7Messagemodel — see docs/HL7Message.md.
// Pull text out of a PDF byte array (Mirth's bundled iText)
var text = pdfUtils.extractText(attachmentBytes);// 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']);// 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// 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.
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- 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()'sMap-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.).
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 reportHighlights (Rhino 1.7.13):
constand array destructuring work at every version, including the default v0 — soconst [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 destructuringfunction ([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.
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 modeTests 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.
- Expand usage examples for each template
- Document the
DBConnection/ChannelUtilsAPI surface (docs/DBConnection.md) - Document
HL7Messagerules 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), andstringUtils(java.util.Base64) - Add tests for the remaining templates:
channelUtils,mirthEventPoller,fetch.mirth,dateUtils,errorUtils,hl7Utils,pdfUtils,assert,utils,$retry/$sleep,required,StandaloneMirthBackup, andDB/*(~420 assertions; a few deep Java passthroughsit.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
Templateper-line transformers 0-based to matchgetLine/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
Pagemin-line padding off-by-one so pages reachminLinesinstead ofminLines - 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.
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!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Code is linted with the rules in .eslintrc (single quotes, 1TBS braces, no mixed tabs/spaces).
Distributed under the MIT License. See LICENSE for more information.
Michael Lee Hobbs — @MichaelLeeHobbs
Project Link: https://github.com/MichaelLeeHobbs/mmc