A local InterSystems IRIS for Health sandbox plus a one-hotkey round trip from Notepad++: edit a transform, hit the key, see the transformed message.
Built for learning IRIS interoperability coming from a Mirth/BridgeLink background.
The obvious reading of "a Notepad++ plugin that runs IRIS transforms" is a new plugin. It should not be. Two reasons:
- PipeHat already is the plugin. It owns HL7 loading, parsing, the message tree, and field-aware Compare Views. A second plugin would rebuild all of it to add one button.
- The hard part is not the editor, it is the execution engine. DTL only runs inside IRIS. Anything you build in Notepad++ is a client to an IRIS instance you still have to stand up. Stand up the instance first; the editor integration is then a four-line NppExec script.
So v1 is: Docker sandbox + a bun driver + NppExec. v2 folds the driver into PipeHat as a command so the result lands in the second view and Compare Views highlights every changed field automatically.
# Start Docker Desktop first, then:
cd C:\opencode\iris-lab
docker compose up -d # ~1 GB pull the first time, a few minutes
bun setup.ts # one-time: enable interoperability on USER
bun xform.ts # the round tripbun setup.ts must print ENSEMBLE-ENABLED=1 and HL7-CLASS=1. If it does not,
nothing downstream will compile, because EnsLib.HL7.* is only mapped into
interoperability-enabled namespaces.
Management Portal: http://localhost:52773/csp/sys/UtilHome.csp (_SYSTEM / SYS).
License: Community Edition is development-and-learning only. Fine for this. Not fine for anything client-facing.
$env:IRIS_MODE = "local"
$env:IRIS_INSTANCE = "<from `iris list`>"
$env:IRIS_NAMESPACE= "USER"
Get-Content lab\input.hl7 -Raw | bun xform.tsThree things differ from the container, all of them discovered the hard way:
There is no iris session on Windows. That spelling is UNIX-only; iris.exe
rejects session as an invalid parameter. The scriptable terminal is a separate
executable, irissession.exe, in the instance's own bin directory. xform.ts
finds it by reading the directory: line out of iris list. Override with
$env:IRIS_SESSION_EXE if it picks wrong.
A native install asks for a password. The container has no web application
and makes no auth decision, so this never comes up there. A piped session gets
Access Denied on the first line unless it supplies credentials:
$env:IRIS_USER = "<your iris username>"
$env:IRIS_PASSWORD = Read-Host "IRIS password"Read-Host keeps it out of your shell history. Credentials go over stdin, not
command-line arguments — arguments are visible to every other user on the box
through the process list, which on a shared or work machine is not theoretical.
Don't persist them into a script or a machine-level environment variable.
Windows PowerShell 5.1 will show a red NativeCommandError on success.
Not a failure. xform.ts follows the UNIX split — the transformed message goes
to stdout so it can be piped, and the OK Lab.Transform ... ms status goes to
stderr so it never contaminates the message. PowerShell 5.1 wraps any stderr
output from a native executable in an ErrorRecord and renders it red, regardless
of exit code.
Check the thing that actually matters:
$LASTEXITCODE # 0 means it workedTo silence the noise, at the cost of losing the timing line:
Get-Content lab\input.hl7 -Raw | bun xform.ts 2>$nullPowerShell 7 does not do this. Neither does cmd.
The namespace needs interoperability enabled. Check it once:
zn "USER"
write ##class(%EnsembleMgr).IsEnsembleNamespace($namespace)
write ##class(%Dictionary.CompiledClass).%ExistsId("EnsLib.HL7.Message")Both must print 1. If the first is 0:
do ##class(%EnsembleMgr).EnableNamespace($namespace,1) — but only on an
instance that is yours. It modifies the namespace, and change control on a
shared dev engine belongs to someone else.
| File | What it is |
|---|---|
lab/input.hl7 |
The sample message. Paste any HL7 v2 here. |
lab/Transform.cls |
The file you edit. Your DTL. Must stay named Lab.Transform. |
lab/output.hl7 |
The result. Overwritten every run. |
src/Lab.Runner.cls |
The harness. Recompiles your transform on every run. |
Errors land in lab/output.hl7 prefixed with ###, on purpose — you should see
the compile error in the editor, not go digging through container logs.
xform.ts is a UNIX filter and nothing more:
stdin <- raw HL7
stdout -> transformed HL7
stderr -> diagnostics (compile errors, timings)
exit 0 = success, non-zero = failure
Nothing in it knows about Notepad++. Run it from a shell, a test script, CI, or any editor that can spawn a process:
Get-Content lab\input.hl7 -Raw | bun xform.tsPipeHat's External Transform Provider speaks exactly that contract. Add this
to PipeHat.providers in the Notepad++ plugin config dir:
iris.command = bun.exe C:\opencode\iris-lab\xform.ts
iris.workdir = C:\opencode\iris-lab
iris.timeout = 20000
iris.desc = InterSystems IRIS DTL
Then, with a message open and a second view showing:
Ctrl+Alt+Shift+X— pick the providerCtrl+Alt+Shift+A— run it again
The result lands in the other view and PipeHat diffs it field by field automatically. PipeHat never learns the word "InterSystems"; it just runs a command. Any other engine you wrap the same way is another line in that file.
| Mirth / BridgeLink | IRIS | Note |
|---|---|---|
| Channel | Production | The container for everything |
| Source connector | Business Service | EnsLib.HL7.Service.TCPService for MLLP |
| Destination connector | Business Operation | EnsLib.HL7.Operation.TCPOperation |
| Filter + Router | Business Process, usually a Routing Rule | Rules are their own editor |
| Transformer step (JavaScript) | DTL | XML, drawn as a graphical mapper |
msg['PID']['PID.5']['PID.5.1'] |
source.{PID:5.1} |
Same idea, different punctuation |
channelMap / globalMap |
Production settings, Ens.Util.* |
No direct equivalent to channelMap |
| JavaScript escape hatch | <code> block, ObjectScript |
Same role, different language |
| Message Browser | Message Viewer / Visual Trace | Visual Trace is genuinely better |
| Channel deploy | Production start/update |
The one that bites: MSH field numbering. In EnsLib.HL7, MSH:1 is the field
separator, so MSH:9 is the message type and MSH:10 the control ID — the schema
numbering, not the raw-pipe offset. PipeHat already honors this, so your instincts
transfer.
- Break it on purpose. Change
{PID:5.1}to{PID:99.1}and run. Read the error. Do it again with a bad XML tag. Learn what each failure looks like now, while the loop is two seconds long. - Work the DTL vocabulary —
<assign>,<if>,<foreach>,<subtransform>,<code>. The sample uses the first four. - Open the same class in the Management Portal (Interoperability > Build > Data Transformations). Same file, graphical view. Edit it there, watch the XML change here. That connection is the thing that makes DTL click.
- Then leave the sandbox and build an actual production: TCP service, routing rule, TCP operation. Point PipeHat's MLLP sender at it — you already have the test harness.
- ObjectScript proper comes after DTL, not before. You will have absorbed
half of it from
<code>blocks by then.
Reference: Developing DTL Transformations · DTL for HL7 · EnsLib.HL7.Message class ref
Scaffolded, not yet run — the Docker daemon was stopped when this was built.
The ObjectScript in src/Lab.Runner.cls is written against the documented APIs
but has not been compiled. Expect one or two fixups on first run, most likely in
OutputToLibraryStream or ResolveSchemaTypeToDocType argument order. Debug with:
docker exec -it iris-lab iris session IRISthen zn "USER" and call the pieces by hand.
No PHI in lab/. Ever.