Troubleshooting
@cosyte/hl7 follows Postel's Law: the parser is liberal (tolerate and warn), the serializer is
conservative (always spec-clean). Most surprises come from that split. This page covers the
common ones, then the Known Limitations you should design around.
"I got warnings, not an error"
That's the default and it's intentional. A vendor quirk becomes a tolerance warning on
msg.warnings. The parse still succeeds. Each warning carries a stable code you can branch on:
import { parseHL7 } from "@cosyte/hl7";
// A ZZZ segment isn't a known HL7 segment: tolerated, surfaced as a warning.
const raw = "MSH|^~\\&|A|B|C|D|20260101||ADT^A01|1|P|2.5\rZZZ|1";
const msg = parseHL7(raw);
msg.warnings.some((w) => w.code === "UNKNOWN_SEGMENT"); // => true
The 20 warning codes are a public, versioned contract. They won't be renamed under you without a breaking change. See Core Concepts for what each one means.
"I want failures, not warnings"
Pass { strict: true }. Strict mode promotes every Tier-2 tolerance warning to a thrown
Hl7ParseError, so nothing quirky slips through silently:
import { parseHL7 } from "@cosyte/hl7";
const raw = "MSH|^~\\&|A|B|C|D|20260101||ADT^A01|1|P|2.5\rZZZ|1";
// The same input that warned above now throws under strict.
parseHL7(raw, { strict: true });
"It threw even though I didn't ask for strict"
Four fatal structural codes throw regardless of mode. They mean the input isn't recoverable as
HL7 at all: NO_MSH_SEGMENT, MSH_TOO_SHORT, INVALID_ENCODING_CHARACTERS, and EMPTY_INPUT.
import { parseHL7 } from "@cosyte/hl7";
// No MSH, no message: fatal even in the default lenient mode.
parseHL7("");
Catch Hl7ParseError and read its code to distinguish them. Everything else is a warning.
"Non-ASCII text comes out garbled"
Character set is declared in MSH-18 (HL7 Table 0211). The parser resolves it and decodes
accordingly; resolveCharset / canonicalCharset let you inspect how a given code is treated
(decoded vs. preserved) before you rely on it.
"A batch/file won't parse"
parseHL7 parses one message. For an FHS/BHS-led batch or file envelope, use splitBatch,
which demarcates the individual MSH-led messages, parses each independently (a malformed message is
isolated, never dropping its siblings), and reconciles the declared BTS-1/FTS-1 counts:
import { splitBatch } from "@cosyte/hl7";
const result = splitBatch(rawBatch);
for (const entry of result.messages) {
// each entry is an ok/failure record: a bad message never suppresses the rest
}
Known limitations
Design around these. They're deliberate scope choices, not bugs:
- Vendor profiles are structural and evidence-grounded. Built-ins ship for Epic, Cerner, Meditech, Athena, a generic lab, and the Visage 7 and Philips Vue PACS imaging systems. A vendor "quirk" is encoded only when a real document grounds it: a publicly published vendor interface spec (as with Visage 7 and Philips Vue PACS) or a real de-identified feed, never invented, so broader per-vendor coverage expands as grounded sources arrive rather than shipping speculative rules.
- No terminology validation, no network, no bundled codesets.
codingSystem()reports what a code claims (HL7 Table 0396). It does not validate a value against LOINC, SNOMED CT, RxNorm, or any external system, and nothing here makes a network call. - One ACK acknowledges one message. Batch-level ACK reconciliation is out of scope; for MLLP
transport framing and ACK correlation over the wire, use the sibling package
@cosyte/mllp. - Datetimes are fidelity values, not eager
Dates.TSfields preserve the raw string and aprecisionso timezone and precision are never silently lost; convert to aDateexplicitly when you need one. Hl7ParseError.snippetis the field to redact first. It carries up to 40 characters of raw input verbatim and the library never redacts it, so scrub it at your logging edge. Under{ strict: true }the escalated error carries asnippetof the first 40 characters of the input, so it remains the field to redact on that path too.- Warning and error messages are bounded, but not absolutely content-free. A token lifted from
the input is echoed only when it matches the form the spec defines for it: a three-character
segment identifier, an MSH-9 type, an MSH-12 version, or a charset label the closed Table 0211
actually contains. Anything else becomes
<withheld>. That is what stops an unescaped line break inside a narrative field, which HL7 v2 requires to be sent as\.br\and which real senders send raw anyway, from forging a "segment identifier" out of a line of clinical text. A message cannot carry a field's value, but it can carry a residue of up to three characters when a malformed line happens to look like a segment identifier, so prefer loggingw.codeandw.positionoverw.messageif your posture is strict. - Pre-alpha on the
0.0.xladder. The 20 warning codes are a stable contract, but the broader surface may still evolve before a 1.0. For the published version, ask the registry (npm view @cosyte/hl7 version) rather than a doc page.
Still stuck?
Open an issue at github.com/cosyte/hl7/issues with a synthetic (never real-PHI) message that reproduces it.