@cosyte/hl7
Namespaces
Classes
Field
Wrapper over a RawField exposing HL7 null/empty discrimination
(isNull), the repetitions tree, and a decoded value getter.
seg.field(3) === seg.field(3): Field instances are referentially stable
per segment position (D-12).
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7(raw);
const pid5 = msg.segments("PID")[0]?.field(5);
console.log(pid5?.value); // "Smith": decoded at parse
console.log(pid5?.isNull); // false: HL7 explicit null is "", absent is not
console.log(pid5?.repetitions.length); // 1
Constructors
Constructor
new Field(
raw,enc,position):Field
Internal
Construct a new Field. Called internally by Segment.field(n); user
code should obtain Field instances via msg.segments(type)[i].field(n).
Parameters
raw
enc
position
Returns
Properties
enc
readonlyenc:EncodingCharacters
Internal
The 5 encoding characters for this message. Exposed for composite parsers.
isNull
readonlyisNull:boolean
HL7 null indicator: true iff the underlying field was the two-char literal "".
position
readonlyposition:Hl7Position
Internal
Position of this field in the parent message: used for position-aware error messages.
raw
readonlyraw:RawField
Internal
The full RawField this wrapper wraps. Exposed for composite parsers.
repetitions
readonlyrepetitions: readonlyRawRepetition[]
Reference to the underlying RawField.repetitions (no defensive copy).
Accessors
text
Get Signature
get text():
string
The field's canonical wire text: the full field re-serialized with the active delimiters and re-escaped content (repetitions, components, and subcomponents included). Contrast with value, which returns only the first subcomponent of the first component of the first repetition (decoded once at parse: never re-unescaped).
Use this when a field must be compared or echoed as a whole: e.g.
correlating an ACK's MSA-2 against the inbound MSH-10, where a
vendor-quirk control id containing an unescaped delimiter (ID^X) must
not be truncated to its first component.
Byte-verbatim for parsed content. The parse pipeline stores
decoded content, but also records the original wire bytes of any escape
whose decode is not byte-faithful (RawComponent.rawSubcomponents), so
re-serialization preserves the sender's exact escape bytes: hex escapes
stay hex (A\X41\B, casing intact), and recognize-and-preserve sequences
(\H\, \N, formatting/charset/vendor escapes) re-emit verbatim
rather than as escaped literal text. Delimiter/newline escapes and plain
content round-trip byte-exact via the re-escape path. The only remaining
canonicalization is structural: trailing insignificant empties are
stripped (D-02), and a field built by hand (not parsed) re-escapes its
decoded value since it has no overlay.
MSH-1/MSH-2 caveat. Like value, calling this on MSH-1 or
MSH-2 (the delimiter-definition fields) re-escapes the encoding
characters themselves and produces garbage: those two fields are only
meaningful through Hl7Message.encodingCharacters.
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7("MSH|^~\\&|A|B|C|D|20260101||ADT^A01|ID^X|P|2.5\r");
msg.segments("MSH")[0]?.field(10).value; // "ID" (first component only)
msg.segments("MSH")[0]?.field(10).text; // "ID^X" (verbatim wire text)
Returns
string
value
Get Signature
get value():
string
First-repetition, first-component, first-subcomponent value as a decoded
string: unescaped ONCE by the tokenizer on parse, returned verbatim here
(never re-unescaped). Returns "" when the field is
empty or HL7 null. Equivalent to msg.get('SEG.N') for a top-level access.
Example
const pid5 = msg.segments("PID")[0]?.field(5);
// wire "Smith\F\Jr" was decoded at parse → the field separator escape:
console.log(pid5?.value); // "Smith|Jr"
Returns
string
Methods
asCe()
asCe():
CE
Coerce this field's first repetition to a typed CE (Coded Element).
Returns
Example
const code = msg.segments("OBX")[0]?.field(3)?.asCe();
console.log(code?.identifier, code?.text);
asCwe()
asCwe():
CWE
Coerce this field's first repetition to a typed CWE (Coded With
Exceptions).
Returns
Example
const code = msg.segments("OBX")[0]?.field(3)?.asCwe();
console.log(code?.identifier, code?.text);
asCx()
asCx():
CX
Coerce this field's first repetition to a typed CX (Extended Composite
ID). assigningAuthority is a nested HD.
Returns
Example
const mrn = msg.segments("PID")[0]?.field(3)?.asCx();
console.log(mrn?.idNumber, mrn?.assigningAuthority?.namespaceId);
asHd()
asHd():
HD
Coerce this field's first repetition to a typed HD (Hierarchic
Designator).
Returns
Example
const sending = msg.segments("MSH")[0]?.field(3)?.asHd();
console.log(sending?.namespaceId);
asNm()
asNm():
NM
Coerce this field's first repetition to a typed NM (Numeric).
{ raw, value }: value is undefined on non-numeric input.
Returns
Example
const nm = msg.segments("OBX")[0]?.field(5)?.asNm();
console.log(nm?.value);
asPl()
asPl():
PL
Coerce this field's first repetition to a typed PL (Person Location).
facility is a nested HD.
Returns
Example
const loc = msg.segments("PV1")[0]?.field(3)?.asPl();
console.log(loc?.pointOfCare, loc?.room, loc?.facility?.namespaceId);
asSn()
asSn():
SN|undefined
Coerce this field's first repetition to a typed SN (Structured Numeric),
or undefined when the field carries no usable structured-numeric content.
Use for an OBX-5 whose OBX-2 value type is SN (a comparator like >90,
a range like 100-200, or a ratio like 1:128). num1/num2 are
number | undefined (never NaN); the comparator is surfaced only when
SN.1 is a recognized operator.
Returns
SN | undefined
Example
const sn = msg.segments("OBX")[0]?.field(5)?.asSn();
console.log(sn?.comparator, sn?.num1); // ">" 90
asTs()
asTs():
DtmParts
Coerce this field's first repetition to a typed TS (Time Stamp): the
fidelity DtmParts (raw + parts + precision + timezone). valid is
false on unparseable input (no throw). Build an absolute instant only on
explicit request via dtmToDate(ts).
Returns
Example
const ts = msg.segments("MSH")[0]?.field(7)?.asTs();
console.log(ts?.raw, ts?.precision, ts?.hasTimezone);
asXad()
asXad():
XAD
Coerce this field's first repetition to a typed XAD (Extended Address).
Returns
Example
const addr = msg.segments("PID")[0]?.field(11)?.asXad();
console.log(addr?.street, addr?.city, addr?.stateOrProvince);
asXcn()
asXcn():
XCN
Coerce this field's first repetition to a typed XCN (Extended Composite
ID Number and Name for Persons). assigningAuthority is a nested HD.
Common on OBR-16 (ordering provider), PV1-7 (attending doctor), PV1-8
(referring doctor). Empty field → {} (never throws).
Returns
Example
const orderedBy = msg.segments("OBR")[0]?.field(16)?.asXcn();
console.log(orderedBy?.idNumber, orderedBy?.familyName, orderedBy?.identifierTypeCode);
asXpn()
asXpn():
XPN
Coerce this field's first repetition to a typed XPN (Extended Person
Name). Absent components are OMITTED from the result
(exactOptionalPropertyTypes). Not memoized in v1: each call re-parses
(D-09).
Returns
Example
const pid5 = msg.segments("PID")[0]?.field(5);
const name = pid5?.asXpn();
console.log(name?.familyName, name?.givenName);
asXtn()
asXtn():
XTN
Coerce this field's first repetition to a typed XTN (Extended
Telecommunication Number).
Returns
Example
const phone = msg.segments("PID")[0]?.field(13)?.asXtn();
console.log(phone?.telephoneNumber);
render()
render(
opts?):RenderedText
Render this field's formatted text (HL7 v2 §2.7 highlight + formatting
escapes) into a normalized RenderedText display model: plain text
plus highlight-aware runs. A read projection over the field's
byte-verbatim wire text: it never mutates the raw value, and it
never fabricates (an unrenderable escape is preserved + flagged). Use this
to surface a clinical narrative (an NTE / OBX-5 note) to a human without
the raw \.br\ / \H sentinels.
Parameters
opts?
see RenderTextOptions (e.g. a custom line-break string).
Returns
the normalized display model.
Example
const note = msg.segments("OBX")[0]?.field(5);
note?.render().text; // "Specimen received.\nGross exam normal."
empty()
staticempty(_enc):Field
Internal
Return a synthetic empty Field sentinel: used by Segment.field(n) to
honor MODEL-05's "never throws on missing" contract. The returned Field
has isNull === false, repetitions === [], and value === "".
Referentially stable across calls (same instance returned each time).
The enc argument is accepted for API symmetry but ignored: the
synthetic field carries no content, so unescape would be a no-op
regardless of the active encoding characters.
Parameters
_enc
Returns
Example
const empty = Field.empty(msg.encodingCharacters);
console.log(empty.value); // ""
Hl7Message
Parsed HL7 v2 message. Produced by parseHL7. Exposes the raw positional
tree (rawSegments), delimiter metadata, warnings, and a typed traversal
surface: get(path) for dot-paths, getAll(type) / segments(type) /
allSegments() for wrapper-level iteration.
Remarks
The warnings array is frozen at the model boundary so downstream
traversal and helpers cannot mutate parser output. The profile
field is populated when a profile is passed, and is undefined
otherwise. Segment/Field wrappers are cached per-message and
invalidated wholesale by the mutation methods.
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7(raw);
console.log(msg.get("PID.5.1")); // "Smith"
for (const obx of msg.segments("OBX")) {
console.log(obx.field(5).value);
}
for (const w of msg.warnings) console.warn(w.code);
Constructors
Constructor
new Hl7Message(
init):Hl7Message
Internal
Construct a new Hl7Message. The constructor takes a plain init
object and freezes the warnings array so callers cannot mutate parser
output after handoff.
Parameters
init
Hl7MessageInit
Returns
Properties
dateFormats
readonlydateFormats: readonlystring[]
Merged dateFormats list: options.dateFormats ++ profile.dateFormats
deduped first-occurrence per D-21. Empty array when neither source
supplied any formats. Exposed publicly so helpers (msg.meta.timestamp)
and advanced callers can introspect the active cascade.
encodingCharacters
readonlyencodingCharacters:EncodingCharacters
profile
readonlyprofile: {lineage: readonlystring[];name:string; } |undefined
rawSegments
readonlyrawSegments: readonlyRawSegment[]
Raw positional tree produced by the parser. 1-indexed per HL7 convention
(fields[0] is the segment-name / MSH separator placeholder slot). Use
segments(type) / allSegments() for typed wrapper access: this field
is exposed for advanced callers that need the raw tree directly.
version
readonlyversion:string
HL7 version the model asserts for this message, from MSH-12.1.1, e.g.
"2.5" or "2.5.1".
Bounded, and it is "<withheld>" when MSH-12 does not hold a version.
Same reasoning as Segment.type: it presents as an identifier and
consumers label with it. meta.version keeps MSH-12 exactly as it
arrived, and is what version-scoped field selection reads, so this bound
changes no parsing behaviour.
warnings
readonlywarnings: readonlyHl7ParseWarning[]
Accessors
meta
Get Signature
get meta():
Meta
MSH-derived message metadata (type, controlId, timestamp, version, etc.).
D-01: plain object. D-02: memoized: msg.meta === msg.meta across
reads until mutation invalidates. D-03: always defined (MSH absence
throws NO_MSH_SEGMENT at parse time).
Example
console.log(msg.meta.type); // "ADT^A01"
console.log(msg.meta.timestamp?.raw); // fidelity TS
console.log(msg.meta.controlId); // "MSG001"
Returns
patient
Get Signature
get patient():
Patient|undefined
PID-derived patient view, or undefined when no PID segment exists
(D-04). D-02: memoized. HELPERS-07: never throws: absent fields
surface as undefined on the returned Patient object.
Example
console.log(msg.patient?.mrn);
console.log(msg.patient?.fullName);
console.log(msg.patient?.dateOfBirth?.raw); // fidelity TS: e.g. "19800115"
Returns
Patient | undefined
structure
Get Signature
get structure():
MessageStructure
Structural-conformance summary for the common message types: a
misroute/truncation safety net, NOT a conformance validator.
Reports, per the message's recognized (MSH-9.1, MSH-9.2) type, which
Required segment groups are present and which are entirely absent
(missingGroups: the same set the parser flags as
MISSING_EXPECTED_GROUP warnings). For an unmodelled type, recognized
is false and missingGroups is empty. D-02: memoized.
Example
console.log(msg.structure.recognized); // true for ORU^R01, ADT^A01, …
console.log(msg.structure.missingGroups); // e.g. ["result"] if no OBR/OBX
Returns
visit
Get Signature
get visit():
Visit|undefined
PV1-derived visit view, or undefined when no PV1 segment exists
(HELPERS-03). D-02: memoized. HELPERS-07: never throws.
Example
console.log(msg.visit?.patientClass); // "I"
console.log(msg.visit?.admitDateTime?.raw); // fidelity TS
console.log(msg.visit?.attendingDoctor?.familyName);
Returns
Visit | undefined
Methods
addSegment()
addSegment(
name,fields):this
Append a new segment to the end of the message. name must match
/^(?:[A-Z]{3}|Z[A-Z0-9]{2})$/u: throws TypeError otherwise (D-19).
fields is interpreted in HL7 1-indexed terms: addSegment("NTE", [a, b, c])
produces a segment whose NTE-1 = a, NTE-2 = b, NTE-3 = c. The
internal RawSegment.fields[0] name/separator placeholder is synthesized
by this method.
Each entry may be a plain string (treated as a single-subcomponent
single-component single-repetition field) or a full RawField object
for advanced callers who need structured content.
Invalidates caches on return; warnings untouched (D-16).
Parameters
name
string
fields
readonly (string | RawField)[]
Returns
this
Example
msg.addSegment("NTE", ["", "note text"]);
msg.get("NTE.2"); // "note text"
allergies()
allergies(): readonly
Allergy[]
Every AL1 as an Allergy in document order. D-05: returns [] when no
AL1 present.
Returns
readonly Allergy[]
Example
for (const al of msg.allergies()) console.log(al.code?.text, al.severity);
allSegments()
allSegments(): readonly
Segment[]
Iterate every Segment in document order (MSH first, then every
subsequent segment). Cached per-message; same array reference and same
Segment instances on repeat calls (D-11). Invalidated wholesale by
the mutation methods.
Returns
readonly Segment[]
Example
for (const seg of msg.allSegments()) {
console.log(seg.type);
}
appointments()
appointments(): readonly
Appointment[]
Every SCH of an SIU message as a typed Appointment, with
the AIS/AIG/AIL/AIP resource segments that follow it grouped positionally
under that SCH. Surfaces the placer/filler appointment ids, SCH-25 filler
status (Table 0278), SCH-11 start/end timing, and the resource groups
(service / general / location / personnel). D-05: returns [] when no SCH
is present. D-06: not memoized. Never throws (HELPERS-07). Not a
scheduling-workflow state machine: see the package known-limitations.
Returns
readonly Appointment[]
Example
for (const appt of msg.appointments()) {
console.log(appt.fillerAppointmentId, appt.fillerStatusCode?.identifier);
for (const r of appt.resources) console.log(r.kind, r.code?.identifier);
}
charges()
charges(): readonly
Charge[]
Every FT1 of a DFT message as a typed Charge, one per
FT1 in document order. Surfaces billing-critical fields (FT1-6 transaction
type, FT1-7 code, FT1-11/12 extended/unit amount, FT1-19 diagnosis linkage)
with no billing logic and no money-as-float: amounts are the verbatim
CP wire text. D-05: returns [] when no FT1 is present. D-06: not memoized.
Never throws (HELPERS-07).
Returns
readonly Charge[]
Example
for (const charge of msg.charges()) {
console.log(charge.transactionType, charge.transactionCode?.identifier);
console.log(charge.amountExtended); // verbatim, never a number
}
diagnoses()
diagnoses(): readonly
Diagnosis[]
Every DG1 as a Diagnosis in document order. D-05: returns [] when no
DG1 present.
Returns
readonly Diagnosis[]
Example
for (const dg of msg.diagnoses()) console.log(dg.code?.identifier);
documents()
documents(): readonly
ClinicalDocument[]
Every TXA of an MDM message as a typed ClinicalDocument,
with the OBX narrative body grouped positionally under that TXA. The
completion status (TXA-17) and availability status (TXA-19) are surfaced as
distinct fields and never conflated: a document can be available before
it is authenticated, and reading a preliminary document as final is the
harm. D-05: returns [] when no TXA is present. D-06: not memoized. Never
throws (HELPERS-07).
Returns
readonly ClinicalDocument[]
Example
for (const doc of msg.documents()) {
console.log(doc.documentType, doc.completionStatus, doc.availabilityStatus);
for (const obx of doc.observations) console.log(obx.value); // narrative body
}
get()
get(
path):string|undefined
Resolve a dot-path (e.g. PID.5.1, OBX[2].5, PID.3[0].1) to its
decoded leaf string (unescaped once at parse: never re-unescaped on
read). Returns undefined when the path doesn't
resolve: never throws on missing path (MODEL-05). Throws TypeError
on malformed path syntax (e.g. "pid.5", empty string).
Parameters
path
string
Returns
string | undefined
Example
const msg = parseHL7(raw);
msg.get("PID.5.1"); // "Smith"
msg.get("OBX[2].5"); // third OBX's 5th field
msg.get("NOT.9.9"); // undefined
msg.get("MSH.12"); // "2.5": HL7 version string
getAll()
getAll(
segmentType): readonlySegment[]
Return every Segment of segmentType in document order. Returns []
(empty array, NEVER undefined) when no segment of that type exists
(MODEL-02). Alias for segments(segmentType): shares the same cache.
Parameters
segmentType
string
Returns
readonly Segment[]
Example
for (const obx of msg.getAll("OBX")) {
console.log(obx.field(5).value);
}
identityEvents()
identityEvents(): readonly
IdentityEvent[]
Every recognized ADT patient-identity event (merge / move / link /
unlink / person add/update), with the MRG-sourced
prior and PID/PV1-sourced surviving parties labelled by role and the
spec-constant direction: "MRG_TO_PID" on merge/move events. Returns
[] when the trigger event is not in the identity family. D-06: not
memoized. Never throws; incomplete merge pairs surface a
MERGE_MISSING_PRIOR_OR_SURVIVOR warning on the event.
Returns
readonly IdentityEvent[]
Example
for (const ev of msg.identityEvents()) {
if (ev.kind === "merge" && ev.prior && ev.surviving) {
// retire ev.prior.identifiers in favour of ev.surviving.identifiers
}
}
immunizations()
immunizations(): readonly
Immunization[]
Every RXA of a VXU^V04 as a typed Immunization, with RXR (route/site)
and OBX children grouped positionally under the RXA and orderControl
from the preceding ORC of the VXU order group. D-05:
returns [] when no RXA present. D-06: not memoized. The vaccine code
carries its own provenance; the action code (RXA-21) is surfaced verbatim
and recordOrigin (administered vs historical) is derived only from the
well-known NIP001 RXA-9.1 codes: never guessed.
Returns
readonly Immunization[]
Example
for (const imm of msg.immunizations()) {
console.log(imm.vaccineCode?.identifier, imm.doseAmount, imm.recordOrigin);
console.log(imm.actionCode, imm.completionStatus);
}
insurance()
insurance(): readonly
Insurance[]
Every IN1 as an Insurance entry with positional IN2/IN3 presence flags.
D-05: returns [] when no IN1 present.
Returns
readonly Insurance[]
Example
for (const ins of msg.insurance()) console.log(ins.planId?.text);
medications()
medications(): readonly
Medication[]
Every RXO/RXE/RXD/RXA as a typed Medication, with RXR (route) and RXC
(component) segments grouped positionally under their parent. D-05:
returns [] when no RX* parent present. D-06: not memoized. The give
amount and give strength are surfaced separately and never
reconciled.
Each medication also carries its TQ1 / legacy embedded-TQ (RXE-1) timings
(repeat pattern verbatim, never resolved to a schedule).
Returns
readonly Medication[]
Example
for (const med of msg.medications()) {
console.log(med.context, med.giveCode?.identifier, med.giveCode?.nameOfCodingSystem);
console.log(med.amount?.minimum, med.strength?.value);
for (const t of med.timings) console.log(t.repeatPattern?.code, t.totalOccurrences);
}
nextOfKin()
nextOfKin(): readonly
NextOfKin[]
Every NK1 as a NextOfKin entry in document order. D-05: returns []
when no NK1 present.
Returns
readonly NextOfKin[]
Example
for (const nk of msg.nextOfKin()) {
console.log(nk.name?.familyName, nk.relationship?.text);
}
notes()
notes(): readonly
string[]
Message-level NTE notes: every NTE segment with no recognized
preceding parent (not immediately following a PID, ORC, OBR, or
OBX), surfaced verbatim in document order so nothing is dropped. Notes
that DO attach to a specific patient / order / result are exposed on those
helper outputs (msg.patient?.notes, order.notes, observation.notes),
not here. D-05: returns [] when there are none. D-06: NOT memoized.
Returns
readonly string[]
Example
for (const note of msg.notes()) console.log(note); // message-level narrative
observations()
observations(): readonly
Observation[]
Every OBX segment as a typed Observation in document order. D-05:
returns [] when no OBX present. D-06: NOT memoized: each call
re-walks rawSegments. Value type is discriminated per D-13.
Returns
readonly Observation[]
Example
for (const obs of msg.observations()) {
if (obs.valueType === "NM") console.log(obs.value); // number | undefined
}
orders()
orders(): readonly
Order[]
Every OBR as an Order with its OBX children grouped positionally (D-12) and
its TQ1 / legacy embedded-TQ (ORC-7) timings (the repeat pattern is
surfaced verbatim, never resolved to a schedule). D-05: returns [] when no
OBR present. D-06: not memoized.
Returns
readonly Order[]
Example
for (const order of msg.orders()) {
console.log(order.placerOrderNumber, order.observations.length);
for (const t of order.timings) console.log(t.repeatPattern?.code); // e.g. "Q6H": verbatim
}
prettyPrint()
prettyPrint():
string
Emit this message as a human-readable multi-line string for logs and
debugging (SER-04). Single opinionated format (D-22 no options):
header line with type / controlId / timestamp / segment count, then
one line per segment with labeled [N]=value fields (D-23). Composite
values render as their raw HL7 string: depth stops at field level
(D-24). Pure: never warns, never throws (D-26).
Field values render as their raw HL7 string representation.
Embedded delimiters in user data appear as escape sequences: e.g.
a patient family name containing | renders as Smith\F\Jones
(NOT Smith|Jones). This preserves round-trip fidelity: copy-pasting
prettyPrint output into parseHL7 yields a structurally equivalent
message. For un-escaped human display, parse the composite first via
typed accessors (e.g. msg.patient?.familyName): those return
already-decoded strings.
Returns
string
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7(raw);
console.log(msg.prettyPrint());
// HL7 ADT^A01 controlId=MSG001 timestamp=2026-04-19T10:15:00Z (5 segments)
// MSH [3]=SENDAPP [4]=SENDFAC ...
// PID [1]=1 [3]=MRN123 [5]=Doe^John
removeSegment()
removeSegment(
segmentType,occurrenceOrOptions?):this
Remove segments by type + occurrence or by type + all. Call shapes:
removeSegment("NTE"): remove the FIRST NTE (occurrence 0).removeSegment("OBX", 1): remove the SECOND OBX (0-indexed per D-01).removeSegment("OBX", { all: true }): remove ALL OBX segments.
MSH is protected: removeSegment("MSH") throws TypeError (every
HL7 message must retain its MSH segment). Unknown segment types are a
no-op (idempotent; no throw). Segment name must match the D-19 shape
regex: invalid shapes throw TypeError for symmetry with addSegment.
Invalidates caches on return; warnings untouched (D-16).
Parameters
segmentType
string
occurrenceOrOptions?
number | { all?: boolean; }
Returns
this
Example
msg.removeSegment("NTE"); // remove first NTE
msg.removeSegment("OBX", 1); // remove second OBX
msg.removeSegment("OBX", { all: true }); // remove all remaining OBX
segments()
segments(
segmentType): readonlySegment[]
Return the cached array of Segment wrappers for segmentType in
document order. The returned array identity and the individual Segment
instances are both stable across calls (D-11). Invalidated wholesale
by the mutation methods.
Parameters
segmentType
string
Returns
readonly Segment[]
Example
const pid = msg.segments("PID")[0];
if (pid !== undefined) console.log(pid.field(5).value);
setComposite()
setComposite<
K>(path,kind,value):this
Set a typed composite at a field (or field-repetition) dot-path:
the conservative-emit mirror of the typed read accessors
(asXpn/asCx/…). The caller passes a structured value (an XPN name, a CX
identifier, a TS timestamp, …) by its CompositeKind, and the setter
encodes it into a spec-clean field using the encode-safe path: any
delimiter embedded in a component value is escaped, never injected, so
a familyName of "Smith^Jr" re-parses to exactly that string rather than
forging a component boundary. No hand-assembly of ^/&/~.
The path must resolve to a field ("PID.5") or a specific
repetition of a field ("PID.11[1]"). A component/subcomponent-level
path ("PID.5.1") is rejected with TypeError: a composite occupies a
whole field, not a single component. Like setField, the target
segment must already exist (addSegment first); the repetition defaults to
index 0 and other repetitions of the field are preserved.
Never fabricates: an omitted optional composite field encodes to an
empty/absent component, never a defaulted value; an all-empty composite
clears the field. Segment/helper caches are invalidated on success; the
frozen warnings array is untouched.
Type Parameters
K
K extends CompositeKind
Parameters
path
string
kind
K
value
Returns
this
Example
const msg = buildMessage({ type: "ADT^A01" }).addSegment("PID", [""]);
msg.setComposite("PID.5", "XPN", { familyName: "Smith", givenName: "Ann" });
msg.setComposite("PID.3", "CX", { idNumber: "MRN001", identifierTypeCode: "MR" });
msg.setComposite("PID.7", "TS", "19880705");
msg.get("PID.5.1"); // "Smith"
setField()
setField(
path,value):this
Set the string value at a dot-path. Mutates the underlying tree and
returns this for chaining (D-15). Auto-creates missing repetitions,
components, and subcomponents WITHIN an existing field, but does NOT
auto-create segments: callers must addSegment first (throws
TypeError with an actionable message otherwise).
The value is accepted verbatim: unescaped delimiter characters are NOT rejected on input (D-18). Re-escaping is the serializer's concern.
MSH-1 / MSH-2 follow the user-facing HL7 convention: setField("MSH.3", ...)
targets MSH-3 (sending application), matching msg.get("MSH.3").
Segment/Field wrapper caches are invalidated wholesale on success (D-17).
The frozen warnings array is never touched (D-16).
Parameters
path
string
value
string
Returns
this
Example
msg.setField("PID.8", "F"); // patient sex → F
msg.setField("PID.5.1", "Jones"); // family name
msg.setField("PID.4[2].1", "MRN2"); // create third repetition of PID-4
toJSON()
toJSON():
SerializedMessage
Emit this message as a structured SerializedMessage JSON projection
(SER-03). Invoked automatically by JSON.stringify(msg) (D-18).
Re-walks rawSegments on every call (D-30 no caching). Mirrors the
raw tree one-for-one, preserves isNull, always includes
warnings: [], and includes profile: { name, lineage } only when
this.profile is truthy (D-19/D-20). Pure: never warns, never throws.
Returns
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7(raw);
const snap = msg.toJSON();
console.log(snap.segments[0]?.name); // "MSH"
console.log(JSON.stringify(msg)); // same content, auto-invokes toJSON
toString()
toString():
string
Emit this message as spec-clean HL7 (SER-01). Re-walks rawSegments
on every call (D-30 no caching). Segments are joined with \r per
D-05; MSH-1 and MSH-2 are inlined verbatim from
this.encodingCharacters per D-06; every field string passes through
reescape per D-04. RawField.isNull === true is preserved as the
HL7 literal "" (D-02). Pure: never warns, never throws (D-07).
Returns
string
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7(raw);
console.log(msg.toString()); // spec-clean, CR-separated HL7
Hl7ParseError
Thrown by parseHL7 when the input violates one of the 4 unrecoverable
Tier-3 structural rules (missing MSH, truncated MSH, invalid encoding
characters, or empty input). Carries positional context plus a short
snippet of the offending input so consumers can log actionable errors.
Remarks
snippet may contain PHI when parsing real clinical messages, and the
library does not redact it: redact at the call site if required by your
compliance posture. It is the only field carrying input verbatim and
unfiltered (capped at 40 characters plus an ellipsis, but not shape-checked
in any way), so it is the one to redact first.
message is bounded but not absolutely content-free, and the difference is
worth stating because message is what a logger prints by default and what
stack embeds. 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>. So 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. The other
shapes are narrower in practice than their patterns allow, because the
library only ever feeds them registry-matched values, but the patterns
themselves admit more (a message type up to 26 characters, a version up to
14), which matters if you construct warnings yourself.
Under { strict: true } an escalated Tier-2 warning is thrown as this
error, carrying the warning's own bounded message and a snippet of the
first 40 characters of the input. That snippet is the head of the
message rather than the deviation's own segment, so it is usually the MSH
header; it is deliberately not re-pointed at the offending segment, since
doing so would move more clinical content into the unredacted field.
Example
import { parseHL7, Hl7ParseError } from "@cosyte/hl7";
try {
parseHL7("");
} catch (err) {
if (err instanceof Hl7ParseError && err.code === "EMPTY_INPUT") {
// handle empty input: err.position, err.snippet available
}
}
Extends
Error
Constructors
Constructor
new Hl7ParseError(
code,message,position,snippet):Hl7ParseError
Internal
Construct a new Hl7ParseError. All four fields are required so every
thrower populates full positional context per the TOL-02 requirement.
Parameters
code
message
string
position
snippet
string
Returns
Overrides
Error.constructor
Properties
code
readonlycode:FatalCode
position
readonlyposition:Hl7Position
snippet
readonlysnippet:string
ProfileDefinitionError
Thrown by defineProfile() and profile-validation code when a
profile definition is structurally invalid: e.g. references an undefined
parent, declares a malformed custom segment, or includes an unsupported
date format. Callers may optionally supply the offending profile name for
better diagnostics.
Example
import { ProfileDefinitionError } from "@cosyte/hl7";
throw new ProfileDefinitionError(
"Unknown parent profile: epic-v7",
"my-epic-extension",
);
Extends
Error
Constructors
Constructor
new ProfileDefinitionError(
message,profileName?):ProfileDefinitionError
Internal
Construct a new ProfileDefinitionError. profileName is optional so
callers may omit it when the offending profile cannot be named
(e.g. during initial validation before a name is parsed).
Parameters
message
string
profileName?
string
Returns
Overrides
Error.constructor
Properties
profileName
readonlyprofileName:string|undefined
Segment
Wrapper over a RawSegment exposing typed per-position Field instances.
seg.field(3) === seg.field(3): referential stability is guaranteed per
segment instance.
Example
import { parseHL7 } from "@cosyte/hl7";
const msg = parseHL7(raw);
const pid = msg.segments("PID")[0];
if (pid !== undefined) console.log(pid.field(5).value);
Constructors
Constructor
new Segment(
raw,enc,absoluteIndex,customFields?):Segment
Internal
Construct a new Segment. Called internally by Hl7Message; user code
should obtain Segment instances via msg.segments(type) or
msg.allSegments().
The optional customFields parameter is the per-segment portion of the
applied profile's merged customSegments map (PROF-07 / D-16). When
supplied, get(name) resolves names against it; otherwise get(name)
always returns undefined.
Parameters
raw
enc
absoluteIndex
number
customFields?
Readonly<Record<string, number>>
Returns
Properties
absoluteIndex
readonlyabsoluteIndex:number
Internal
Absolute index of this segment in Hl7Message.rawSegments[]. Used for position tracking.
customFields
readonlycustomFields:Readonly<Record<string,number>> |undefined
Internal
Lookup map from profile-declared field name → 1-indexed HL7 position.
Absent when no profile was applied to the parent message, or when the
applied profile does not declare customSegments for this segment's
type. Consumed by get(name) to resolve named-field access (PROF-07).
enc
readonlyenc:EncodingCharacters
Internal
The 5 encoding characters for this message. Exposed for composite parsers.
fields
readonlyfields: readonlyRawField[]
Reference to the underlying RawSegment.fields: 1-indexed per HL7 convention.
raw
readonlyraw:RawSegment
Internal
The full RawSegment this wrapper wraps. Exposed for mutation methods.
type
readonlytype:string
Segment identifier: three characters with a leading letter, e.g. "PID",
"OBX", "ZPI".
Bounded, and it is "<withheld>" when the raw name is not that shape.
A line with no field separator has its whole content read as a segment
name, so an unescaped line break inside a narrative field forges a
"segment" whose name is clinical text. This field presents itself as a
structural identifier and consumers interpolate it into labels, loci and
reports, so it never carries that text. "" still means absent, which is
a different fact from withheld.
Use Segment.raw.name when you need the verbatim text: it is the
unbounded value, and it is what serialization emits, so the byte-verbatim
round-trip is unaffected. One consequence worth knowing: a segment whose
raw name fails the shape (a 4-character vendor Z-segment, which HL7 v2
Ch. 2 §2.5 does not permit) is not matched by msg.segments(name).
Methods
field()
field(
n):Field
Return the Field wrapper at HL7 position n. Indexing follows the HL7
1-indexed convention: seg.field(5) on a PID segment maps to PID-5.
MSH segments use the same user-facing convention: msh.field(1) returns
the field-separator (MSH-1), msh.field(2) returns encoding chars
(MSH-2), msh.field(3) returns MSH-3, and so on: the internal
fields[N-1] offset for MSH segments is applied here (mirrors the
dot-path resolver in dot-path.ts, keeping msg.segments('MSH')[0].field(3)
and msg.get('MSH.3') in agreement).
Returns a synthetic empty Field (.isNull === false, .value === "")
when n is out of range: never throws (MODEL-05). Successive calls with
the same n return the same Field instance (D-12).
Parameters
n
number
Returns
Example
const pid5 = msg.segments("PID")[0]?.field(5);
console.log(pid5?.value); // "Smith"
const msh3 = msg.segments("MSH")[0]?.field(3);
console.log(msh3?.value); // sending application (HL7 MSH-3)
get()
get(
name):Field|undefined
Return the Field at the profile-declared position for name, or
undefined when no custom mapping exists (PROF-07). Unlike field(n),
missing names return undefined, NOT a synthetic empty Field, so
typos surface instead of silently resolving to an empty string (D-14).
For segments without a profile-declared customSegments slice (most
non-Z segments, and any Z-segment whose host message had no profile
applied), this method always returns undefined (D-15 defense-in-depth
: D-05 already rejects standard-segment overlays at defineProfile()
time).
When the declared position is out of range for the underlying
RawSegment.fields, get(name) returns undefined (NOT a
synthetic-empty Field) so callers can distinguish "name not declared"
from "name declared but position missing in the raw message" only at
the presence level (both collapse to undefined per D-14).
Parameters
name
string
Returns
Field | undefined
Example
const zpi = msg.allSegments().find((s) => s.type === "ZPI");
console.log(zpi?.get("encounterId")?.value);
Interfaces
AckErrorDetail
One ERR segment's worth of error detail for buildAck. Carries codes and
locations only: never echoed PHI values (the phi-redaction-review gate
binds this contract).
Example
import { buildAck } from "@cosyte/hl7";
const ack = buildAck(inbound, {
code: "AE",
error: { conditionCode: "101", severity: "E", location: "PID^1^5" },
});
Properties
conditionCode?
readonlyoptionalconditionCode?:string
HL7 Table 0357 message error condition code (ERR-3.1). Defaults to "207"
(Application internal error). The standard display text is looked up from
Table 0357 and emitted in ERR-3.2; unknown codes emit with empty text
(the code is preserved verbatim: never dropped).
location?
readonlyoptionallocation?:string
Error location (ERR-2, an HL7 ERL). A structural path such as "PID^1^5"
(segment id ^ segment sequence ^ field position). Must not contain a
patient data value: locations point at where, never what.
severity?
readonlyoptionalseverity?:ErrSeverity
HL7 Table 0516 severity (ERR-4). Defaults to "E" (Error).
AckErrorEntry
One ERR segment interpreted from an ACK. All fields are surfaced verbatim (no table lookup) and OMITTED when the underlying component is absent (exactOptionalPropertyTypes). Codes/locations only: never PHI.
Properties
conditionCode?
readonlyoptionalconditionCode?:string
ERR-3.1: HL7 Table 0357 condition code.
conditionCodeSystem?
readonlyoptionalconditionCodeSystem?:string
ERR-3.3: condition code system name (e.g. HL70357).
conditionText?
readonlyoptionalconditionText?:string
ERR-3.2: condition code display text.
location?
readonlyoptionallocation?:string
ERR-2: error location (an HL7 ERL), surfaced verbatim.
severity?
readonlyoptionalseverity?:string
ERR-4: HL7 Table 0516 severity (I/W/E).
Acknowledgment
Typed view of an inbound ACK. accepted / error / rejected are derived
from MSA-1 against HL7 Table 0008 and are mutually exclusive; all three
are false when MSA-1 is absent or not a recognized code (fail-safe).
Example
import { interpretAck, parseHL7 } from "@cosyte/hl7";
const ack = interpretAck(parseHL7(rawAck));
if (ack.accepted) {
// safe to consider the message acknowledged
} else if (ack.rejected) {
for (const e of ack.errors) console.error(e.conditionCode, e.severity);
}
Properties
accepted
readonlyaccepted:boolean
True iff MSA-1 is a positive accept (AA/CA).
code?
readonlyoptionalcode?:string
MSA-1 acknowledgment code (HL7 Table 0008), verbatim. Omitted when absent.
controlId?
readonlyoptionalcontrolId?:string
MSA-2 message control id (the correlated inbound MSH-10), surfaced as the
field's canonical wire text (Field.text): the whole field,
delimiters included, never truncated to the first component. Note this is
the re-escaped form: a properly-escaped id arrives as its wire bytes
(ID\S\X), and an HL7 explicit-null MSA-2 surfaces as the literal
two-character "". Omitted when absent/empty.
error
readonlyerror:boolean
True iff MSA-1 is an error acknowledgment (AE/CE).
errors
readonlyerrors: readonlyAckErrorEntry[]
Every ERR segment in document order ( [] when none ).
rejected
readonlyrejected:boolean
True iff MSA-1 is a reject acknowledgment (AR/CR).
AdtEvent
Typed EVN (event type) content for buildAdt.
Properties
eventOccurred?
readonlyoptionaleventOccurred?:string|DtmParts
EVN-6 Event Occurred.
recordedDateTime?
readonlyoptionalrecordedDateTime?:string|DtmParts
EVN-2 Recorded Date/Time.
AdtPatient
Typed PID (patient identification) content for buildAdt.
Properties
accountNumber?
readonlyoptionalaccountNumber?:CX
PID-18 Patient Account Number.
address?
PID-11 Patient Address: one or more XAD addresses.
administrativeSex?
readonlyoptionaladministrativeSex?:string
PID-8 Administrative Sex (e.g. "F", "M", "U").
birthDateTime?
readonlyoptionalbirthDateTime?:string|DtmParts
PID-7 Date/Time of Birth.
identifiers?
PID-3 Patient Identifier List: one or more CX identifiers (MRN, SSN, …).
mothersMaidenName?
readonlyoptionalmothersMaidenName?:XPN
PID-6 Mother's Maiden Name.
name?
PID-5 Patient Name.
phoneHome?
PID-13 Phone Number - Home: one or more XTN telecoms.
setId?
readonlyoptionalsetId?:string
PID-1 Set ID.
AdtVisit
Typed PV1 (patient visit) content for buildAdt.
Properties
admitDateTime?
readonlyoptionaladmitDateTime?:string|DtmParts
PV1-44 Admit Date/Time.
assignedLocation?
readonlyoptionalassignedLocation?:PL
PV1-3 Assigned Patient Location.
attendingDoctor?
PV1-7 Attending Doctor.
patientClass?
readonlyoptionalpatientClass?:string
PV1-2 Patient Class (e.g. "I" inpatient, "O" outpatient, "E" emergency).
referringDoctor?
PV1-8 Referring Doctor.
setId?
readonlyoptionalsetId?:string
PV1-1 Set ID.
visitNumber?
readonlyoptionalvisitNumber?:CX
PV1-19 Visit Number.
Allergy
AL1-derived allergy entry (HELPERS-06). onsetDate is the fidelity TS.
Example
import type { Allergy } from "@cosyte/hl7";
const al: Allergy = {
type: "DA",
code: { identifier: "PEN", text: "Penicillin" },
severity: "SV",
reaction: "Hives",
};
Properties
code?
readonlyoptionalcode?:CWE
AL1-3 allergen code.
onsetDate?
readonlyoptionalonsetDate?:DtmParts
AL1-6 onset date as the fidelity TS.
reaction?
readonlyoptionalreaction?:string
AL1-5 allergy reaction description (first value).
severity?
readonlyoptionalseverity?:string
AL1-4 severity (SV=severe, MO=moderate, MI=mild).
type?
readonlyoptionaltype?:string
AL1-2 allergy type (DA=drug, FA=food, EA=environmental, ...).
Appointment
SCH-derived appointment entry (SIU scheduling breadth). Surfaces the appointment identifiers, filler status (SCH-25, Table 0278), SCH-11 start/end timing, and the AI* resource groups. NOT a scheduling-workflow state machine.
Example
import type { Appointment } from "@cosyte/hl7";
const appt: Appointment = {
fillerAppointmentId: "A1001",
fillerStatusCode: { identifier: "Booked" },
resources: [],
};
Properties
endDateTime?
readonlyoptionalendDateTime?:DtmParts
Appointment end date/time: SCH-11 TQ.5 (fidelity TS).
fillerAppointmentId?
readonlyoptionalfillerAppointmentId?:string
SCH-2 filler appointment ID (EI first component, verbatim).
fillerStatusCode?
readonlyoptionalfillerStatusCode?:CWE
SCH-25 filler status code (HL7 Table 0278): the appointment status, verbatim/provenance-only.
placerAppointmentId?
readonlyoptionalplacerAppointmentId?:string
SCH-1 placer appointment ID (EI first component, verbatim).
resources
readonlyresources: readonlyAppointmentResource[]
AIS/AIG/AIL/AIP resources grouped under this SCH. Always present (possibly empty).
startDateTime?
readonlyoptionalstartDateTime?:DtmParts
Appointment start date/time: SCH-11 TQ.4 (fidelity TS).
AppointmentResource
One appointment resource grouped under a SCH: an AIS (service),
AIG (general resource), AIL (location), or AIP (personnel / provider) segment.
The resource identifier lives at position 3 of every AI* segment; for the
personnel resource (AIP) it is additionally surfaced as a typed person
(XCN), while the coded code (first component verbatim) is always available.
Example
import type { AppointmentResource } from "@cosyte/hl7";
const r: AppointmentResource = { kind: "location", code: { identifier: "OR-1" } };
Properties
code?
readonlyoptionalcode?:CWE
The AI*-3 resource identifier surfaced as a coded element: code.identifier
is the resource id (first component, verbatim). AIS-3 / AIG-3 are coded
elements, so code.text / code.nameOfCodingSystem are meaningful there;
AIL-3 is a PL (location) rather than a coded element, so only
code.identifier (the location id, PL.1) is meaningful and the other CWE
fields are positional provenance, not a coding system. Provenance-only.
kind
readonlykind:"service"|"general"|"location"|"personnel"
Which AI* segment sourced this resource: AIS→service, AIG→general, AIL→location, AIP→personnel.
person?
readonlyoptionalperson?:XCN
AIP-3 personnel resource as a typed XCN (personnel resources only): the appointment provider.
Batch
One batch within a stream: a run of messages delimited by a BHS header
and/or a BTS trailer (both optional in §2.10.3, so a BTS closes a
preceding run into a batch even with no BHS). A run of messages with
neither a header nor a trailer is not a batch: those messages live in
BatchSplitResult.messages only, so they never inflate a batch count.
declaredMessageCount is BTS-1 when the trailer declared a usable
non-negative integer (it is optional [0..1] in the spec, so it may be
absent); actualMessageCount is always the real count.
Example
import { splitBatch } from "@cosyte/hl7";
const [batch] = splitBatch(raw).batches;
batch?.header?.name; // "BHS" (or undefined for a headerless BTS-closed run)
batch?.actualMessageCount; // messages actually in the batch
Properties
actualMessageCount
readonlyactualMessageCount:number
The actual number of messages split out of this batch.
declaredMessageCount?
readonlyoptionaldeclaredMessageCount?:number
BTS-1 batch message count, when declared as a non-negative integer.
header?
readonlyoptionalheader?:BatchEnvelopeSegment
The BHS header, when this batch was opened by one.
messages
readonlymessages: readonlyBatchMessageEntry[]
The messages in this batch, in stream order.
trailer?
readonlyoptionaltrailer?:BatchEnvelopeSegment
The BTS trailer, when this batch was closed by one.
BatchEnvelopeSegment
A raw batch-envelope segment (FHS/BHS/BTS/FTS) surfaced by
splitBatch. fields is the segment split on its own field separator
with fields[0] holding the segment name: deliberately the raw token
array, not a typed model: typed FHS/BHS field helpers beyond the raw
fields are not part of this surface. Note the
HL7 MSH-family indexing quirk: for FHS/BHS, fields[1] is the
encoding-characters field (FHS-2/BHS-2); for BTS/FTS, fields[1] is
field 1 (BTS-1 batch message count / FTS-1 file batch count).
Example
import { splitBatch } from "@cosyte/hl7";
const { fileHeader } = splitBatch("FHS|^~\\&|SENDER\r...");
fileHeader?.name; // "FHS"
fileHeader?.fields[2]; // "SENDER" (FHS-3, the File Sending Application)
Properties
fields
readonlyfields: readonlystring[]
Raw field tokens; fields[0] is the segment name.
name
readonlyname:BatchEnvelopeName
position
readonlyposition:Hl7Position
Position of this envelope segment in the split stream.
raw
readonlyraw:string
The verbatim segment string (line-ending normalized).
BatchSplitResult
The result of splitBatch: the flattened messages (every message
across every batch, in stream order: the primary surface), the nested
batches, the raw file envelope segments, and the batch-level warnings
(count-mismatch / missing-trailer). hadEnvelope is false for a bare
passthrough (no FHS/BHS/BTS/FTS seen).
Example
import { splitBatch } from "@cosyte/hl7";
const result = splitBatch(rawBatchFile);
result.messages.length; // every message, batched or not
result.batches.length; // === result.actualBatchCount
result.warnings; // BATCH_COUNT_MISMATCH / BATCH_MISSING_TRAILER (counts only)
Properties
actualBatchCount
readonlyactualBatchCount:number
The number of explicit batches split out of the stream.
batches
readonlybatches: readonlyBatch[]
The explicit (BHS-delimited) batches, in stream order.
declaredBatchCount?
readonlyoptionaldeclaredBatchCount?:number
The last FTS-1 file batch count, when declared as a non-negative integer.
fileHeader?
readonlyoptionalfileHeader?:BatchEnvelopeSegment
The first FHS file header, when present.
fileTrailer?
readonlyoptionalfileTrailer?:BatchEnvelopeSegment
The last FTS file trailer, when present.
hadEnvelope
readonlyhadEnvelope:boolean
false when no envelope segment was seen (bare passthrough).
messages
readonlymessages: readonlyBatchMessageEntry[]
Every message split out of the stream, in order: the primary surface. This
includes messages that belong to no explicit batch (a bare stream, or
content outside any BHS/BTS), so it is a superset of the messages
reachable via batches.
warnings
readonlywarnings: readonlyHl7ParseWarning[]
Batch-level warnings (count mismatch, missing trailer); counts/positions only, never PHI.
BuildAckOptions
Options for buildAck.
Example
buildAck(inbound, { code: "AA" }); // bare accept
buildAck(inbound, { code: "AR", error: { conditionCode: "200" } });
Properties
code
readonlycode:AckCode
The acknowledgment disposition to emit in MSA-1 (HL7 Table 0008). One of
AA/AE/AR (original) or CA/CE/CR (enhanced accept-level).
Required: buildAck builds the disposition it is told. An unknown code
is a programming error and throws TypeError.
error?
readonlyoptionalerror?:AckErrorDetail| readonlyAckErrorDetail[]
Optional error detail. A single AckErrorDetail or an array → one
ERR segment each. Typically supplied for AE/AR/CE/CR.
mode?
readonlyoptionalmode?:AckMode
Optional explicit acknowledgment mode. When omitted it is derived from the
inbound MSH-15/16 via detectAckMode. buildAck emits code
verbatim regardless of mode: this field is advisory metadata for adapters
(e.g. @cosyte/mllp's commit-policy layer) that need the detected mode.
BuildAdtInit
Input for buildAdt: the MSH envelope plus the typed segment bodies.
Extends
MessageEnvelope
Properties
controlId?
readonlyoptionalcontrolId?:string
Auto-generated via generateControlId() when omitted.
Inherited from
MessageEnvelope.controlId
event?
readonlyoptionalevent?:AdtEvent
EVN content (EVN-1 is the trigger event; EVN-2/6 optional).
patient
readonlypatient:AdtPatient
PID content. Required: never fabricated.
processingId?
readonlyoptionalprocessingId?:string
Defaults to "P" (production).
Inherited from
MessageEnvelope.processingId
receivingApp?
readonlyoptionalreceivingApp?:string
Inherited from
MessageEnvelope.receivingApp
receivingFacility?
readonlyoptionalreceivingFacility?:string
Inherited from
MessageEnvelope.receivingFacility
sendingApp?
readonlyoptionalsendingApp?:string
Inherited from
MessageEnvelope.sendingApp
sendingFacility?
readonlyoptionalsendingFacility?:string
Inherited from
MessageEnvelope.sendingFacility
timestamp?
readonlyoptionaltimestamp?:string|Date
Date → HL7 YYYYMMDDHHmmss (UTC, seconds); a pre-formatted HL7 TS string
passes through verbatim. Defaults to new Date().
Inherited from
MessageEnvelope.timestamp
version?
readonlyoptionalversion?:string
Defaults to "2.5".
Inherited from
MessageEnvelope.version
visit?
readonlyoptionalvisit?:AdtVisit
PV1 content. Optional; an (empty) PV1 is emitted regardless so the visit group is present.
BuildMessageInit
Input shape for buildMessage (SER-06). Mirrors msg.meta 1-for-1 so
read and write surfaces share field names (sendingApp, sendingFacility,
receivingApp, receivingFacility, controlId, timestamp, version,
processingId). type is the only required field.
Empty string vs. undefined semantics: omitting a field and passing an
empty string produce IDENTICAL wire output (both emit as an absent
positional field). To emit an HL7 explicit null ("") at a specific
position in an outbound message, build the message first and then call
.setField(path, '""'): the mutation method sets isNull=true
on the underlying RawField, and the emitter preserves that as the
literal two-char output per D-02.
Example
import { buildMessage } from "@cosyte/hl7";
const msg = buildMessage({
type: "ADT^A01",
sendingApp: "CLINIC",
sendingFacility: "MAIN",
receivingApp: "LAB",
receivingFacility: "REF",
timestamp: new Date("2026-04-19T10:15:00Z"),
})
.addSegment("PID", ["", "", "MRN123", "", "Doe^John"]);
console.log(msg.toString());
// To emit HL7 explicit null ("") instead of absent:
// msg.setField("PID.2", '""'); // distinct from empty/omitted
Properties
controlId?
readonlyoptionalcontrolId?:string
Auto-generated via generateControlId() when omitted (D-12).
processingId?
readonlyoptionalprocessingId?:string
Defaults to "P" (production).
receivingApp?
readonlyoptionalreceivingApp?:string
receivingFacility?
readonlyoptionalreceivingFacility?:string
sendingApp?
readonlyoptionalsendingApp?:string
sendingFacility?
readonlyoptionalsendingFacility?:string
timestamp?
readonlyoptionaltimestamp?:string|Date
Date formatted to HL7 YYYYMMDDHHmmss (UTC, seconds) when supplied;
pre-formatted HL7 TS string passed through verbatim (D-13). Defaults to
new Date().
type
readonlytype:string
HL7 message type, e.g. "ADT^A01" (code + trigger) or
"ORU^R01^ORU_R01" (code + trigger + structure). Required (D-16).
The string is split on ^ into MSH-9 components; each component is
emitted verbatim. Literal ^ characters in a component are NOT
representable via this field: splitting is unconditional. Callers
needing that edge case should build the message and then use
.setField("MSH.9.1", ...) etc. after construction.
Rejected at runtime (D-16 / WR-04):
- empty string
""or whitespace-only" "; - strings whose every
^-split component is empty/whitespace (e.g."^","^^"," ^ ").
version?
readonlyoptionalversion?:string
Defaults to "2.5".
BuildOruInit
Input for buildOru: the MSH envelope plus the typed segment bodies.
Extends
MessageEnvelope
Properties
controlId?
readonlyoptionalcontrolId?:string
Auto-generated via generateControlId() when omitted.
Inherited from
MessageEnvelope.controlId
observations
readonlyobservations: readonlyOruObservation[]
OBX content. Required, non-empty: an ORU with no result is a typed error.
order?
readonlyoptionalorder?:OruOrder
OBR content. Optional; an (empty) OBR is emitted regardless so the result group is well-formed.
patient
readonlypatient:AdtPatient
PID content. Required: never fabricated.
processingId?
readonlyoptionalprocessingId?:string
Defaults to "P" (production).
Inherited from
MessageEnvelope.processingId
receivingApp?
readonlyoptionalreceivingApp?:string
Inherited from
MessageEnvelope.receivingApp
receivingFacility?
readonlyoptionalreceivingFacility?:string
Inherited from
MessageEnvelope.receivingFacility
sendingApp?
readonlyoptionalsendingApp?:string
Inherited from
MessageEnvelope.sendingApp
sendingFacility?
readonlyoptionalsendingFacility?:string
Inherited from
MessageEnvelope.sendingFacility
timestamp?
readonlyoptionaltimestamp?:string|Date
Date → HL7 YYYYMMDDHHmmss (UTC, seconds); a pre-formatted HL7 TS string
passes through verbatim. Defaults to new Date().
Inherited from
MessageEnvelope.timestamp
version?
readonlyoptionalversion?:string
Defaults to "2.5".
Inherited from
MessageEnvelope.version
Cardinality
A repetition-count constraint. min / max are inclusive bounds on the
number of repetitions (for a field rule) or occurrences (for a segment
rule). max may be the literal "*" for "unbounded". Omitted bounds are
unconstrained on that side.
Cardinality min is checked only when the element is present. An absent
Required element is reported as FINDING_CODES.PROFILE_REQUIRED_ABSENT
(a usage finding), not a cardinality finding: so a missing R field with
cardinality.min = 1 yields exactly one finding, never two.
Example
import type { Cardinality } from "@cosyte/hl7";
const once: Cardinality = { min: 1, max: 1 };
const many: Cardinality = { min: 1, max: "*" };
Properties
max?
readonlyoptionalmax?:number|"*"
min?
readonlyoptionalmin?:number
CE
HL7 v2 Coded Element (CE): coded element per HL7 Chapter 2. All 6 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- identifier (e.g. "GLU")
- text (human-readable, e.g. "Glucose")
- nameOfCodingSystem (e.g. "LN" for LOINC)
- alternateIdentifier
- alternateText
- nameOfAlternateCodingSystem
Components 7+ (present when a CWE-shaped value is read through the CE
accessor: e.g. version ids, originalText) are surfaced verbatim on
extraComponents rather than dropped.
Example
import type { CE } from "@cosyte/hl7";
const code: CE = { identifier: "GLU", text: "Glucose", nameOfCodingSystem: "LN" };
Properties
alternateIdentifier?
readonlyoptionalalternateIdentifier?:string
alternateText?
readonlyoptionalalternateText?:string
extraComponents?
readonlyoptionalextraComponents?: readonlystring[]
Components beyond the modeled 6 (HL7 component 7 onward), preserved
verbatim and in order. Non-empty only when a CWE-shaped value is read
through the CE accessor; OMITTED otherwise. An absent interior component
is preserved as "" so extraComponents[i] maps to HL7 component
7 + i.
identifier?
readonlyoptionalidentifier?:string
nameOfAlternateCodingSystem?
readonlyoptionalnameOfAlternateCodingSystem?:string
nameOfCodingSystem?
readonlyoptionalnameOfCodingSystem?:string
text?
readonlyoptionaltext?:string
Charge
FT1-derived charge entry (DFT financial breadth). Billing-critical fields surfaced with no billing logic and no money-as-float: the extended/unit amounts are the verbatim CP wire text, never parsed to a number.
Example
import type { Charge } from "@cosyte/hl7";
const c: Charge = {
transactionType: "CG",
transactionCode: { identifier: "80053", text: "Metabolic panel" },
amountExtended: "150.00^USD",
diagnoses: [{ identifier: "E11.9" }],
};
Properties
amountExtended?
readonlyoptionalamountExtended?:string
FT1-11 transaction amount, extended (CP): canonical wire text (e.g. 150.00^USD, byte-exact for a plain amount); never parsed to a number.
amountUnit?
readonlyoptionalamountUnit?:string
FT1-12 transaction amount, unit (CP): canonical wire text; never parsed to a number.
diagnoses
readonlydiagnoses: readonlyCWE[]
FT1-19 diagnosis code(s) linked to this charge (CE, repeating): billing diagnosis linkage. Always present (possibly empty).
quantity?
readonlyoptionalquantity?:number
FT1-10 transaction quantity (NM; strict-parsed, never NaN).
transactionCode?
readonlyoptionaltransactionCode?:CWE
FT1-7 transaction code: the institution charge/procedure code (CWE, provenance-only, never validated).
transactionDate?
readonlyoptionaltransactionDate?:DtmParts
FT1-4 transaction date (fidelity TS).
transactionType?
readonlyoptionaltransactionType?:string
FT1-6 transaction type (HL7 Table 0017: CG charge, CD credit, PY payment, AJ adjustment). Verbatim.
CharsetResolution
The outcome of resolving an MSH-18 label (or options.charset override)
against Table 0211.
Properties
canonical
readonlycanonical:string
The canonical Table-0211 code the label resolved to (e.g. "UTF-8",
"8859/1", "ISO IR87"). For an unrecognized label this is the input
trimmed + upper-cased, so it is still a stable key for comparison.
decoder
readonlydecoder:string
The WHATWG TextDecoder label to decode with when treatment === "decode".
Empty when treatment === "verbatim".
recognized
readonlyrecognized:boolean
true when the label is a recognized HL7 Table-0211 code (whether or not
the parser decodes it); false when the label is not in Table 0211.
Drives the warning code on the verbatim path: recognized-but-verbatim
→ UNSUPPORTED_CHARSET, unrecognized → UNKNOWN_CHARSET.
treatment
readonlytreatment:CharsetTreatment
Whether the parser decodes this set or preserves its bytes verbatim.
ClinicalDocument
TXA-derived clinical-document entry (MDM document breadth). The load-bearing safety property: completion status (TXA-17) and availability status (TXA-19) are DISTINCT fields and are never conflated: a document can be available before it is authenticated, and reading a preliminary document as final is the clinical harm. Both are verbatim / provenance-only.
Example
import type { ClinicalDocument } from "@cosyte/hl7";
const doc: ClinicalDocument = {
documentType: "DS",
completionStatus: "IP", // in progress: NOT yet authenticated
availabilityStatus: "AV", // available: a different axis
observations: [],
};
Properties
activityDateTime?
readonlyoptionalactivityDateTime?:DtmParts
TXA-4 activity date/time (fidelity TS).
availabilityStatus?
readonlyoptionalavailabilityStatus?:string
TXA-19 document availability status (HL7 Table 0273: AV available,
CA cancelled, OB obsolete, UN unavailable). Surfaced DISTINCT from
completionStatus; verbatim, never validated, never merged.
completionStatus?
readonlyoptionalcompletionStatus?:string
TXA-17 document completion status (HL7 Table 0271: e.g. DO documented,
IP in progress, AU authenticated, LA legally authenticated, IN
incomplete). Surfaced DISTINCT from availabilityStatus; verbatim,
never validated, never merged.
documentType?
readonlyoptionaldocumentType?:string
TXA-2 document type (HL7 Table 0270), verbatim.
observations
readonlyobservations: readonlyObservation[]
OBX narrative body grouped under this TXA. Always present (possibly empty).
parentDocumentNumber?
readonlyoptionalparentDocumentNumber?:string
TXA-13 parent document number (EI first component): addendum / replacement link.
uniqueDocumentNumber?
readonlyoptionaluniqueDocumentNumber?:string
TXA-12 unique document number (EI first component, verbatim).
CodedSystemFields
Structural shape of any coded element that carries a primary + alternate
coding system: both CWE and CE satisfy it. Kept structural
(not a union) so callers can pass a dg.code / obs.code directly.
Properties
nameOfAlternateCodingSystem?
readonlyoptionalnameOfAlternateCodingSystem?:string
nameOfCodingSystem?
readonlyoptionalnameOfCodingSystem?:string
CodingSystemInfo
A coding-system provenance answer: the system a code CLAIMS, never
validated. claimed is always present and verbatim (never dropped); the
resolved id / name are present only when the claim maps to a registered
Table 0396 entry.
Properties
claimed
readonlyclaimed:string
The coding-system id exactly as it appeared in CWE.3 / CE.3: preserved verbatim (original case and spelling), never altered, never dropped.
id?
readonlyoptionalid?:string
Registered Table 0396 acronym (alias-normalized). Present only when known.
known
readonlyknown:boolean
true when claimed resolved (directly or via alias) to a registered Table 0396 entry.
name?
readonlyoptionalname?:string
Canonical human-readable name. Present only when known.
CompositeValueByKind
Maps each CompositeKind to the typed value encodeComposite
(and setComposite) accept for it. TS also accepts a pre-formatted HL7
timestamp string; NM also accepts a number or a raw numeric string,
both are emitted verbatim (the serializer never re-formats a claimed value).
Properties
CE
readonlyCE:CE
CWE
readonlyCWE:CWE
CX
readonlyCX:CX
HD
readonlyHD:HD
NM
readonlyNM:string|number|NM
PL
readonlyPL:PL
TS
readonlyTS:string|DtmParts
XAD
readonlyXAD:XAD
XCN
readonlyXCN:XCN
XPN
readonlyXPN:XPN
XTN
readonlyXTN:XTN
ConformanceFinding
One typed conformance finding. Carries the FindingCode, a
FindingSeverity, the structural FindingLocus, and a
human-readable message describing the rule that fired.
The message is PHI-safe by construction: it names the locus, the rule,
and (for a value-set miss) the SIZE of the value set, but never the
offending field value.
Example
import type { ConformanceFinding } from "@cosyte/hl7";
const f: ConformanceFinding = {
code: "PROFILE_VALUE_NOT_IN_SET",
severity: "error",
locus: { segment: "PID", field: 8, component: 1 },
message: 'PID-8 component 1 value is not in the profile value set (3 permitted codes).',
};
Properties
code
readonlycode:FindingCode
locus
readonlylocus:FindingLocus
message
readonlymessage:string
severity
readonlyseverity:FindingSeverity
ConformanceProfile
A user-authored, declarative conformance profile. The consumer supplies this; hl7 ships none. It is a bounded subset of the HL7 v2 Message-Profile model: usage / cardinality / length / consumer-supplied value set: with no conditional-predicate language, no bundled code set, and no network binding (all deliberate scope boundaries).
Example
import type { ConformanceProfile } from "@cosyte/hl7";
// A minimal ADT profile the CONSUMER authors: example, NOT an attestation.
const profile: ConformanceProfile = {
name: "example-adt-min",
segments: [
{ segment: "MSH", usage: "R", fields: [{ field: 10, name: "Control ID", usage: "R" }] },
{ segment: "PID", usage: "R", cardinality: { min: 1, max: 1 }, fields: [
{ field: 3, name: "Patient Identifiers", usage: "R", cardinality: { min: 1, max: 1 } },
{ field: 8, name: "Administrative Sex", usage: "RE", valueSet: ["M", "F", "U"] },
] },
{ segment: "ZZZ", usage: "X" },
],
};
Properties
name
readonlyname:string
A name for provenance: echoed into ConformanceResult.profileName.
segments
readonlysegments: readonlySegmentRule[]
The segment rules, evaluated in array order (stable finding order).
ConformanceResult
The result of validateAgainstProfile: the profile's name and the
ordered list of findings.
findings.length === 0 is NOT a conformance attestation. It means every
rule the profile declared was satisfied: nothing about the parts of the
message the profile did not cover, and nothing about clinical correctness.
Read it as "no declared rule was violated," never as "conformant."
Properties
findings
readonlyfindings: readonlyConformanceFinding[]
profileName
readonlyprofileName:string
CustomSegmentDefinition
Shape of a single custom Z-segment declaration used by profile authoring
(defineProfile()). fields maps a caller-visible field NAME to its
1-indexed HL7 position within the segment. Declared here (alongside
Profile) to keep the parser's type module the single source of truth;
src/profiles/define.ts re-exports this type, so consumers can write
import type { CustomSegmentDefinition } from "@cosyte/hl7".
Example
import type { CustomSegmentDefinition } from "@cosyte/hl7";
const zdp: CustomSegmentDefinition = {
fields: { departmentCode: 3, departmentName: 4 },
};
Properties
fields
readonlyfields:Readonly<Record<string,number>>
CWE
HL7 v2 Coded with Exceptions (CWE): coded element per HL7 Chapter 2. All 9 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- identifier (e.g. "GLU")
- text (human-readable, e.g. "Glucose")
- nameOfCodingSystem (e.g. "LN" for LOINC, "SCT" for SNOMED CT)
- alternateIdentifier
- alternateText
- nameOfAlternateCodingSystem
- codingSystemVersionId
- alternateCodingSystemVersionId
- originalText
Components 10+ (present only on v2.7+ senders) are surfaced verbatim, in
order, on extraComponents: never silently truncated.
Example
import type { CWE } from "@cosyte/hl7";
const code: CWE = { identifier: "GLU", text: "Glucose", nameOfCodingSystem: "LN" };
Properties
alternateCodingSystemVersionId?
readonlyoptionalalternateCodingSystemVersionId?:string
alternateIdentifier?
readonlyoptionalalternateIdentifier?:string
alternateText?
readonlyoptionalalternateText?:string
codingSystemVersionId?
readonlyoptionalcodingSystemVersionId?:string
extraComponents?
readonlyoptionalextraComponents?: readonlystring[]
Components beyond the modeled 9 (HL7 component 10 onward), preserved
verbatim and in order for forward-compatibility with v2.7+ senders that
carry the second-alternate triplet or coding-system / value-set OIDs.
OMITTED when the element has no components past the 9th. An absent interior
component is preserved as "" so extraComponents[i] maps to HL7
component 10 + i.
identifier?
readonlyoptionalidentifier?:string
nameOfAlternateCodingSystem?
readonlyoptionalnameOfAlternateCodingSystem?:string
nameOfCodingSystem?
readonlyoptionalnameOfCodingSystem?:string
originalText?
readonlyoptionaloriginalText?:string
text?
readonlyoptionaltext?:string
CX
HL7 v2 Extended Composite ID (CX): structured identifier per HL7 Chapter
2. All 10 components are optional. Fields are OMITTED when the underlying
component is absent (exactOptionalPropertyTypes). assigningAuthority
uses the nested HD shape; assigningFacility is flattened to a plain
string in v1.
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- idNumber
- checkDigit
- checkDigitScheme (ISO 7064, M10, M11, NPI)
- assigningAuthority (nested HD: 3 subcomponents form a HD composite)
- identifierTypeCode (MR, SSN, DL, MC, ...)
- assigningFacility (v1: flattened to string; spec is HD-shaped)
- effectiveDate (raw HL7 TS string)
- expirationDate (raw HL7 TS string)
- assigningJurisdiction (v1: flattened to string)
- assigningAgencyOrDepartment (v1: flattened to string)
Example
import type { CX } from "@cosyte/hl7";
const mrn: CX = {
idNumber: "123456",
assigningAuthority: { namespaceId: "EPIC", universalId: "1.2.840.114350", universalIdType: "ISO" },
identifierTypeCode: "MR",
};
Properties
assigningAgencyOrDepartment?
readonlyoptionalassigningAgencyOrDepartment?:string
assigningAuthority?
readonlyoptionalassigningAuthority?:HD
assigningFacility?
readonlyoptionalassigningFacility?:string
assigningJurisdiction?
readonlyoptionalassigningJurisdiction?:string
checkDigit?
readonlyoptionalcheckDigit?:string
checkDigitScheme?
readonlyoptionalcheckDigitScheme?:string
effectiveDate?
readonlyoptionaleffectiveDate?:string
expirationDate?
readonlyoptionalexpirationDate?:string
identifierTypeCode?
readonlyoptionalidentifierTypeCode?:string
idNumber?
readonlyoptionalidNumber?:string
DefineProfileOptions
Options accepted by defineProfile() (D-02). Mirrors the locked
Profile shape plus the extends input key. Every field except
name is optional.
Example
import { defineProfile, type DefineProfileOptions } from "@cosyte/hl7";
const opts: DefineProfileOptions = {
name: "my-lab",
dateFormats: ["MM/DD/YYYY"],
customSegments: { ZLB: { fields: { noteText: 3 } } },
};
const profile = defineProfile(opts);
Properties
customSegments?
readonlyoptionalcustomSegments?:Readonly<Record<string,CustomSegmentDefinition>>
dateFormats?
readonlyoptionaldateFormats?: readonlystring[]
description?
readonlyoptionaldescription?:string
extends?
name
readonlyname:string
onWarning?
readonlyoptionalonWarning?:OnWarningCallback
Diagnosis
DG1-derived diagnosis entry (HELPERS-06). dateTime is the fidelity TS.
Example
import type { Diagnosis } from "@cosyte/hl7";
const dg: Diagnosis = {
code: { identifier: "E11.9", text: "Type 2 diabetes" },
description: "Type 2 diabetes mellitus without complications",
type: "F",
};
Properties
code?
readonlyoptionalcode?:CWE
DG1-3 diagnosis code.
dateTime?
readonlyoptionaldateTime?:DtmParts
DG1-5 diagnosis date/time as the fidelity TS.
description?
readonlyoptionaldescription?:string
DG1-4 diagnosis description.
type?
readonlyoptionaltype?:string
DG1-6 diagnosis type (A=admitting, W=working, F=final).
DotPath
Parsed representation of a dot-path string. Produced by parsePath,
consumed by resolvePath. All numeric indices are normalized to the
internal convention (segmentIndex = 0-based occurrence, fieldIndex =
1-based HL7 field number, repetitionIndex = 0-based rep, componentIndex
and subcomponentIndex = 1-based HL7 positions).
Example
import { parsePath } from "@cosyte/hl7";
parsePath("OBX[2].5.1");
// { segmentType: "OBX", segmentIndex: 2, fieldIndex: 5,
// repetitionIndex: 0, componentIndex: 1 }
Properties
componentIndex?
readonlyoptionalcomponentIndex?:number
1-based HL7 component position (within a repetition).
fieldIndex
readonlyfieldIndex:number
1-based HL7 field number; maps to RawSegment.fields[fieldIndex].
repetitionIndex?
readonlyoptionalrepetitionIndex?:number
0-based repetition index; defaults to 0 when [N] is omitted.
segmentIndex
readonlysegmentIndex:number
0-based occurrence of this segment type in the message.
segmentType
readonlysegmentType:string
3-char segment identifier (e.g. "PID", "OBX", "ZPI").
subcomponentIndex?
readonlyoptionalsubcomponentIndex?:number
1-based HL7 subcomponent position (within a component).
DtmParts
Parsed HL7 v2 TS/DTM value: the raw string plus its structural parts, with
the stated precision and timezone fidelity preserved. This is the
shape of the TS composite (field.asTs()) and every helper datetime.
The parts are only populated when valid is true. month/day/… are
spec-native (month is 1–12, NOT the JS Date 0–11). offsetMinutes
is present iff hasTimezone is true and is signed minutes east of UTC
(+0500 → 300, -0430 → -270). A missing offset is flagged
(hasTimezone: false), never resolved to UTC: the consumer decides how to
localize with dtmToDate.
Example
import type { DtmParts } from "@cosyte/hl7";
const dob: DtmParts = {
raw: "19880705", valid: true, precision: "day",
year: 1988, month: 7, day: 5, hasTimezone: false,
};
Properties
day?
readonlyoptionalday?:number
Day of month, 1–31.
fractionalSeconds?
readonlyoptionalfractionalSeconds?:string
Fractional-second digits exactly as populated (no leading dot), e.g.
"5" (0.5 s), "0500" (0.05 s). Preserved verbatim: never rounded.
hasTimezone
readonlyhasTimezone:boolean
true iff an explicit +/-ZZZZ offset was present.
hour?
readonlyoptionalhour?:number
Hour, 0–23.
matchedFormat?
readonlyoptionalmatchedFormat?:string
The fallback format that matched (parseDtmCascade only), e.g.
"MM/DD/YYYY" or "ISO-8601". Absent for a strict HL7 DTM parse.
minute?
readonlyoptionalminute?:number
Minute, 0–59.
month?
readonlyoptionalmonth?:number
Month, 1–12 (spec-native, NOT JS 0–11).
offsetMinutes?
readonlyoptionaloffsetMinutes?:number
Signed minutes east of UTC; present iff hasTimezone is true.
precision?
readonlyoptionalprecision?:DtmPrecision
Stated precision; absent when valid is false.
raw
readonlyraw:string
The original HL7 string, exactly as it appeared (already unescaped).
second?
readonlyoptionalsecond?:number
Second, 0–59.
valid
readonlyvalid:boolean
true when raw is a well-formed, in-range HL7 DTM (or a matched
fallback format). false for empty, malformed, or calendar-out-of-range
input: in which case only raw and hasTimezone: false are meaningful.
year?
readonlyoptionalyear?:number
Four-digit year.
DtmToDateOptions
Options controlling how dtmToDate resolves a missing timezone.
Example
import { parseDtm, dtmToDate } from "@cosyte/hl7";
// Treat an offset-less value as UTC (an explicit caller choice):
dtmToDate(parseDtm("20250102"), { assumeOffsetMinutes: 0 });
// ...or as US Eastern standard time (UTC-05:00):
dtmToDate(parseDtm("20250102"), { assumeOffsetMinutes: -300 });
Properties
assumeOffsetMinutes?
readonlyoptionalassumeOffsetMinutes?:number
Offset (signed minutes east of UTC) to assume when the value carries no
timezone. Without it, an offset-less value resolves to undefined,
dtmToDate never guesses a zone. Ignored when the value already has
an offset. Pass 0 to explicitly treat a naive value as UTC.
EncodingCharacters
The HL7 delimiter characters discovered from MSH-1 (field separator) and
MSH-2 (encoding characters). The first four: component, repetition,
escape, subcomponent: are mandatory across all HL7 v2 versions. The fifth,
truncation, is the v2.7+ truncation character (default # per spec
§2.5.5.2): only present when MSH-2 actually carries 5 encoding characters,
so messages that pre-date v2.7 round-trip with a 4-char MSH-2 unchanged.
Example
import type { EncodingCharacters } from "@cosyte/hl7";
const v25: EncodingCharacters = {
field: "|",
component: "^",
repetition: "~",
escape: "\\",
subcomponent: "&",
};
const v27: EncodingCharacters = { ...v25, truncation: "#" };
Properties
component
readonlycomponent:string
escape
readonlyescape:string
field
readonlyfield:string
repetition
readonlyrepetition:string
subcomponent
readonlysubcomponent:string
truncation?
readonlyoptionaltruncation?:string
ExpectedSegmentGroup
One expected segment group for a recognized trigger event. The group is
considered present when at least one of its anchorSegments appears in
the parsed message; its total absence is what signals a truncated or
misrouted message.
Properties
anchorSegments
readonlyanchorSegments: readonlystring[]
The segment name(s) whose presence proves this group is present. Only segments the HL7 v2.5.1 abstract syntax marks Required (R) for the owning trigger event appear here, so a conformant message can never lack all of them.
name
readonlyname:string
Human-readable group label, e.g. "result", "patient", "order".
FieldRule
A rule for one field position within a segment. Every
constraint is optional; a rule with only a field index is a no-op.
Remarks
field is the 1-indexed HL7 position (MSH offset handled internally, exactly
like Segment.field(n): { field: 9 } on MSH targets MSH-9). Length and
value-set checks read the value at component (default 1), so a coded
field's code (CWE.1 / CE.1) is checked by default; set component to
check a different component. Both are applied per present repetition.
Properties
cardinality?
readonlyoptionalcardinality?:Cardinality
Repetition-count constraint for this field.
component?
readonlyoptionalcomponent?:number
1-indexed component whose value the length / valueSet checks read.
Defaults to 1 (the first component: a coded element's code).
field
readonlyfield:number
1-indexed HL7 field position (e.g. 3 for PID-3, 9 for MSH-9).
length?
readonlyoptionallength?:number
Maximum character length of the checked component value (inclusive).
name?
readonlyoptionalname?:string
Optional human label for the field (e.g. "Patient Identifier List").
Structural documentation for the profile author only: findings identify a
field by its PHI-free structural locus (segment + index), never by this
label, so the label is never echoed into a finding message.
severity?
readonlyoptionalseverity?:FindingSeverity
Severity for findings this rule produces. Defaults to "error". A profile
author can downgrade a data-quality rule (e.g. a length or value-set check)
to "warning" or "info" without changing the check itself.
usage?
readonlyoptionalusage?:UsageCode
Usage constraint (see UsageCode). Omitted ⇒ Optional.
valueSet?
readonlyoptionalvalueSet?: readonlystring[]
Consumer-supplied permitted-value list. The checked component value must be a member (case-sensitive exact match). hl7 ships no code set: this is BYO terminology; membership is a literal string check, never a LOINC / SNOMED / ICD / RxNorm lookup and never a network call.
FindingLocus
The structural locus a finding refers to: segment name plus, where applicable, the field position, component, repetition, and segment occurrence. Every member is a name or an index: a locus is inherently PHI-free and never carries a field value.
A finding with no field is segment-level (presence / cardinality
of the segment itself); a finding with a field is field-level. For a
FINDING_CODES.PROFILE_MALFORMED diagnostic (a defect in the profile,
not the message) segment may be the sentinel "(profile)".
Properties
component?
readonlyoptionalcomponent?:number
1-indexed component, when a component-scoped check (length / value-set) fired.
field?
readonlyoptionalfield?:number
1-indexed field position, when the finding is field-level.
occurrence?
readonlyoptionaloccurrence?:number
0-indexed segment occurrence, when the segment type repeats.
repetition?
readonlyoptionalrepetition?:number
0-indexed field repetition, when the finding is repetition-scoped.
segment
readonlysegment:string
Segment name (e.g. "PID"), or "(profile)" for a profile-shape defect.
HD
HL7 v2 Hierarchic Designator (HD): per HL7 Chapter 2 data type. All 3 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- namespaceId: application- or facility-scoped identifier (e.g. "EPIC").
- universalId: globally-unique id (e.g. an OID or UUID string).
- universalIdType: classifier for
universalId(ISO, GUID, UUID, DNS, URI, HL7, HCD, Random, etc.).
Example
import type { HD } from "@cosyte/hl7";
const authority: HD = { namespaceId: "EPIC", universalId: "1.2.840.114350", universalIdType: "ISO" };
Properties
namespaceId?
readonlyoptionalnamespaceId?:string
universalId?
readonlyoptionaluniversalId?:string
universalIdType?
readonlyoptionaluniversalIdType?:string
Hl7ParseWarning
Data shape for every Tier-2 warning emitted by the parser. Warnings are
plain data (distinct from Hl7ParseError, which is a thrown Error
subclass) so they can be safely accumulated into
Hl7Message.warnings and passed to onWarning callbacks.
Example
import type { Hl7ParseWarning } from "@cosyte/hl7";
const w: Hl7ParseWarning = {
code: "UNKNOWN_SEGMENT",
message: "Unknown segment: ZZZ",
position: { segmentIndex: 4 },
};
Properties
code
readonlycode:WarningCode
message
readonlymessage:string
position
readonlyposition:Hl7Position
Hl7Position
Positional context attached to every warning and fatal error. Fields are
1-indexed against the HL7 spec convention (see RawSegment.fields for the
index 0 slot convention). All fields past segmentIndex are optional,
for a top-level fatal like EMPTY_INPUT only segmentIndex: 0 is
populated; for a tokenizer warning deep inside a subcomponent all five
indices may be set.
Remarks
With exactOptionalPropertyTypes: true, do not pass fieldIndex: undefined
explicitly: omit the key instead.
Example
import type { Hl7Position } from "@cosyte/hl7";
const pos: Hl7Position = { segmentIndex: 2, fieldIndex: 5 };
Properties
componentIndex?
readonlyoptionalcomponentIndex?:number
fieldIndex?
readonlyoptionalfieldIndex?:number
repetitionIndex?
readonlyoptionalrepetitionIndex?:number
segmentIndex
readonlysegmentIndex:number
subcomponentIndex?
readonlyoptionalsubcomponentIndex?:number
IdentityEvent
One recognized patient-identity event. For merge / move kinds the
surviving (PID/PV1-sourced) and prior (MRG-sourced) parties are also
exposed directly, with the spec-constant direction: "MRG_TO_PID": the
prior identifiers are the ones being retired in favour of the surviving
ones, never the reverse (HL7 v2 Ch. 3, A18/A39/A40).
parties is the complete role-labelled surface in document order: nothing
present in the message is dropped, including a nonconforming MRG in a
link/add message (surfaced as a prior-role party).
warnings carries the event's own fail-safe warnings (currently
MERGE_MISSING_PRIOR_OR_SURVIVOR); they are scoped to the event and are
NOT appended to Hl7Message.warnings (a read-side helper never mutates
the message).
Example
import { parseHL7 } from "@cosyte/hl7";
for (const ev of parseHL7(raw).identityEvents()) {
if (ev.kind === "merge" && ev.prior && ev.surviving) {
// retire ev.prior.identifiers in favour of ev.surviving.identifiers
} else if (ev.warnings.length > 0) {
// incomplete pair: do NOT apply; route for review
}
}
Properties
direction?
readonlyoptionaldirection?:"MRG_TO_PID"
Spec-constant merge/move direction: the MRG (prior) identifiers merge
INTO the PID (surviving) identifiers. Present on merge / move events
only; never inferred from content.
eventType
readonlyeventType:string
Trigger event code (MSH-9.2, falling back to EVN-1), e.g. "A40".
kind
readonlykind:IdentityEventKind
Classification of the trigger event.
parties
readonlyparties: readonlyIdentityParty[]
Every party in document order, role-labelled: the complete surface.
prior?
readonlyoptionalprior?:IdentityParty
The prior (non-surviving) party (merge/move): ONLY ever sourced from MRG.
surviving?
readonlyoptionalsurviving?:IdentityParty
The surviving party (merge/move): ONLY ever sourced from PID/PV1.
warnings
readonlywarnings: readonlyHl7ParseWarning[]
Event-scoped fail-safe warnings (never PHI-bearing).
IdentityParty
One party (one patient identity) in an identity event, labelled by role with its source segment recorded as provenance. Absent fields are OMITTED (exactOptionalPropertyTypes). All arrays and the object itself are frozen.
Field sources by sourceSegment:
"PID":identifiers= PID-3 repetitions,legacyPatientId= PID-2 (pre-v2.7 only),accountNumber= PID-18,visitNumber= PV1-19 (from the group's PV1, when present),name= PID-5 (first repetition)."MRG":identifiers= MRG-1 repetitions,legacyPatientId= MRG-4 (pre-v2.7 only),accountNumber= MRG-3,visitNumber= MRG-5,name= MRG-7 (first repetition).
Example
import { parseHL7 } from "@cosyte/hl7";
const ev = parseHL7(raw).identityEvents()[0];
if (ev?.prior) {
console.log(ev.prior.role); // "prior"
console.log(ev.prior.sourceSegment); // "MRG"
for (const cx of ev.prior.identifiers) console.log(cx.idNumber);
}
Properties
accountNumber?
readonlyoptionalaccountNumber?:CX
Patient account number (PID-18 / MRG-3).
identifiers
readonlyidentifiers: readonlyCX[]
Identifier list (PID-3 / MRG-1), every non-empty CX repetition.
legacyPatientId?
readonlyoptionallegacyPatientId?:CX
Legacy single patient ID (PID-2 / MRG-4). Backward-compat only; withdrawn as of HL7 v2.7: OMITTED (not read) when MSH-12 declares v2.7 or later.
name?
readonlyoptionalname?:XPN
Patient name (PID-5 / MRG-7, first repetition).
role
readonlyrole:IdentityRole
Role of this party in the event: the safety-critical label.
sourceSegment
readonlysourceSegment:"PID"|"MRG"
Segment this party was sourced from: provenance for the role label.
visitNumber?
readonlyoptionalvisitNumber?:CX
Visit number (PV1-19 for a PID party / MRG-5 for a prior party).
Immunization
A vaccine dose extracted from one RXA (Pharmacy/Treatment Administration)
segment of a VXU^V04 immunization message, with its RXR
(route/site) and OBX (e.g. VFC eligibility / funding source) children grouped
positionally under the RXA, and orderControl from the preceding ORC of the
VXU order group (ORC→RXA→[RXR]→[{OBX}]).
Safety contract. A wrong vaccine, dose, or mis-keyed action code can harm a patient or corrupt an IIS (Immunization Information System) registry, so this view is deliberately conservative:
vaccineCodecarries its own coding-system provenance via the CWE (vaccineCode.nameOfCodingSystem:CVXHL7 Table 0292; live IIS feeds frequently dual-code RXA-5 with an alternate CVX/NDC in CWE.4-6, surfaced asvaccineCode.alternateIdentifier/alternateText/nameOfAlternateCodingSystem). The helper reports the claim; it never validates or looks the code up.actionCode(RXA-21,A/D/U) is surfaced verbatim and never defaulted: mis-keying it corrupts a registry's add/delete/update dedup.doseAmountis strict-Number()parsed; the IIS "unknown dose" sentinel999is surfaced as the number999, never specially coerced.recordOrigin(administered vs historical) is derived only from the well-known NIP001 RXA-9.1 codes and OMITTED otherwise: see ImmunizationRecordOrigin.- Malformed RXA segments never throw: absent fields are omitted keys.
routes and observations are ALWAYS present (possibly empty). Deferred (not
v1): IIS-specific state profile constraints; CVX/MVX validity checks; the 2nd+
repetition of the repeating RXA-15/16/17 lot/expiry/manufacturer fields (only
the first repetition is surfaced).
Example
import type { Immunization } from "@cosyte/hl7";
const imm: Immunization = {
vaccineCode: { identifier: "115", text: "Tdap", nameOfCodingSystem: "CVX" },
doseAmount: 0.5,
doseUnits: { identifier: "mL", nameOfCodingSystem: "UCUM" },
doseUnitsAreUcum: true,
recordOrigin: "administered",
manufacturer: { identifier: "PMC", text: "Sanofi Pasteur", nameOfCodingSystem: "MVX" },
completionStatus: "CP",
actionCode: "A",
routes: [{ route: { identifier: "IM", text: "Intramuscular" } }],
observations: [],
};
Properties
actionCode?
readonlyoptionalactionCode?:string
RXA-21 action code (A=add, D=delete, U=update): preserved verbatim, NEVER defaulted.
administeredDateTime?
readonlyoptionaladministeredDateTime?:DtmParts
RXA-3 date/time start of administration as the fidelity TS.
completionStatus?
readonlyoptionalcompletionStatus?:string
RXA-20 completion status (CP=complete, RE=refused, NA=not administered, PA=partially administered).
doseAmount?
readonlyoptionaldoseAmount?:number
RXA-6 administered dose amount (strict-parsed; never NaN). 999 = IIS "unknown", surfaced as-is.
doseUnits?
readonlyoptionaldoseUnits?:CWE
RXA-7 administered dose units (UCUM).
doseUnitsAreUcum?
readonlyoptionaldoseUnitsAreUcum?:boolean
true iff RXA-7's coding system (CWE.3) is exactly UCUM (HL7 Table 0396)
: i.e. the dose unit is declared UCUM and safe to interpret as computable.
false means a unit IS present but is NOT declared UCUM (surfaced as-is,
never coerced). OMITTED when RXA-7 is absent. A claim check only: UCUM
grammar is not validated here.
expirationDate?
readonlyoptionalexpirationDate?:DtmParts
RXA-16 substance expiration date (first repetition) as the fidelity TS.
informationSource?
readonlyoptionalinformationSource?:CWE
RXA-9 immunization information source (HL7 Table NIP001), preserved verbatim.
lotNumber?
readonlyoptionallotNumber?:string
RXA-15 substance lot number (first repetition).
manufacturer?
readonlyoptionalmanufacturer?:CWE
RXA-17 substance manufacturer (MVX, HL7 Table 0227; first repetition).
observations
readonlyobservations: readonlyObservation[]
OBX children grouped under this RXA (VFC eligibility, funding source, …). Always present (possibly empty).
orderControl?
readonlyoptionalorderControl?:string
ORC-1 order control when an ORC precedes this RXA in the VXU order group.
recordOrigin?
readonlyoptionalrecordOrigin?:ImmunizationRecordOrigin
Derived administered-vs-historical classification from RXA-9.1. See ImmunizationRecordOrigin.
refusalReason?
readonlyoptionalrefusalReason?:CWE
RXA-18 substance/treatment refusal reason (first repetition).
routes
readonlyroutes: readonlyMedicationRoute[]
RXR children grouped under this RXA (Table 0162 route / Table 0163 site). Always present (possibly empty).
vaccineCode?
readonlyoptionalvaccineCode?:CWE
RXA-5 administered vaccine code (CVX, HL7 Table 0292) with provenance + any alternate coding.
Insurance
IN1-derived insurance entry (HELPERS-06) with positional IN2/IN3 presence
flags (D-05 extension). hasIn2 / hasIn3 are ALWAYS present booleans;
callers who need the full IN2/IN3 surface can walk msg.segments("IN2").
Example
import type { Insurance } from "@cosyte/hl7";
const ins: Insurance = {
planId: { identifier: "PLAN1", text: "Aetna PPO" },
policyNumber: "POL123",
groupNumber: "GRP1",
hasIn2: false,
hasIn3: false,
};
Properties
companyId?
readonlyoptionalcompanyId?:CX
IN1-3 insurance company id.
companyName?
readonlyoptionalcompanyName?:string
IN1-4 insurance company name (first repetition, first component).
effectiveDate?
readonlyoptionaleffectiveDate?:DtmParts
IN1-12 plan effective date as the fidelity TS.
expirationDate?
readonlyoptionalexpirationDate?:DtmParts
IN1-13 plan expiration date as the fidelity TS.
groupNumber?
readonlyoptionalgroupNumber?:string
IN1-8 group number.
hasIn2
readonlyhasIn2:boolean
true iff an IN2 segment follows this IN1 before the next IN1.
hasIn3
readonlyhasIn3:boolean
true iff an IN3 segment follows this IN1 before the next IN1.
insuredName?
readonlyoptionalinsuredName?:XPN
IN1-16 insured's name.
planId?
readonlyoptionalplanId?:CWE
IN1-2 insurance plan id.
policyNumber?
readonlyoptionalpolicyNumber?:string
IN1-36 policy number.
KnownCodingSystem
One recognized HL7 Table 0396 coding system: its registered acronym (the value expected in CWE.3 / CE.3), a canonical human-readable name, and the widely-used aliases tolerated for it. Aliases are matched case-insensitively.
Properties
aliases
readonlyaliases: readonlystring[]
Widely-used alternative spellings that claim the same system (matched
case-insensitively), e.g. "LOINC" for LN. The registered id itself
is always recognized and need not be repeated here.
id
readonlyid:string
Registered Table 0396 acronym, e.g. "LN".
name
readonlyname:string
Canonical human-readable name, e.g. "LOINC".
Medication
A medication extracted from one RXO/RXE/RXD/RXA segment, with its RXR
(route) and RXC (component) children grouped
positionally. context records which RX* segment this came from (give
vs dispense vs administered).
Safety contract. A wrong drug, strength, or route can harm a real patient, so this view is deliberately conservative:
giveCodecarries its own coding-system provenance via the CWE (giveCode.nameOfCodingSystem: e.g.RXNRxNorm,NDC). The helper surfaces the claim; it never validates or looks the code up.amount(how much) andstrength(concentration) are SEPARATE fields and are never reconciled: including against any strength a coded drug implies. A disagreement is preserved for the consumer to see.- Malformed RX* segments never throw: absent fields are omitted keys.
routes and components are ALWAYS present (possibly empty). So is
timings: empty when no TQ1 / legacy embedded TQ (RXE-1)
accompanies the medication; the repeat pattern is surfaced verbatim, never
normalized to a schedule. Deferred (not v1): sig/frequency interpretation,
dose-range or interaction checking, pharmacologic resolution of compounds.
Example
import type { Medication } from "@cosyte/hl7";
const med: Medication = {
context: "encoded",
giveCode: { identifier: "1049630", text: "Acetaminophen 325 MG", nameOfCodingSystem: "RXN" },
amount: { minimum: 2, units: { identifier: "TAB" } },
strength: { value: 325, units: { identifier: "mg", nameOfCodingSystem: "UCUM" } },
routes: [{ route: { identifier: "PO", text: "Oral" } }],
components: [],
timings: [{ source: "TQ1", repeatPattern: { code: "Q6H", kind: "parametric", interval: { count: 6, unit: "H" } } }],
};
Properties
amount?
readonlyoptionalamount?:MedicationAmount
Give/dispense/administered amount (+ units). See MedicationAmount.
components
readonlycomponents: readonlyMedicationComponent[]
RXC children grouped under this RX* (compound components). Always present (possibly empty).
context
readonlycontext:MedicationContext
Which RX* segment this medication came from (give/dispense/administered).
dosageForm?
readonlyoptionaldosageForm?:CWE
RXO-5 requested dosage form (order context).
giveCode?
readonlyoptionalgiveCode?:CWE
RXO-1 / RXE-2 / RXD-2 / RXA-5 give/dispense/administered drug code, with provenance.
routes
readonlyroutes: readonlyMedicationRoute[]
RXR children grouped under this RX* (Table 0162 route). Always present (possibly empty).
strength?
readonlyoptionalstrength?:MedicationStrength
RXE-25/26 give strength: ENCODED context only; never reconciled with giveCode.
timings
readonlytimings: readonlyOrderTiming[]
TQ1 / legacy embedded-TQ (RXE-1) timing(s) grouped under this medication. Always present: empty when the medication carries no timing. See OrderTiming.
MedicationAmount
The give / dispense / administered amount of a Medication.
Carries the HL7 min/max amount pair and its units.
- For an order (RXO-2/3) or encoded order (RXE-3/4) the amount is a genuine min..max range: both keys may be present.
- For a dispense (RXD-4) or administration (RXA-6) there is a SINGLE
amount; it is surfaced as
minimumwithmaximumOMITTED. This is a single value, not a range: do not read the absentmaximumas "no upper bound on a range".
minimum/maximum are strict-Number() parsed (undefined, never NaN).
units is the give/dispense/administered units CWE (RXO-4 / RXE-5 / RXD-5 /
RXA-7); check units.nameOfCodingSystem === "UCUM" for computable units.
Example
import type { MedicationAmount } from "@cosyte/hl7";
const amount: MedicationAmount = { minimum: 250, units: { identifier: "mg", nameOfCodingSystem: "UCUM" } };
Properties
maximum?
readonlyoptionalmaximum?:number
RXO-3 / RXE-4 maximum. OMITTED for single-amount (dispense/administration) contexts.
minimum?
readonlyoptionalminimum?:number
RXO-2 / RXE-3 minimum, or the single dispense (RXD-4) / administered (RXA-6) amount.
units?
readonlyoptionalunits?:CWE
RXO-4 / RXE-5 / RXD-5 / RXA-7 give/dispense/administered units.
MedicationComponent
One RXC (Pharmacy/Treatment Component Order) grouped under its parent RX* segment: a component of a compound/IV. Surfaced STRUCTURALLY (the component list as authored), NOT pharmacologically resolved.
Example
import type { MedicationComponent } from "@cosyte/hl7";
const c: MedicationComponent = { type: "B", code: { identifier: "D5W", text: "Dextrose 5%" }, amount: 1000 };
Properties
amount?
readonlyoptionalamount?:number
RXC-3 component amount (strict-parsed; never NaN).
code?
readonlyoptionalcode?:CWE
RXC-2 component code.
type?
readonlyoptionaltype?:string
RXC-1 component type (e.g. "B"=base, "A"=additive: HL7 Table 0166).
units?
readonlyoptionalunits?:CWE
RXC-4 component units.
MedicationRoute
One RXR (Pharmacy/Treatment Route) grouped under its parent RX* segment.
route is HL7 Table 0162 (CWE); site is Table 0163 (CWE).
Provenance travels on the CWE (route.nameOfCodingSystem): a "PO" route is
only safe to act on when you know the system it was coded against.
Example
import type { MedicationRoute } from "@cosyte/hl7";
const r: MedicationRoute = { route: { identifier: "PO", text: "Oral" } };
Properties
route?
readonlyoptionalroute?:CWE
RXR-1 route of administration (HL7 Table 0162).
site?
readonlyoptionalsite?:CWE
RXR-2 administration site (HL7 Table 0163).
MedicationStrength
The give strength of an encoded Medication (RXE-25 value + RXE-26
units). Strength is the concentration of active ingredient (e.g.
"250 mg"), distinct from the give amount (how much is administered, e.g.
"2 tablets"). Only the "encoded" (RXE) context carries strength.
Fail-safe: strength is surfaced exactly as the explicit
RXE-25/26 fields declare it, and is NEVER reconciled against any strength
implied by the give code (e.g. an NDC that encodes "250 mg"). A consumer
that sees both an explicit strength here and a coded drug in giveCode must
treat a disagreement as a real signal: the library does not silently pick a
winner. value is strict-Number() parsed (undefined, never NaN).
Example
import type { MedicationStrength } from "@cosyte/hl7";
const strength: MedicationStrength = { value: 250, units: { identifier: "mg", nameOfCodingSystem: "UCUM" } };
Properties
units?
readonlyoptionalunits?:CWE
RXE-26 give strength units.
value?
readonlyoptionalvalue?:number
RXE-25 give strength numeric value (strict-parsed; never NaN).
MessageStructure
The structure summary for a parsed message: the data behind
Hl7Message.structure. For an unrecognized type, recognized is false,
expectedGroups is empty, and missingGroups is empty (the safety net is
deliberately silent on types it does not model).
Properties
expectedGroups
readonlyexpectedGroups: readonlyStructureGroup[]
Per-expected-group presence verdicts (empty when unrecognized).
messageCode
readonlymessageCode:string
MSH-9.1 message code observed on the message. "" when absent, and
"<withheld>" when recognized is false and the observed value is not
identifier-shaped (MSH-9 can hold a data field on a malformed message).
missingGroups
readonlymissingGroups: readonlystring[]
Names of the expected groups that are entirely absent (the warnings).
recognized
readonlyrecognized:boolean
true when a MESSAGE_STRUCTURE_DEFINITIONS entry matched the type.
triggerEvent
readonlytriggerEvent:string
MSH-9.2 trigger event observed on the message. "" when absent, and
"<withheld>" when recognized is false and the observed value is not
identifier-shaped. Note that on the recognized branch this is echoed
verbatim, which for a definition matching on message code alone (ACK)
means an arbitrary MSH-9.2.
MessageStructureDefinition
The expected structure of one recognized message type, keyed on the
(messageCode, triggerEvent) pair. A definition with an empty triggerEvents
list matches on messageCode alone (used for ACK, which carries no
trigger event in MSH-9.2).
Properties
expectedGroups
readonlyexpectedGroups: readonlyExpectedSegmentGroup[]
The Required (R) segment groups expected for these events.
messageCode
readonlymessageCode:string
MSH-9.1 message code, e.g. "ORU".
triggerEvents
readonlytriggerEvents: readonlystring[]
The MSH-9.2 trigger events this definition applies to, e.g. ["R01"].
An empty list means "match on message code alone" (e.g. ACK).
Meta
MSH-derived message metadata (HELPERS-01). D-03: always defined on
Hl7Message.meta (MSH absence throws NO_MSH_SEGMENT at parse time);
individual fields are optional because vendor-quirky messages routinely
omit pieces of MSH. timestamp is the fidelity TS (precision +
timezone preserved), not an eager UTC-assuming Date.
Example
import type { Meta } from "@cosyte/hl7";
const meta: Meta = {
type: "ADT^A01",
messageCode: "ADT",
triggerEvent: "A01",
controlId: "MSG001",
version: "2.5",
};
console.log(meta.timestamp?.raw, meta.timestamp?.precision);
Properties
controlId?
readonlyoptionalcontrolId?:string
MSH-10 message control ID: unique per message per sender.
messageCode?
readonlyoptionalmessageCode?:string
MSH-9.1 message code (e.g. "ADT", "ORU").
messageStructure?
readonlyoptionalmessageStructure?:string
MSH-9.3 message structure (e.g. "ADT_A01").
processingId?
readonlyoptionalprocessingId?:string
MSH-11.1 processing id (P=production, T=test, D=debug).
receivingApp?
readonlyoptionalreceivingApp?:string
MSH-5.1 receiving application namespace id.
receivingFacility?
readonlyoptionalreceivingFacility?:string
MSH-6.1 receiving facility namespace id.
sendingApp?
readonlyoptionalsendingApp?:string
MSH-3.1 sending application namespace id.
sendingFacility?
readonlyoptionalsendingFacility?:string
MSH-4.1 sending facility namespace id.
timestamp?
readonlyoptionaltimestamp?:DtmParts
MSH-7 message date/time as the fidelity TS.
triggerEvent?
readonlyoptionaltriggerEvent?:string
MSH-9.2 trigger event (e.g. "A01", "R01").
type?
readonlyoptionaltype?:string
MSH-9 full message type string, e.g. "ADT^A01" or "ORU^R01^ORU_R01".
version?
readonlyoptionalversion?:string
MSH-12 HL7 version string (e.g. "2.5", "2.5.1").
NextOfKin
NK1-derived next-of-kin entry (HELPERS-06). Lean subset: callers can
reach for msg.segments("NK1") when they need the full NK1 surface.
Example
import type { NextOfKin } from "@cosyte/hl7";
const nk: NextOfKin = {
name: { familyName: "Doe", givenName: "John" },
relationship: { identifier: "FTH", text: "Father" },
};
Properties
address?
readonlyoptionaladdress?:XAD
NK1-4 address.
contactRole?
readonlyoptionalcontactRole?:CWE
NK1-7 contact role.
name?
readonlyoptionalname?:XPN
NK1-2 next-of-kin name.
phone?
readonlyoptionalphone?:XTN
NK1-5 phone (first repetition).
relationship?
readonlyoptionalrelationship?:CWE
NK1-3 relationship to patient (FTH=father, MTH=mother, SPO=spouse, ...).
NM
HL7 v2 Numeric (NM) composite. Carries both the raw HL7 numeric string
and the parsed JS number. .value is undefined when the raw string
is empty or not fully numeric: NEVER throws.
Unlike most composites in this phase, both raw and value are
ALWAYS-PRESENT keys (not optional): value is explicitly typed as
number | undefined so callers can destructure uniformly.
Example
import type { NM } from "@cosyte/hl7";
const glucose: NM = { raw: "120", value: 120 };
const bad: NM = { raw: "N/A", value: undefined };
Properties
raw
readonlyraw:string
value
readonlyvalue:number|undefined
ObservationBase
Fields shared by every Observation variant, regardless of the OBX-2
value type. Split from the discriminated union to keep the union
declaration readable (D-15 locked field list).
Example
import type { ObservationBase } from "@cosyte/hl7";
const base: ObservationBase = {
setId: "1",
identifier: { identifier: "GLU", text: "Glucose" },
};
Properties
abnormalFlags?
readonlyoptionalabnormalFlags?:string
OBX-8 abnormal flags (e.g. "H", "HH", "L", "LL").
identifier
readonlyidentifier:CWE
OBX-3 observation identifier. Always present (may be {} if OBX-3 absent).
notes?
readonlyoptionalnotes?: readonlystring[]
NTE note lines positionally attached to this OBX: each non-empty NTE-3 (Comment, FT) repetition of every NTE immediately following this observation, HL7-unescaped, in document order. OMITTED when the observation carries no notes. High-PHI-risk clinical narrative.
observedDateTime?
readonlyoptionalobservedDateTime?:DtmParts
OBX-14 date/time of observation as the fidelity TS.
referenceRange?
readonlyoptionalreferenceRange?:string
OBX-7 reference range (e.g. "80-110").
setId?
readonlyoptionalsetId?:string
OBX-1 set id (string: typically sequential "1", "2", ...).
status?
readonlyoptionalstatus?:string
OBX-11 observation result status (e.g. "F"=final, "P"=preliminary).
units?
readonlyoptionalunits?:CWE
OBX-6 units.
unitsAreUcum?
readonlyoptionalunitsAreUcum?:boolean
true iff OBX-6's coding system (CWE.3, "name of coding system") is
exactly UCUM (HL7 Table 0396): i.e. the unit is declared UCUM and is
safe to interpret as a computable unit. false means a unit IS present
but is NOT declared UCUM (e.g. a local code or free text) and is surfaced
as-is, never coerced. OMITTED when OBX-6 is absent. This is a claim check
only: the library does not validate UCUM grammar or check the alternate
coding system (CWE.6).
Order
OBR-derived order (HELPERS-05, D-16) with positionally-grouped OBX children
(D-12). observations is ALWAYS present: empty when no OBX follows this
OBR before the next OBR or end-of-message. timings is ALWAYS present,
empty when the order carries no TQ1 / legacy embedded TQ.
Example
import type { Order } from "@cosyte/hl7";
const order: Order = {
placerOrderNumber: "PLACER1",
fillerOrderNumber: "FILLER1",
universalServiceId: { identifier: "GLU", text: "Glucose" },
orderStatus: "F",
observations: [],
timings: [],
};
Properties
fillerOrderNumber?
readonlyoptionalfillerOrderNumber?:string
OBR-3 filler order number.
notes?
readonlyoptionalnotes?: readonlystring[]
NTE note lines positionally attached to this order: the
ORC-region notes (before the OBR) followed by the OBR-region notes, in
document order. Several ORCs before one OBR all contribute here; nothing is
dropped. OMITTED when the order carries no notes. A note on a trailing or
dangling ORC that never opens an order is surfaced at message level
(msg.notes()), not here: still never dropped. High-PHI-risk clinical
narrative.
observations
readonlyobservations: readonlyObservation[]
OBX children grouped under this OBR (D-12 positional grouping). Always present.
orderControl?
readonlyoptionalorderControl?:string
ORC-1 order control when an ORC precedes this OBR.
orderedBy?
readonlyoptionalorderedBy?:XCN
OBR-16 ordering provider (D-24a XCN).
orderStatus?
readonlyoptionalorderStatus?:string
OBR-25 result status (HL7 Table 0123, e.g. "F" final, "P" preliminary).
placerOrderNumber?
readonlyoptionalplacerOrderNumber?:string
OBR-2 placer order number.
timings
readonlytimings: readonlyOrderTiming[]
TQ1 / legacy embedded-TQ timing(s) grouped under this order. Always present: empty when the order carries no timing. See OrderTiming.
universalServiceId?
readonlyoptionaluniversalServiceId?:CWE
OBR-4 universal service identifier (test code + description).
OrderTiming
The order/medication timing structure: one TQ1 segment (v2.5+) or the legacy embedded TQ in ORC-7 / RXE-1 (pre-v2.5). Attached to Order.timings and Medication.timings.
Safety contract. hl7 surfaces the timing structure; it does not
compute administration schedules, resolve "institution-specified times" to
clock times, or interpret sig. The load-bearing repeatPattern and
totalOccurrences are preserved verbatim (see RepeatPattern);
startDateTime/endDateTime keep the TS precision + timezone
fidelity. A malformed timing never throws: absent pieces are omitted keys.
Example
import type { OrderTiming } from "@cosyte/hl7";
const t: OrderTiming = {
source: "TQ1",
quantity: { value: 1 },
repeatPattern: { code: "Q6H", kind: "parametric", interval: { count: 6, unit: "H" } },
totalOccurrences: 20,
};
Properties
endDateTime?
readonlyoptionalendDateTime?:DtmParts
TQ1-8 / legacy TQ.5 end date/time as the fidelity TS.
explicitTime?
readonlyoptionalexplicitTime?:string
TQ1-4 / legacy TQ.2 interval RI.2 explicit time(s): surfaced verbatim (first repetition/value).
priority?
readonlyoptionalpriority?:CWE
TQ1-9 priority (CWE) / legacy TQ.6 priority (surfaced as a CWE { identifier }).
quantity?
readonlyoptionalquantity?:TimingQuantity
TQ1-2 / legacy TQ.1 service quantity (CQ).
repeatPattern?
readonlyoptionalrepeatPattern?:RepeatPattern
TQ1-3 / legacy TQ.2 interval RI.1 repeat pattern (Table 0335): verbatim. See RepeatPattern.
serviceDuration?
readonlyoptionalserviceDuration?:string
TQ1-6 / legacy TQ.3 service duration: surfaced verbatim.
source
readonlysource:"TQ1"|"legacy"
Which structure this timing was read from: the dedicated TQ1 segment (v2.5+) or the legacy embedded TQ data type in ORC-7 (orders) / RXE-1 (encoded medications, pre-v2.5). The library treats the presence of a TQ1 segment as the v2.5+ signal: the legacy embedded TQ is surfaced only when no TQ1 accompanies the order, so the same timing is never double-counted and a legacy-only timing is never dropped.
startDateTime?
readonlyoptionalstartDateTime?:DtmParts
TQ1-7 / legacy TQ.4 start date/time as the fidelity TS.
totalOccurrences?
readonlyoptionaltotalOccurrences?:number
TQ1-14 / legacy TQ.12 total occurrences (NM): how many times the service
is to be performed (strict-parsed; never NaN). TQ1-14, not TQ1-11
(TQ1-11 is Text Instruction). Load-bearing: losing it drops the total
administered count.
OruObservation
Typed OBX (observation / result) content for buildOru.
Properties
abnormalFlags?
readonlyoptionalabnormalFlags?:string
OBX-8 Abnormal Flags (e.g. "H", "L", "N").
identifier?
readonlyoptionalidentifier?:CWE
OBX-3 Observation Identifier.
observationDateTime?
readonlyoptionalobservationDateTime?:string|DtmParts
OBX-14 Date/Time of the Observation.
observationResultStatus?
readonlyoptionalobservationResultStatus?:string
OBX-11 Observation Result Status (e.g. "F" final, "P" preliminary).
referenceRange?
readonlyoptionalreferenceRange?:string
OBX-7 References Range.
setId?
readonlyoptionalsetId?:string
OBX-1 Set ID.
units?
readonlyoptionalunits?:CWE
OBX-6 Units.
value?
readonlyoptionalvalue?:string
OBX-5 Observation Value (emitted verbatim: the caller owns its formatting).
valueType?
readonlyoptionalvalueType?:string
OBX-2 Value Type (e.g. "NM", "ST", "CE", "TX").
OruOrder
Typed OBR (observation request / order) content for buildOru.
Properties
fillerOrderNumber?
readonlyoptionalfillerOrderNumber?:string
OBR-3 Filler Order Number.
observationDateTime?
readonlyoptionalobservationDateTime?:string|DtmParts
OBR-7 Observation Date/Time.
orderingProvider?
OBR-16 Ordering Provider.
placerOrderNumber?
readonlyoptionalplacerOrderNumber?:string
OBR-2 Placer Order Number.
resultStatus?
readonlyoptionalresultStatus?:string
OBR-25 Result Status (e.g. "F" final, "P" preliminary, "C" corrected).
setId?
readonlyoptionalsetId?:string
OBR-1 Set ID.
universalServiceId?
readonlyoptionaluniversalServiceId?:CWE
OBR-4 Universal Service Identifier.
ParseOptions
Options accepted by parseHL7 to tune lenient/strict behaviour, inject a
profile, and configure optional preprocessing steps. Every field is
optional; parseHL7(raw, {}) is valid and produces the library defaults.
Remarks
With exactOptionalPropertyTypes: true, callers cannot pass
{ strict: undefined }: either omit the key or pass a boolean. The
profile: null form is the explicit opt-out from the process-scoped
default profile (PROF-08 semantics); profile omitted means "use the
default if one is registered".
Example
import { parseHL7, type ParseOptions } from "@cosyte/hl7";
const opts: ParseOptions = {
strict: true,
onWarning: (w) => console.warn(w.code),
dateFormats: ["YYYY-MM-DD"],
};
parseHL7(raw, opts);
Properties
charset?
readonlyoptionalcharset?:string
Override the character set used to decode Buffer input. When supplied
this wins over MSH-18 auto-discovery. When both are supplied and they
disagree (after alias normalization) the parser emits
ENCODING_MISMATCH and honours this override. Ignored for string
input.
Example
import { parseHL7 } from "@cosyte/hl7";
parseHL7(buf, { charset: "ISO-8859-1" });
dateFormats?
readonlyoptionaldateFormats?: readonlystring[]
onWarning?
readonlyoptionalonWarning?:OnWarningCallback
profile?
readonlyoptionalprofile?:Profile|null
strict?
readonlyoptionalstrict?:boolean
stripMllpFraming?
readonlyoptionalstripMllpFraming?:boolean
trimFields?
readonlyoptionaltrimFields?:boolean
Patient
PID-derived patient view (HELPERS-02). msg.patient is undefined (D-04)
when no PID segment exists; this interface describes the shape when
present. identifiers and phoneNumbers are ALWAYS present as arrays
(D-09 / D-20): empty when the underlying field is absent. name is
ALWAYS present (D-19) even if {} when PID-5 is empty.
Example
import type { Patient } from "@cosyte/hl7";
const p: Patient = {
mrn: "MRN123",
identifiers: [{ idNumber: "MRN123", identifierTypeCode: "MR" }],
name: { familyName: "Smith", givenName: "Jane" },
familyName: "Smith",
givenName: "Jane",
fullName: "Jane Smith",
phoneNumbers: [],
};
console.log(p.dateOfBirth?.raw, p.dateOfBirth?.precision);
Properties
address?
readonlyoptionaladdress?:XAD
PID-11 home address parsed as XAD.
dateOfBirth?
readonlyoptionaldateOfBirth?:DtmParts
PID-7 date of birth as the fidelity TS. A day-only DOB keeps
precision: "day": never coerced to a UTC-midnight instant that would
read as the previous day in a negative-offset zone.
ethnicity?
readonlyoptionalethnicity?:CWE
PID-22 ethnic group.
familyName?
readonlyoptionalfamilyName?:string
PID-5.1 flat family name convenience (D-19).
fullName?
readonlyoptionalfullName?:string
Composed Western-order name "Given Middle Family, Suffix" (D-17).
givenName?
readonlyoptionalgivenName?:string
PID-5.2 flat given name convenience (D-19).
identifiers
readonlyidentifiers: readonlyCX[]
Full PID-3 identifier list, each parsed as a CX. Always present (D-09).
language?
readonlyoptionallanguage?:CE
PID-15 primary language.
middleName?
readonlyoptionalmiddleName?:string
PID-5.3 mapped from XPN.secondName (D-19).
mrn?
readonlyoptionalmrn?:string
Medical record number picked via pickMrn (D-07 / D-08).
name
readonlyname:XPN
Full PID-5 parsed name (first repetition). Always present as {} when empty (D-19).
notes?
readonlyoptionalnotes?: readonlystring[]
NTE note lines positionally attached to the (first) PID: notes immediately following the patient's PID segment, HL7-unescaped, in document order. OMITTED when the patient carries no notes. High-PHI-risk clinical narrative.
phoneNumbers
readonlyphoneNumbers: readonlyXTN[]
PID-13 (home) + PID-14 (business) repetitions concatenated. Always present (D-20).
race?
readonlyoptionalrace?:CWE
PID-10 race.
sex?
readonlyoptionalsex?:string
PID-8 administrative sex code.
PL
HL7 v2 Person Location (PL): structured location per HL7 Chapter 2. All
11 v1 components are optional. Fields are OMITTED when the underlying
component is absent (exactOptionalPropertyTypes). facility uses the
nested HD shape; assigningAuthorityForLocation is flattened to a
plain string in v1 (HL7 spec treats it as HD-shaped).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- pointOfCare: e.g. "ICU", "ED"
- room
- bed
- facility: nested HD (3 subcomponents form an HD composite)
- locationStatus: O=Occupied, U=Unoccupied, K=Contaminated, C=Closed, H=Housekeeping, I=Isolated
- personLocationType: C=Clinic, D=Department, H=Home, N=Nursing Unit, O=Office, R=Revenue Location
- building
- floor
- locationDescription: free-text
- comprehensiveLocationId
- assigningAuthorityForLocation (v1: flattened to string)
Example
import type { PL } from "@cosyte/hl7";
const bed: PL = {
pointOfCare: "ICU",
room: "101",
bed: "A",
facility: { namespaceId: "HOSP", universalId: "1.2.3", universalIdType: "UUID" },
};
Properties
assigningAuthorityForLocation?
readonlyoptionalassigningAuthorityForLocation?:string
bed?
readonlyoptionalbed?:string
building?
readonlyoptionalbuilding?:string
comprehensiveLocationId?
readonlyoptionalcomprehensiveLocationId?:string
facility?
readonlyoptionalfacility?:HD
floor?
readonlyoptionalfloor?:string
locationDescription?
readonlyoptionallocationDescription?:string
locationStatus?
readonlyoptionallocationStatus?:string
personLocationType?
readonlyoptionalpersonLocationType?:string
pointOfCare?
readonlyoptionalpointOfCare?:string
room?
readonlyoptionalroom?:string
Profile
Structural placeholder for HL7 profiles. A profile bundles vendor-specific
tolerances, date formats, custom segment definitions, and optional
callbacks. Profiles are built with the defineProfile() factory.
customSegments is narrowed to the locked CustomSegmentDefinition shape,
and Profile carries an optional describe? method
so defineProfile()-produced profiles can be introspected without
consumers needing to narrow away from the Profile type. The describe
method is only populated by defineProfile(): hand-authored Profile
objects may omit it.
Example
import type { Profile } from "@cosyte/hl7";
const epic: Profile = {
name: "epic",
description: "Epic-specific quirks and date formats",
dateFormats: ["YYYYMMDDHHmmss", "YYYYMMDD"],
customSegments: {
ZDP: { fields: { departmentCode: 3, departmentName: 4 } },
},
};
Properties
customSegments?
readonlyoptionalcustomSegments?:Readonly<Record<string,CustomSegmentDefinition>>
dateFormats?
readonlyoptionaldateFormats?: readonlystring[]
describe?
readonlyoptionaldescribe?: () =>string
Returns
string
description?
readonlyoptionaldescription?:string
lineage?
readonlyoptionallineage?: readonlystring[]
name
readonlyname:string
onWarning?
readonlyoptionalonWarning?:OnWarningCallback
RawComponent
A single component inside a repetition: the most deeply nested data
layer in the HL7 positional tree. A component is an ordered list of
subcomponent strings (subcomponent separator & by default).
Example
import type { RawComponent } from "@cosyte/hl7";
const comp: RawComponent = { subcomponents: ["Smith", "John"] };
Properties
rawSubcomponents?
readonlyoptionalrawSubcomponents?: readonly (string|undefined)[]
Internal
Escape-fidelity overlay, positionally aligned with subcomponents:
for each index, the subcomponent's original wire bytes to emit
verbatim instead of re-escaping the decoded form. An entry is present
(non-undefined) only when a subcomponent carried an escape whose decoded
form does not re-escape back to the exact wire bytes: i.e. a recognize-
and-preserve escape (\H\, \Z.., charset/formatting) or a hex escape
(\X41\, or non-canonical hex casing). Delimiter escapes (\F→|) round-
trip through reescape unchanged and get no overlay. The whole field is
absent when no subcomponent needs it (the overwhelming common case), so a
plain message's raw tree is byte-for-byte the shape it was before this
overlay existed.
Consumed only by the serializer ("src/serialize/emit-field.ts"); never read by the value/coercion surface, so it changes no decoded value.
subcomponents
readonlysubcomponents: readonlystring[]
The component's subcomponents, HL7-decoded: the tokenizer expands
escape sequences on parse so consumers read literal values (Smith|Jones,
not Smith\F\Jones). This is the value surface every reader uses
(Field.value, the composite parsers, dot-path, toJSON).
RawField
A positional field inside a segment. Carries its repetitions plus an
isNull discriminant that distinguishes the HL7 explicit null ("", a
two-character literal double quote) from an empty field (no content
between delimiters).
Example
import type { RawField } from "@cosyte/hl7";
const nullField: RawField = { repetitions: [], isNull: true };
const emptyField: RawField = { repetitions: [], isNull: false };
Properties
isNull
readonlyisNull:boolean
repetitions
readonlyrepetitions: readonlyRawRepetition[]
RawRepetition
A single repetition inside a field: HL7 fields may repeat using the
repetition separator (~ by default). Each repetition is an ordered list
of components.
Example
import type { RawRepetition } from "@cosyte/hl7";
const rep: RawRepetition = { components: [{ subcomponents: ["Smith"] }] };
Properties
components
readonlycomponents: readonlyRawComponent[]
RawSegment
A parsed HL7 segment: the top level of the positional tree. The name
is the three-character segment identifier (MSH, PID, ZPI, ...) and
fields is the 1-indexed positional field array (see the JSDoc on
fields for the index 0 slot convention).
Example
import type { RawSegment } from "@cosyte/hl7";
const pid: RawSegment = {
name: "PID",
fields: [
{ repetitions: [], isNull: false },
],
};
Properties
fields
readonlyfields: readonlyRawField[]
Positional fields array using HL7 1-indexed convention for ALL segments.
fields[0]is the segment name / separator placeholder slot (never a data field).fields[N]for N >= 1 is the HL7 N-th field.
Examples:
- MSH:
fields[0]= field-separator char,fields[1]= MSH-2 (encoding chars),fields[2]= MSH-3, ...,fields[11]= MSH-12. - PID:
fields[0]= "PID" name placeholder,fields[1]= PID-1,fields[2]= PID-2, ....
name
readonlyname:string
RenderedText
The normalized display model produced by renderText: a flat plain-text string plus the structured highlight-aware runs, plus an honesty list of the escape sequences that were preserved rather than rendered.
Properties
runs
readonlyruns: readonlyTextRun[]
The structured, highlight-aware form: { text, highlighted } runs in
document order. Empty runs are elided. Use this to preserve emphasis
(bold / reverse-video / etc.) that the flat text drops.
text
readonlytext:string
The full plain-text normalization: formatting commands become
whitespace / line breaks and highlight boundaries are dropped. This is
the string to show a human or feed a downstream .text. Equal to the
concatenation of every runs entry's text.
unrenderedSequences
readonlyunrenderedSequences: readonlystring[]
The escape sequences renderText preserved verbatim instead of
rendering: vendor \Zdddd…\, charset switches \Cxxyy/\Mxxyyzz,
and any malformed / unterminated sequence. Their literal characters ALSO
appear in text/runs (never silently dropped); this list
exists so a consumer can detect that a non-render decision was made and
surface or route those sequences deliberately. Empty when everything
rendered cleanly.
RenderTextOptions
Options for renderText.
Properties
newline?
readonlyoptionalnewline?:string
The string emitted for each line break (\.br\, \.sp, \.ce\, and a
raw CR/LF/CRLF in the input). Defaults to "\n". Set "\r\n" for
Windows-style display, or " " to flatten a note to a single line.
RepeatPattern
An order/medication timing repeat pattern (HL7 Table 0335): the frequency/SIG field (TQ1-3, or the legacy embedded TQ interval RI.1).
Safety contract. code is the decoded field value (HL7 escapes are
unescaped as with every field read) and is never resolved to clock times,
normalized, or mapped to a different frequency: reading Q6H as "daily"
or silently dropping a BID changes the administered dose count, a
transcription-class harm. kind/interval are convenience provenance ONLY;
code is the authoritative value.
Example
import type { RepeatPattern } from "@cosyte/hl7";
const q6h: RepeatPattern = { code: "Q6H", kind: "parametric", interval: { count: 6, unit: "H" } };
const bid: RepeatPattern = { code: "BID", kind: "named" };
Properties
code
readonlycode:string
The Table-0335 repeat-pattern code exactly as authored (e.g. "Q6H", "BID"). Never normalized.
interval?
readonlyoptionalinterval?:object
For a "parametric" Q<integer><unit> template only: the load-bearing
integer and its unit letter (S/M/H/D/W/L, or J for
day-of-week). OMITTED for "named"/"unknown" patterns. Informational,
code remains authoritative.
count
readonlycount:number
unit
readonlyunit:string
kind
readonlykind:RepeatPatternKind
Provenance classification of code: never used to resolve a schedule. See RepeatPatternKind.
SegmentRule
A rule for one segment type. usage constrains whether
the segment must / must not appear; cardinality constrains how many times;
fields are the per-field rules, applied to every occurrence of the
segment.
Properties
cardinality?
readonlyoptionalcardinality?:Cardinality
Occurrence-count constraint for this segment across the message.
fields?
readonlyoptionalfields?: readonlyFieldRule[]
Per-field rules, applied to each occurrence of this segment.
segment
readonlysegment:string
Segment name: 3 chars, [A-Z][A-Z0-9]{2} (standard or Z… segment).
severity?
readonlyoptionalseverity?:FindingSeverity
Severity for the segment-level presence / cardinality findings. Default "error".
usage?
readonlyoptionalusage?:UsageCode
Usage for the segment as a whole. R ⇒ at least one occurrence required;
X ⇒ none permitted; RE / O ⇒ no presence constraint; C / CE ⇒
presence not evaluated (no predicate language). Omitted ⇒ Optional.
SerializedMessage
Snapshot-stable JSON projection of an Hl7Message (SER-03). Every field
is readonly; segment order is preserved; isNull flags are preserved.
Runtime immutability: the top-level object is Object.freezed
(boundary-frozen). Inner arrays are readonly at the TypeScript type
level but mutable at runtime: treat as immutable, do not mutate.
Example
import { parseHL7, type SerializedMessage } from "@cosyte/hl7";
const msg = parseHL7(raw);
const snap: SerializedMessage = msg.toJSON();
console.log(snap.segments[0]?.name); // "MSH"
console.log(snap.warnings.length); // 0 when clean
console.log(JSON.stringify(msg) === JSON.stringify(snap)); // true
console.log(Object.isFrozen(snap)); // true (D-30 boundary freeze)
Properties
encodingCharacters
readonlyencodingCharacters:EncodingCharacters
profile?
readonlyoptionalprofile?:object
lineage
readonlylineage: readonlystring[]
name
readonlyname:string
segments
readonlysegments: readonlyobject[]
warnings
readonlywarnings: readonlyHl7ParseWarning[]
SN
HL7 v2 Structured Numeric (SN) composite. num1 and num2 are
ALWAYS-PRESENT keys (typed number | undefined) so callers can destructure
uniformly, mirroring NM; comparator and separatorOrSuffix are OMITTED
when absent (exactOptionalPropertyTypes).
An absent comparator means the default = relation per HL7 Chapter 2A.
This library surfaces the structure only: it does not evaluate the
inequality, validate the unit, or convert values.
Example
import type { SN } from "@cosyte/hl7";
const gfr: SN = { comparator: ">", num1: 90, num2: undefined }; // >90
const range: SN = { num1: 100, separatorOrSuffix: "-", num2: 200 }; // 100-200
Properties
comparator?
readonlyoptionalcomparator?:string
SN.1 comparator: one of > < >= <= = <>. Omitted ⇒ default =.
num1
readonlynum1:number|undefined
SN.2 first numeric value. undefined when absent or non-numeric (never NaN).
num2
readonlynum2:number|undefined
SN.4 second numeric value. undefined when absent or non-numeric (never NaN).
separatorOrSuffix?
readonlyoptionalseparatorOrSuffix?:string
SN.3 separator/suffix: - (range), :// (ratio), + (suffix), .. Omitted when absent.
StructureGroup
The presence verdict for one expected group of a recognized message type.
Properties
anchorSegments
readonlyanchorSegments: readonlystring[]
The anchor segment name(s) whose presence would satisfy this group.
name
readonlyname:string
The group label from its ExpectedSegmentGroup, e.g. "result".
present
readonlypresent:boolean
true when at least one anchor segment is present in the message.
TextRun
One contiguous run of rendered display text, tagged with whether it fell
inside a \H…\N highlight span. Runs preserve emphasis boundaries that
the flat RenderedText.text intentionally drops.
Properties
highlighted
readonlyhighlighted:boolean
true when this run is inside a \H\…\N highlight span.
text
readonlytext:string
The literal display text of this run: escape sentinels already resolved (delimiter/hex decoded, formatting → whitespace/line breaks). Never contains a formatting sentinel; may contain the newline used for breaks.
TimingQuantity
A composite-quantity (CQ) value on an order/medication timing: the TQ1-2
service quantity. value is strict-Number() parsed (undefined,
never NaN); units carries any CQ.2 units. Both keys OMITTED when absent.
Example
import type { TimingQuantity } from "@cosyte/hl7";
const q: TimingQuantity = { value: 1, units: { identifier: "tablet" } };
Properties
units?
readonlyoptionalunits?:CWE
CQ.2 units.
value?
readonlyoptionalvalue?:number
CQ.1 quantity numeric value (strict-parsed; never NaN).
Visit
PV1-derived visit view (HELPERS-03). msg.visit is undefined when no PV1
segment exists; this interface describes the shape when present. D-24a:
doctor fields use XCN (not flat strings). Date/time fields are the
fidelity TS (precision + timezone preserved).
Example
import type { Visit } from "@cosyte/hl7";
const v: Visit = {
patientClass: "I",
location: { pointOfCare: "ICU", room: "101" },
visitNumber: "VISIT001",
};
console.log(v.attendingDoctor?.familyName);
console.log(v.admitDateTime?.raw);
Properties
admitDateTime?
readonlyoptionaladmitDateTime?:DtmParts
PV1-44 admit date/time as the fidelity TS.
attendingDoctor?
readonlyoptionalattendingDoctor?:XCN
PV1-7 attending doctor (D-24a XCN).
dischargeDateTime?
readonlyoptionaldischargeDateTime?:DtmParts
PV1-45 discharge date/time as the fidelity TS.
location?
readonlyoptionallocation?:PL
PV1-3 assigned patient location (ward / room / bed) as PL.
patientClass?
readonlyoptionalpatientClass?:string
PV1-2 patient class ("I"=inpatient, "O"=outpatient, "E"=ER, ...).
referringDoctor?
readonlyoptionalreferringDoctor?:XCN
PV1-8 referring doctor (D-24a XCN).
visitNumber?
readonlyoptionalvisitNumber?:string
PV1-19 visit number.
XAD
HL7 v2 Extended Address (XAD): structured postal address per HL7 Chapter 2. All 12 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- street: street address (house number + street name).
- otherDesignation: apartment number, suite, floor, etc.
- city
- stateOrProvince
- zipOrPostalCode
- country (ISO-3166 3-letter, e.g. "USA", "CAN")
- addressType (H=Home, B=Business, M=Mailing, O=Office, P=Permanent, ...)
- otherGeographicDesignation
- countyParishCode
- censusTract
- addressRepresentationCode
- addressValidityRange
Example
import type { XAD } from "@cosyte/hl7";
const addr: XAD = { street: "123 Main St", city: "Boston", stateOrProvince: "MA" };
Properties
addressRepresentationCode?
readonlyoptionaladdressRepresentationCode?:string
addressType?
readonlyoptionaladdressType?:string
addressValidityRange?
readonlyoptionaladdressValidityRange?:string
censusTract?
readonlyoptionalcensusTract?:string
city?
readonlyoptionalcity?:string
country?
readonlyoptionalcountry?:string
countyParishCode?
readonlyoptionalcountyParishCode?:string
otherDesignation?
readonlyoptionalotherDesignation?:string
otherGeographicDesignation?
readonlyoptionalotherGeographicDesignation?:string
stateOrProvince?
readonlyoptionalstateOrProvince?:string
street?
readonlyoptionalstreet?:string
zipOrPostalCode?
readonlyoptionalzipOrPostalCode?:string
XCN
HL7 v2 Extended Composite ID Number and Name for Persons (XCN): per HL7 Chapter 2.A.88. All 13 v1 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- idNumber: e.g. employee ID, NPI digits, DEA number (CX-1 analogue)
- familyName (XPN-1)
- givenName (XPN-2)
- secondName: second and further given names (XPN-3)
- suffix: Jr., III, etc. (XPN-4)
- prefix: Dr., Mrs., etc. (XPN-5)
- degree: MD, PhD, etc. (XPN-6)
- sourceTable
- assigningAuthority: nested HD (CX-4 analogue)
- nameTypeCode: L=Legal, M=Maiden, N=Nickname, ... (XPN-7)
- identifierCheckDigit
- checkDigitScheme: ISO 7064, M10, M11, NPI
- identifierTypeCode: "NPI", "DN" (DEA number), ... (CX-5 analogue)
Example
import type { XCN } from "@cosyte/hl7";
const orderingProvider: XCN = {
idNumber: "1234567890",
familyName: "Smith",
givenName: "Jane",
identifierTypeCode: "NPI",
};
Properties
assigningAuthority?
readonlyoptionalassigningAuthority?:HD
checkDigitScheme?
readonlyoptionalcheckDigitScheme?:string
degree?
readonlyoptionaldegree?:string
familyName?
readonlyoptionalfamilyName?:string
givenName?
readonlyoptionalgivenName?:string
identifierCheckDigit?
readonlyoptionalidentifierCheckDigit?:string
identifierTypeCode?
readonlyoptionalidentifierTypeCode?:string
idNumber?
readonlyoptionalidNumber?:string
nameTypeCode?
readonlyoptionalnameTypeCode?:string
prefix?
readonlyoptionalprefix?:string
secondName?
readonlyoptionalsecondName?:string
sourceTable?
readonlyoptionalsourceTable?:string
suffix?
readonlyoptionalsuffix?:string
XPN
HL7 v2 Extended Person Name (XPN): structured name per HL7 Chapter 2. All 14 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- familyName
- givenName
- secondName (or "second and further given names")
- suffix (e.g. Jr., III)
- prefix (e.g. Dr., Mrs.)
- degree (e.g. MD, PhD)
- nameTypeCode (L=Legal, M=Maiden, N=Nickname, S=Coded Pseudo-Name, ...)
- nameRepresentationCode
- nameContext (flattened to string in v1: CWE nesting is out of scope)
- nameValidityRange
- nameAssemblyOrder (F=family first, G=given first)
- effectiveDate (raw HL7 TS string: caller may parse via parseDtm)
- expirationDate
- professionalSuffix
Example
import type { XPN } from "@cosyte/hl7";
const name: XPN = { familyName: "Smith", givenName: "Jane", prefix: "Mrs." };
Properties
degree?
readonlyoptionaldegree?:string
effectiveDate?
readonlyoptionaleffectiveDate?:string
expirationDate?
readonlyoptionalexpirationDate?:string
familyName?
readonlyoptionalfamilyName?:string
givenName?
readonlyoptionalgivenName?:string
nameAssemblyOrder?
readonlyoptionalnameAssemblyOrder?:string
nameContext?
readonlyoptionalnameContext?:string
nameRepresentationCode?
readonlyoptionalnameRepresentationCode?:string
nameTypeCode?
readonlyoptionalnameTypeCode?:string
nameValidityRange?
readonlyoptionalnameValidityRange?:string
prefix?
readonlyoptionalprefix?:string
professionalSuffix?
readonlyoptionalprofessionalSuffix?:string
secondName?
readonlyoptionalsecondName?:string
suffix?
readonlyoptionalsuffix?:string
XTN
HL7 v2 Extended Telecommunication Number (XTN): structured telecom per HL7 Chapter 2. All 12 v1 components are optional. Fields are OMITTED when the underlying component is absent (exactOptionalPropertyTypes).
Component positions (HL7 1-indexed; this interface is 0-indexed by key):
- telephoneNumber: formatted or unformatted phone number
- telecommunicationUseCode: PRN=Primary Residence, WPN=Work, NET=Internet, ORN=Other Residence, BPN=Beeper, VHN=Vacation Home, ASN=Answering Service, EMR=Emergency, ...
- telecommunicationEquipmentType: PH=Phone, FX=Fax, MD=Modem, CP=Cellular Phone, BP=Beeper, Internet, X.400, TDD, TTY
- emailAddress
- countryCode (e.g. "+1")
- areaCityCode
- localNumber
- extension
- anyText: free-text note
- extensionPrefix (e.g. "x")
- speedDialCode
- unformattedTelephoneNumber
Example
import type { XTN } from "@cosyte/hl7";
const phone: XTN = {
telephoneNumber: "(555) 555-1234",
telecommunicationUseCode: "WPN",
telecommunicationEquipmentType: "PH",
};
Properties
anyText?
readonlyoptionalanyText?:string
areaCityCode?
readonlyoptionalareaCityCode?:string
countryCode?
readonlyoptionalcountryCode?:string
emailAddress?
readonlyoptionalemailAddress?:string
extension?
readonlyoptionalextension?:string
extensionPrefix?
readonlyoptionalextensionPrefix?:string
localNumber?
readonlyoptionallocalNumber?:string
speedDialCode?
readonlyoptionalspeedDialCode?:string
telecommunicationEquipmentType?
readonlyoptionaltelecommunicationEquipmentType?:string
telecommunicationUseCode?
readonlyoptionaltelecommunicationUseCode?:string
telephoneNumber?
readonlyoptionaltelephoneNumber?:string
unformattedTelephoneNumber?
readonlyoptionalunformattedTelephoneNumber?:string
Type Aliases
AckCode
Acknowledgment code union (HL7 Table 0008). Narrow on this to know whether a
disposition is accept (AA/CA), error (AE/CE), or reject (AR/CR).
AckCondition
AckCondition = typeof
ACK_CONDITIONS[keyof typeofACK_CONDITIONS]
Accept/application acknowledgment condition union (HL7 Table 0155).
AckMode
AckMode =
"original"|"enhanced"
The two HL7 acknowledgment modes. original = both MSH-15 and MSH-16 are absent/null; enhanced = either is present (HL7 v2 Chapter 2 §2.9).
BatchEnvelopeName
BatchEnvelopeName =
"FHS"|"BHS"|"BTS"|"FTS"
The four HL7 batch-protocol envelope segment names (Ch. 2 §2.10.3).
Example
import type { BatchEnvelopeName } from "@cosyte/hl7";
const trailer: BatchEnvelopeName = "BTS";
BatchMessageEntry
BatchMessageEntry = {
message:Hl7Message;ok:true;position:Hl7Position;raw:string; } | {error:Hl7ParseError;ok:false;position:Hl7Position;raw:string; }
One message extracted from a batch stream. Discriminated on ok: a
successful parse carries the Hl7Message (whose own .warnings hold any
per-message Tier-2 deviations); a message that hit one of the four Tier-3
fatal conditions carries the Hl7ParseError instead: isolated, so the
rest of the batch still yields. raw is the message's verbatim source
(re-parseable by parseHL7); position is the message's MSH (or,
for pre-MSH stray content, its first) segment index in the stream.
Example
import { splitBatch } from "@cosyte/hl7";
for (const entry of splitBatch(raw).messages) {
if (entry.ok) handle(entry.message);
else quarantine(entry.raw, entry.error.code);
}
CharsetTreatment
CharsetTreatment =
"decode"|"verbatim"
How the parser treats a resolved MSH-18 character set.
decode: decode the byte stream to text with CharsetResolution.decoder.verbatim: read the raw bytes as a 1:1latin1mapping; do not decode (byte-recoverable for single-byte content: see the module header).
CompositeKind
CompositeKind =
"XPN"|"XAD"|"CX"|"CWE"|"CE"|"XTN"|"PL"|"TS"|"NM"|"HD"|"XCN"
The 11 typed composite kinds this module can encode. Mirrors the read-side
composite set (XPN, XAD, CX, CWE, CE, XTN, PL, TS, NM,
HD, XCN).
DtmPrecision
DtmPrecision =
"year"|"month"|"day"|"hour"|"minute"|"second"|"fraction"
Stated precision of a parsed HL7 DTM value: the number of populated
characters (excluding the timezone offset) determines which level applies.
A value's precision is preserved verbatim: |1970| is "year", never
silently promoted to a full timestamp.
Example
import { parseDtm } from "@cosyte/hl7";
console.log(parseDtm("1970").precision); // "year"
console.log(parseDtm("198807050000").precision); // "minute"
ErrSeverity
ErrSeverity = typeof
ERR_SEVERITIES[keyof typeofERR_SEVERITIES]
Error-severity union (HL7 Table 0516, ERR-4).
FatalCode
FatalCode = typeof
FATAL_CODES[keyof typeofFATAL_CODES]
Discriminant type for Hl7ParseError.code. Narrowing a caught error by
this code lets consumers write exhaustive switch blocks (enabled by the
switch-exhaustiveness-check lint rule) and guarantees a typo-free
comparison against the FATAL_CODES registry.
Example
import type { FatalCode } from "@cosyte/hl7";
function describe(code: FatalCode): string {
switch (code) {
case "EMPTY_INPUT":
return "input was empty";
case "NO_MSH_SEGMENT":
return "missing MSH";
case "MSH_TOO_SHORT":
return "MSH truncated";
case "INVALID_ENCODING_CHARACTERS":
return "bad MSH-1/MSH-2";
}
}
FindingCode
FindingCode = typeof
FINDING_CODES[keyof typeofFINDING_CODES]
Discriminant union of every ConformanceFinding code. Enables
exhaustive switch narrowing (the switch-exhaustiveness-check lint rule).
FindingSeverity
FindingSeverity =
"error"|"warning"|"info"
Severity of a ConformanceFinding. error is a constraint violation;
warning / info are author-downgraded advisories (via a rule's
severity). FINDING_CODES.PROFILE_MALFORMED is always error.
Hl7StreamSource
Hl7StreamSource =
AsyncIterable<string|Buffer|Uint8Array> |Iterable<string|Buffer|Uint8Array>
A chunked source parseStream can consume: a Node Readable, any
async-iterable, or any plain iterable of chunks. Chunks are string
(text stream) or Buffer/Uint8Array (binary stream). A real Node stream in
binary mode yields Buffers; in text mode it yields strings: a source is
expected to be homogeneous (all-text or all-binary), which every real
Readable is. A binary chunk is decoded 1:1 via latin1 (a lossless
byte↔codepoint mapping) so each message's own MSH-18 charset resolution runs
on its original bytes, exactly as splitBatch does.
Example
import { createReadStream } from "node:fs";
import { parseStream } from "@cosyte/hl7";
const src: Hl7StreamSource = createReadStream("feed.hl7");
for await (const entry of parseStream(src)) {
if (entry.ok) handle(entry.message);
else quarantine(entry.raw, entry.error.code);
}
IdentityEventKind
IdentityEventKind =
"merge"|"move"|"link"|"unlink"|"add"|"update"
Classification of a recognized identity trigger event.
merge: A18 / A34 / A35 / A36 / A39 / A40 / A41 / A42 (MRG expected)move: A43 / A44 (MRG expected)link/unlink: A24 / A37 (two PID groups, no MRG)add/update: A28 / A31 (person add/update, no MRG)
Example
import type { IdentityEventKind } from "@cosyte/hl7";
const kind: IdentityEventKind = "merge";
IdentityRole
IdentityRole =
"surviving"|"prior"|"linked"|"subject"
Role of one party in an identity event. surviving / subject / linked
parties are ONLY ever sourced from PID (+ PV1); prior parties are ONLY
ever sourced from MRG. That is the role-labelling invariant.
Example
import type { IdentityRole } from "@cosyte/hl7";
const role: IdentityRole = "surviving";
ImmunizationRecordOrigin
ImmunizationRecordOrigin =
"administered"|"historical"
Whether an Immunization records a dose that was administered by the
reporting system or is historical information sourced from elsewhere,
derived conservatively from RXA-9.1 against HL7 Table NIP001 (Immunization
Information Source, CDC v2.5.1 Immunization Messaging IG):
"administered": RXA-9.1 is exactly"00"(New immunization record)."historical": RXA-9.1 is"01".."08"(any "Historical information …" source).
Fail-safe: this is OMITTED (never guessed) when RXA-9 is absent or carries
a code outside the NIP001 administered/historical set. The raw RXA-9 claim is
always preserved verbatim on Immunization.informationSource, so a consumer
can inspect the original code even when recordOrigin is undefined. The
distinction matters because an IIS de-duplicates a historical report
differently from a dose it believes was administered: guessing corrupts
the registry.
MedicationContext
MedicationContext =
"order"|"encoded"|"dispense"|"administration"
Which pharmacy/treatment segment a Medication was extracted from: the
clinical "phase" of the medication. Each value maps 1:1 to one RX* parent
segment (Ch. 4A):
"order": RXO (Pharmacy/Treatment Order): the originally requested give code/amount/dosage-form, before pharmacy encoding."encoded": RXE (Encoded Order): the pharmacy-encoded give code/amount, plus the give strength (RXE-25/26): the only context that carries a separate strength."dispense": RXD (Dispense): what was actually dispensed."administration": RXA (Administration): what was actually given to the patient.
The context is preserved verbatim and never collapsed: an RDE order that
carries both an RXO request and an RXE encoded line surfaces as TWO
Medication entries with distinct contexts: the helper never reconciles
one against the other.
Observation
Observation =
ObservationBase& {value:number|undefined;valueType:"NM"; } | {value:SN|undefined;valueType:"SN"; } | {value:TS|undefined;valueType:"TS"|"DT"; } | {value:CWE|CE|undefined;valueType:"CWE"|"CE"; } | {value:string|undefined;valueType:string; }
One OBX segment as a typed Observation. value is discriminated by
valueType (OBX-2) per D-13:
"NM"→number | undefined"SN"→SN | undefined(structured numeric: comparator / range / ratio)"TS" | "DT"→TS | undefined(fidelity parts preserved)"CWE" | "CE"→CWE | CE | undefined(full composite per D-14)- other (
"ST","TX","FT","ID","IS","NA", unknown) →string | undefined(decoded, D-23)
D-22: value is undefined when OBX-5 is empty OR malformed for its
declared type (never throws, never NaN). A TS/DT value is always the
TS structure; check its .valid flag rather than expecting a Date.
Example
import type { Observation } from "@cosyte/hl7";
const glucose: Observation = {
setId: "1",
identifier: { identifier: "GLU", text: "Glucose" },
valueType: "NM",
value: 120,
units: { identifier: "mg/dL" },
referenceRange: "80-110",
abnormalFlags: "H",
status: "F",
};
OnWarningCallback
OnWarningCallback = (
warning) =>void
Callback invoked inline each time the parser emits a Tier-2 warning.
Always fires BEFORE the warning is appended to Hl7Message.warnings so
consumers observe warnings in the same order the parser discovered them.
Parameters
warning
Returns
void
Example
import { parseHL7, type OnWarningCallback } from "@cosyte/hl7";
const onWarning: OnWarningCallback = (w) => {
console.warn(w.code, w.message);
};
parseHL7(raw, { onWarning });
OruPatient
OruPatient =
AdtPatient
Typed PID content for buildOru. Reuses AdtPatient: the patient-identification shape is identical across message families.
RepeatPatternKind
RepeatPatternKind =
"parametric"|"named"|"unknown"
How an order/medication RepeatPattern code (HL7 Table 0335) is classified,
provenance only, never used to resolve a schedule. The code
is always authoritative and surfaced verbatim; this flag only tells a
consumer what kind of pattern it is looking at so it can decide whether a
load-bearing integer is present.
"parametric": aQ<integer><unit>template (Q6H,Q30M,Q2D,Q1W,Q3J5) whose integer is load-bearing:Q6H(every 6 hours) is a different dose count fromQ8H. The parsed integer + unit ride on RepeatPattern.interval."named": a recognized fixed Table-0335 mnemonic scheduled at institution-specified times (BID,TID,QID,QOD,QHS,QAM,QPM,QSHIFT,PRN,AC,PC,HS,C). No numeric interval."unknown": anything else (a local code, free text, an unrecognized mnemonic). Surfaced verbatim, never mapped to a frequency.
StreamMessageEntry
StreamMessageEntry = {
message:Hl7Message;ok:true;position:Hl7Position;raw:string;streamWarnings: readonlyHl7ParseWarning[]; } | {error:Hl7ParseError;ok:false;position:Hl7Position;raw:string;streamWarnings: readonlyHl7ParseWarning[]; }
One message yielded by parseStream. Discriminated on ok, mirroring
splitBatch's per-message entry: a successful parse carries the
Hl7Message (whose own .warnings hold per-message Tier-2 deviations); a
message that hit one of the four Tier-3 fatals carries the Hl7ParseError
instead: isolated, so the rest of the stream still yields.
raw is the message's verbatim source (re-parseable by parseHL7);
position.segmentIndex is the message's MSH (or, for stray pre-MSH
content, its first) segment index in the overall stream: the streaming
analogue of splitBatch's message position. streamWarnings holds
stream-level diagnostics that are not per-message parse warnings: today
only unterminatedStreamMessage on a final message that lacked a
terminator. It is kept separate from message.warnings (which stays exactly
what a whole-buffer parseHL7 of the same bytes would produce) and is
empty for every message but a possibly-truncated final one. Both the entry
and streamWarnings are frozen.
Example
import { parseStream, WARNING_CODES } from "@cosyte/hl7";
for await (const entry of parseStream(source)) {
for (const w of entry.streamWarnings) {
if (w.code === WARNING_CODES.UNTERMINATED_STREAM_MESSAGE) {
// the feed may have been cut off mid-message
}
}
}
TS
TS =
DtmParts
HL7 v2 Time Stamp (TS) / Date Time (DTM) composite: the raw HL7 string plus
its parsed parts, preserving the stated precision and timezone
fidelity. This is a DtmParts: valid is false (with no parts)
for unparseable input: NEVER throws (TYPES-04 no-throw guarantee).
There is deliberately no date field: a day-only value coerced to a JS
Date at UTC midnight silently shifts the calendar day in negative-offset
zones. To obtain an absolute instant, call dtmToDate(ts) explicitly (and
supply assumeOffsetMinutes for an offset-less value).
Example
import type { TS } from "@cosyte/hl7";
import { dtmToDate } from "@cosyte/hl7";
const dob: TS = { raw: "19880705", valid: true, precision: "day",
year: 1988, month: 7, day: 5, hasTimezone: false };
console.log(dob.precision); // "day": not a full timestamp
console.log(dtmToDate(dob)); // undefined: refuses to guess the zone
UsageCode
UsageCode =
"R"|"RE"|"C"|"CE"|"O"|"X"
The six HL7 v2 conformance usage codes (HL7 Conformance Methodology, Message Profiles; IHE ITI TF Vol.2 Appendix C). They constrain whether an element must, may, or must not appear:
R: Required. The element SHALL be present (with a value). Absent → FINDING_CODES.PROFILE_REQUIRED_ABSENT.RE: Required but may be Empty. The element is supported and SHALL be sent when the sender has the data; its absence is never a violation (this engine cannot know whether the sender had the data).C: Conditional. Presence depends on a predicate. This bounded engine ships no predicate language (a documented defer: roadmap §5), so aCelement's presence is not evaluated (treated as optional); its length / value-set / cardinality rules still apply when it IS present.CE: Conditional but may be Empty. Same non-evaluation asC.O: Optional. No presence constraint.X: Not supported / not permitted. The element SHALL NOT be present. Present → FINDING_CODES.PROFILE_NOT_PERMITTED.
Example
import type { UsageCode } from "@cosyte/hl7";
const usage: UsageCode = "R";
WarningCode
WarningCode = typeof
WARNING_CODES[keyof typeofWARNING_CODES]
Discriminant type for Hl7ParseWarning.code. Narrowing a warning by this
code lets consumers write exhaustive switch blocks (enabled by the
switch-exhaustiveness-check lint rule) and guarantees a typo-free
comparison against the WARNING_CODES registry.
Example
import type { Hl7ParseWarning, WarningCode } from "@cosyte/hl7";
function describe(w: Hl7ParseWarning): string {
const code: WarningCode = w.code;
switch (code) {
case "MLLP_FRAMING_STRIPPED":
return "stripped MLLP framing";
default:
return `warning: ${code}`;
}
}
Variables
ACK_CODES
constACK_CODES:object
HL7 Table 0008: Acknowledgment code (MSA-1). The two acknowledgment vocabularies:
- original mode:
AAApplication Accept ·AEApplication Error ·ARApplication Reject. - enhanced mode accept-level:
CACommit Accept ·CECommit Error ·CRCommit Reject (the application-level response in enhanced mode reusesAA/AE/AR).
Type Declaration
AA
readonlyAA:"AA"="AA"
AE
readonlyAE:"AE"="AE"
AR
readonlyAR:"AR"="AR"
CA
readonlyCA:"CA"="CA"
CE
readonlyCE:"CE"="CE"
CR
readonlyCR:"CR"="CR"
Example
import { ACK_CODES } from "@cosyte/hl7";
ACK_CODES.AA; // "AA"
ACK_CONDITIONS
constACK_CONDITIONS:object
HL7 Table 0155: Accept/application acknowledgment conditions (MSH-15 /
MSH-16): AL Always · NE Never · ER Error/reject conditions only ·
SU Successful completion only. Exposed read-only for adapters that
surface the inbound sender's stated acknowledgment expectations.
Type Declaration
AL
readonlyAL:"AL"="AL"
ER
readonlyER:"ER"="ER"
NE
readonlyNE:"NE"="NE"
SU
readonlySU:"SU"="SU"
Example
import { ACK_CONDITIONS } from "@cosyte/hl7";
ACK_CONDITIONS.AL; // "AL" (Always)
BUILTIN_DATE_FALLBACKS
constBUILTIN_DATE_FALLBACKS: readonlystring[]
Ordered list of built-in timestamp formats parseDtmCascade falls
back to when neither the strict HL7 DTM match nor any user-supplied format
succeeds. ISO-8601 is tried first (most constrained); MM/DD/YYYY HH:mm:ss
last (it overlaps the date-only form).
Example
import { BUILTIN_DATE_FALLBACKS } from "@cosyte/hl7";
console.log(BUILTIN_DATE_FALLBACKS);
// ["ISO-8601", "YYYY-MM-DD", "MM/DD/YYYY", "MM/DD/YYYY HH:mm:ss"]
DEFAULT_ENCODING_CHARACTERS
constDEFAULT_ENCODING_CHARACTERS:EncodingCharacters
The HL7 default 5-tuple of encoding characters used when a message does
not override them via MSH-1 / MSH-2. Re-used by downstream stages (the
escape map, parseHL7) as a synthetic-message fallback.
Example
import { DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
DEFAULT_ENCODING_CHARACTERS.field; // "|"
ERR_CONDITION_CODE_SYSTEM
constERR_CONDITION_CODE_SYSTEM:"HL70357"="HL70357"
Code-system name emitted in ERR-3.3 for Table 0357 condition codes.
ERR_CONDITION_CODES
constERR_CONDITION_CODES:Readonly<Record<string,string>>
HL7 Table 0357: Message error condition codes (ERR-3.1 → ERR-3.2 text).
Frozen read-only map of code → standard display text. The code system
name emitted in ERR-3.3 is ERR_CONDITION_CODE_SYSTEM.
Codes 104 (value too long) and 105 (table value not found) are v2.7+
additions; the rest are present from v2.5. buildAck emits whatever code it
is told (it never invents a condition) and looks up the display text here.
Example
import { ERR_CONDITION_CODES } from "@cosyte/hl7";
ERR_CONDITION_CODES["101"]; // "Required field missing"
ERR_SEVERITIES
constERR_SEVERITIES:object
HL7 Table 0516: Error severity (ERR-4). A v2.5+ construct (ERR was structured differently in v2.3.1).
Type Declaration
E
readonlyE:"E"="E"
Error.
I
readonlyI:"I"="I"
Information.
W
readonlyW:"W"="W"
Warning.
Example
import { ERR_SEVERITIES } from "@cosyte/hl7";
ERR_SEVERITIES.E; // "E" (Error)
FATAL_CODES
constFATAL_CODES:object
Stable string codes for every Tier-3 fatal the parser may throw. Locked
at four codes: anything else MUST be a Tier-2 warning. Consumers narrow
on err.code to react to specific structural failures.
Type Declaration
EMPTY_INPUT
readonlyEMPTY_INPUT:"EMPTY_INPUT"="EMPTY_INPUT"
INVALID_ENCODING_CHARACTERS
readonlyINVALID_ENCODING_CHARACTERS:"INVALID_ENCODING_CHARACTERS"="INVALID_ENCODING_CHARACTERS"
MSH_TOO_SHORT
readonlyMSH_TOO_SHORT:"MSH_TOO_SHORT"="MSH_TOO_SHORT"
NO_MSH_SEGMENT
readonlyNO_MSH_SEGMENT:"NO_MSH_SEGMENT"="NO_MSH_SEGMENT"
Example
import { parseHL7, FATAL_CODES, Hl7ParseError } from "@cosyte/hl7";
try {
parseHL7("");
} catch (err) {
if (err instanceof Hl7ParseError && err.code === FATAL_CODES.EMPTY_INPUT) {
// handle empty input
}
}
FINDING_CODES
constFINDING_CODES:object
The frozen registry of finding codes. Stable, additive string codes: a
consumer compares finding.code === FINDING_CODES.PROFILE_REQUIRED_ABSENT.
Segment-level vs field-level is disambiguated by whether the finding's
FindingLocus.field is present, not by separate codes.
Type Declaration
PROFILE_CARDINALITY
readonlyPROFILE_CARDINALITY:"PROFILE_CARDINALITY"="PROFILE_CARDINALITY"
A segment-occurrence or field-repetition count is outside its cardinality.
PROFILE_LENGTH
readonlyPROFILE_LENGTH:"PROFILE_LENGTH"="PROFILE_LENGTH"
A checked component value exceeds the declared maximum length.
PROFILE_MALFORMED
readonlyPROFILE_MALFORMED:"PROFILE_MALFORMED"="PROFILE_MALFORMED"
The profile ITSELF is structurally malformed (a diagnostic, not a message finding).
PROFILE_NOT_PERMITTED
readonlyPROFILE_NOT_PERMITTED:"PROFILE_NOT_PERMITTED"="PROFILE_NOT_PERMITTED"
A Not-permitted (X) segment or field is present.
PROFILE_REQUIRED_ABSENT
readonlyPROFILE_REQUIRED_ABSENT:"PROFILE_REQUIRED_ABSENT"="PROFILE_REQUIRED_ABSENT"
A Required (R) segment or field is absent (or present-but-empty).
PROFILE_VALUE_NOT_IN_SET
readonlyPROFILE_VALUE_NOT_IN_SET:"PROFILE_VALUE_NOT_IN_SET"="PROFILE_VALUE_NOT_IN_SET"
A checked component value is not a member of the consumer-supplied value set.
Example
import { validateAgainstProfile, FINDING_CODES } from "@cosyte/hl7";
const { findings } = validateAgainstProfile(msg, profile);
const missing = findings.filter((f) => f.code === FINDING_CODES.PROFILE_REQUIRED_ABSENT);
KNOWN_CODING_SYSTEMS
constKNOWN_CODING_SYSTEMS: readonlyKnownCodingSystem[]
The safety-relevant subset of HL7 Table 0396 this library recognizes for
provenance. Deliberately small and frozen: it is NOT the full Table 0396
registry. Each entry's source is recorded in
docs-content/spec-notes-coding-system.md.
Note on I10: Table 0396 registers I10 as ICD-10 (the WHO base
classification). US v2 feeds frequently send I10 when they mean
ICD-10-CM, but that clinical-modification specificity is the sender's
convention, not what the acronym registers: so this map reports the
registered claim ("ICD-10") and does not silently upgrade it to CM.
Example
import { KNOWN_CODING_SYSTEMS } from "@cosyte/hl7";
console.log(KNOWN_CODING_SYSTEMS.find((s) => s.id === "LN")?.name); // "LOINC"
KNOWN_SEGMENTS
constKNOWN_SEGMENTS:ReadonlySet<string>
Frozen set of every standard HL7 v2 segment name the library recognises.
Consumers parsing any segment whose name is neither in this set nor
declared by the active profile will see UNKNOWN_SEGMENT in
msg.warnings.
Example
import { KNOWN_SEGMENTS } from "@cosyte/hl7";
console.log(KNOWN_SEGMENTS.has("PID")); // true
console.log(KNOWN_SEGMENTS.has("ZPI")); // false
MESSAGE_STRUCTURE_DEFINITIONS
constMESSAGE_STRUCTURE_DEFINITIONS: readonlyMessageStructureDefinition[]
The conservative, Required-only expected-group registry. Each entry's spec
source is recorded in docs-content/spec-notes-structure.md. Deliberately
narrow: it recognizes the common message types' core Required groups
only and is NOT a conformance validator. Frozen (data, not config).
Example
import { MESSAGE_STRUCTURE_DEFINITIONS } from "@cosyte/hl7";
const oru = MESSAGE_STRUCTURE_DEFINITIONS.find((d) => d.messageCode === "ORU");
console.log(oru?.expectedGroups[0]?.name); // "result"
profiles
constprofiles:object
Namespace object exposing the 8 shipped built-in vendor profiles (epic,
cerner, meditech, athena, genericLab, visage, philips, va). Each is authored
via the public defineProfile() API (BIP-01..09).
Type Declaration
athena
readonlyathena:Profile
cerner
readonlycerner:Profile
epic
readonlyepic:Profile
genericLab
readonlygenericLab:Profile
meditech
readonlymeditech:Profile
philips
readonlyphilips:Profile
va
readonlyva:Profile
visage
readonlyvisage:Profile
Example
import { parseHL7, profiles } from "@cosyte/hl7";
const msg = parseHL7(raw, profiles.epic);
console.log(msg.profile?.name); // "epic"
SUPPORTED_DATE_TOKENS
constSUPPORTED_DATE_TOKENS: readonlystring[]
Every date-format token the library's format-string matcher and
defineProfile() D-08 validator recognize. Re-exported so profile authors
can introspect the valid token set. SSSS (fractional seconds) is
recognised by the D-08 validator only.
Example
import { SUPPORTED_DATE_TOKENS } from "@cosyte/hl7";
console.log(SUPPORTED_DATE_TOKENS);
// ["YYYY", "MM", "DD", "HH", "mm", "ss", "SSSS"]
USAGE_CODES
constUSAGE_CODES: readonlyUsageCode[]
The frozen set of valid UsageCodes, for runtime validation and
introspection. USAGE_CODES.R === "R".
Example
import { USAGE_CODES } from "@cosyte/hl7";
USAGE_CODES.includes("R" as const); // true
VERSION
constVERSION:string="0.0.8"
Library version string, synced with package.json#version at build time
by downstream phases. Exported now so consumers (and the type-check
pipeline) have at least one symbol to resolve through the exports map.
Example
import { VERSION } from "@cosyte/hl7";
console.log(VERSION);
WARNING_CODES
constWARNING_CODES:object
Stable string codes for every Tier-2 warning the parser may emit. The
registry is frozen via as const so TypeScript infers the exact string
literal union for WarningCode: there is zero runtime cost and no
magic-string comparisons for consumers.
Type Declaration
ACK_NO_CORRELATION_ID
readonlyACK_NO_CORRELATION_ID:"ACK_NO_CORRELATION_ID"="ACK_NO_CORRELATION_ID"
BATCH_COUNT_MISMATCH
readonlyBATCH_COUNT_MISMATCH:"BATCH_COUNT_MISMATCH"="BATCH_COUNT_MISMATCH"
BATCH_MISSING_TRAILER
readonlyBATCH_MISSING_TRAILER:"BATCH_MISSING_TRAILER"="BATCH_MISSING_TRAILER"
DUPLICATE_REQUIRED_SEGMENT
readonlyDUPLICATE_REQUIRED_SEGMENT:"DUPLICATE_REQUIRED_SEGMENT"="DUPLICATE_REQUIRED_SEGMENT"
ENCODING_MISMATCH
readonlyENCODING_MISMATCH:"ENCODING_MISMATCH"="ENCODING_MISMATCH"
EXTRA_FIELDS
readonlyEXTRA_FIELDS:"EXTRA_FIELDS"="EXTRA_FIELDS"
FIELD_WHITESPACE_TRIMMED
readonlyFIELD_WHITESPACE_TRIMMED:"FIELD_WHITESPACE_TRIMMED"="FIELD_WHITESPACE_TRIMMED"
MERGE_MISSING_PRIOR_OR_SURVIVOR
readonlyMERGE_MISSING_PRIOR_OR_SURVIVOR:"MERGE_MISSING_PRIOR_OR_SURVIVOR"="MERGE_MISSING_PRIOR_OR_SURVIVOR"
MISSING_EXPECTED_GROUP
readonlyMISSING_EXPECTED_GROUP:"MISSING_EXPECTED_GROUP"="MISSING_EXPECTED_GROUP"
MISSING_REQUIRED_FIELD
readonlyMISSING_REQUIRED_FIELD:"MISSING_REQUIRED_FIELD"="MISSING_REQUIRED_FIELD"
MLLP_FRAMING_STRIPPED
readonlyMLLP_FRAMING_STRIPPED:"MLLP_FRAMING_STRIPPED"="MLLP_FRAMING_STRIPPED"
OUT_OF_ORDER_SEGMENT
readonlyOUT_OF_ORDER_SEGMENT:"OUT_OF_ORDER_SEGMENT"="OUT_OF_ORDER_SEGMENT"
SEGMENT_CASE
readonlySEGMENT_CASE:"SEGMENT_CASE"="SEGMENT_CASE"
TIMESTAMP_FALLBACK_FORMAT
readonlyTIMESTAMP_FALLBACK_FORMAT:"TIMESTAMP_FALLBACK_FORMAT"="TIMESTAMP_FALLBACK_FORMAT"
UNKNOWN_CHARSET
readonlyUNKNOWN_CHARSET:"UNKNOWN_CHARSET"="UNKNOWN_CHARSET"
UNKNOWN_ESCAPE_SEQUENCE
readonlyUNKNOWN_ESCAPE_SEQUENCE:"UNKNOWN_ESCAPE_SEQUENCE"="UNKNOWN_ESCAPE_SEQUENCE"
UNKNOWN_SEGMENT
readonlyUNKNOWN_SEGMENT:"UNKNOWN_SEGMENT"="UNKNOWN_SEGMENT"
UNSUPPORTED_CHARSET
readonlyUNSUPPORTED_CHARSET:"UNSUPPORTED_CHARSET"="UNSUPPORTED_CHARSET"
UNTERMINATED_STREAM_MESSAGE
readonlyUNTERMINATED_STREAM_MESSAGE:"UNTERMINATED_STREAM_MESSAGE"="UNTERMINATED_STREAM_MESSAGE"
VERSION_MISMATCH
readonlyVERSION_MISMATCH:"VERSION_MISMATCH"="VERSION_MISMATCH"
Example
import { parseHL7, WARNING_CODES } from "@cosyte/hl7";
const msg = parseHL7(raw);
if (msg.warnings.some((w) => w.code === WARNING_CODES.MLLP_FRAMING_STRIPPED)) {
// handle MLLP-wrapped input
}
Functions
ackNoCorrelationId()
ackNoCorrelationId(
position):Hl7ParseWarning
Build an ACK_NO_CORRELATION_ID warning. Emitted by buildAck,
not by the parser: the inbound message carried no MSH-10 message control ID,
so the generated ACK leaves MSA-2 empty and, when a positive accept was
requested, downgrades it to an error code rather than fabricating an
unverifiable AA/CA. The position references the inbound MSH segment.
The message NEVER echoes a PHI value: only the structural fact.
Parameters
position
Returns
Example
import { buildAck, WARNING_CODES } from "@cosyte/hl7";
const ack = buildAck(inbound, { code: "AA" }); // inbound has no MSH-10
ack.warnings.some((w) => w.code === WARNING_CODES.ACK_NO_CORRELATION_ID); // true
alternateCodingSystemOf()
alternateCodingSystemOf(
coded):CodingSystemInfo|undefined
Provenance of a coded element's alternate coding system (CWE.6 / CE.6).
Returns undefined when the element claims no alternate system. Useful for
dual-coded fields (e.g. a problem carrying both SNOMED CT and ICD-10), where
assuming a single coding system would be unsafe.
Parameters
coded
Returns
CodingSystemInfo | undefined
Example
import { alternateCodingSystemOf } from "@cosyte/hl7";
const alt = alternateCodingSystemOf(dg.code);
if (alt) console.log("also coded in", alt.name ?? alt.claimed);
analyzeMessageStructure()
analyzeMessageStructure(
messageCode,triggerEvent,presentSegmentNames):MessageStructure
Analyze a parsed message's structure against the conservative expected-group
registry. Pure: it takes the message code, trigger event, and the set of
segment names actually present, and returns a MessageStructure summary. It
decides nothing about whether to warn: the caller (parser) emits one
MISSING_EXPECTED_GROUP warning per name in missingGroups.
Parameters
messageCode
string
MSH-9.1 (e.g. "ORU"); empty string if absent.
triggerEvent
string
MSH-9.2 (e.g. "R01"); empty string if absent.
presentSegmentNames
ReadonlySet<string>
the set of segment names present in the message.
Returns
Example
import { analyzeMessageStructure } from "@cosyte/hl7";
const s = analyzeMessageStructure("ORU", "R01", new Set(["MSH", "PID"]));
console.log(s.recognized); // true
console.log(s.missingGroups); // ["result"] (no OBR/OBX)
batchCountMismatch()
batchCountMismatch(
position,unit,declared,actual):Hl7ParseWarning
Build a BATCH_COUNT_MISMATCH warning. Emitted by
splitBatch(): attached to the returned BatchSplitResult.warnings, never
to Hl7Message.warnings: when a declared envelope count does not equal the
count actually split out: a BTS-1 batch message count that differs from
the messages found in that batch, or an FTS-1 file batch count that
differs from the batches found in the file (HL7 v2 Ch. 2 §2.10.3; BTS-1 / FTS-1
per the BTS / FTS segment definitions in §2.15).
The splitter never drops the tail to make the numbers agree: the
mismatch is surfaced and every message is still returned; the caller decides
whether to reject.
The message carries only the declared-vs-actual integers and the unit
(message for BTS-1, batch for FTS-1): NEVER a field value, facility
identifier, or any other content, so no PHI is exposed. position references
the trailer segment (BTS/FTS) that declared the count.
Parameters
position
unit
"message" | "batch"
declared
number
actual
number
Returns
Example
import { batchCountMismatch } from "@cosyte/hl7";
const w = batchCountMismatch({ segmentIndex: 4 }, "message", 3, 2);
batchMissingTrailer()
batchMissingTrailer(
position,header,expectedTrailer):Hl7ParseWarning
Build a BATCH_MISSING_TRAILER warning. Emitted by
splitBatch() (attached to BatchSplitResult.warnings) when an envelope
header opens a scope that is never closed: a BHS batch header with no
matching BTS trailer, or an FHS file header with no matching FTS
trailer (HL7 v2 Ch. 2 §2.10.3: each envelope segment is optional, but a
profile such as an IIS file-submission spec may mandate the full frame).
splitBatch does not enforce such a rule: it splits, warns, and leaves
the accept/reject decision to the caller; the parse never throws for this.
The message carries only the header/trailer segment names: NEVER a field
value, so no PHI is exposed. position references the unmatched header
segment (FHS/BHS).
Parameters
position
header
"FHS" | "BHS"
expectedTrailer
"BTS" | "FTS"
Returns
Example
import { batchMissingTrailer } from "@cosyte/hl7";
const w = batchMissingTrailer({ segmentIndex: 0 }, "BHS", "BTS");
buildAck()
buildAck(
inbound,options):Hl7Message
Build a spec-clean ACK (MSH + MSA [+ ERR…]) responding to inbound.
Behavior:
- MSH: sender/receiver are swapped (inbound MSH-5/6 → ACK MSH-3/4;
inbound MSH-3/4 → ACK MSH-5/6); MSH-7 is the current UTC time; MSH-9 is
ACK(with the inbound trigger event echoed asACK^<trigger>^ACKwhen present); MSH-10 is a freshly generated control id; MSH-11 (processing id) and MSH-12 (version) echo the inbound values. - MSA: MSA-1 =
code; MSA-2 echoes the full inbound MSH-10 field (the raw field structure is carried over whole: a vendor-quirk id likeID^Xis never truncated to its first component). The echo carries the inbound field's escape-fidelity overlay, so an id bearing a hex escape (ID\X41\Q) or a preserved escape (\H\) echoes byte-verbatim, not canonicalized: exactly the bytes the sender put on the wire, which is what MSA-2 correlation compares. The only structural transform is trailing-empty canonicalization (D-02). (A sender that used custom encoding characters is re-delimited spec-cleanly: the overlay carries the sender's raw bytes in the sender's alphabet, so echoing them verbatim under the ACK's default alphabet would corrupt the field's structure and break correlation: the overlay is therefore bypassed and the decoded id is re-escaped under default, which re-parses back to the same control id. Default-delimiter senders, the norm, keep the byte-exact overlay echo.) - ERR: one segment per supplied
AckErrorDetail: ERR-2 location (when given), ERR-3 the Table 0357 condition code as a CWE (code^text^HL70357), ERR-4 the Table 0516 severity.
Fail-safe. If the inbound message has no MSH-10, the
ACK cannot be correlated. buildAck then leaves MSA-2 empty and, if a
positive accept (AA/CA) was requested, downgrades it to the matching
error code (AE/CE): it never fabricates an unverifiable positive ACK.
The returned message carries an ACK_NO_CORRELATION_ID warning. (This is the
inbound-side complement to @cosyte/mllp's "no commit ⇒ never AA".)
Pure aside from the generated control id + timestamp; never throws except on
a programming error (inbound not an Hl7Message, or an unknown code).
Parameters
inbound
options
Returns
Example
import { buildAck, parseHL7 } from "@cosyte/hl7";
const inbound = parseHL7(raw);
const ack = buildAck(inbound, { code: "AA" });
console.log(ack.toString()); // MSH|...\rMSA|AA|<inbound MSH-10>
parseHL7(ack.toString()).meta.type; // "ACK" (round-trips clean)
buildAdt()
buildAdt(
event,init):Hl7Message
Build a spec-clean ADT message for event (the MSH-9.2 trigger, e.g.
"A01", "A04", "A08") from typed inputs.
The result is a real Hl7Message: msg.toString() serialises it,
parseHL7(msg.toString()) round-trips with zero warnings, and
msg.patient / msg.visit read back the values supplied.
Parameters
event
string
the ADT trigger event (MSH-9.2). Required, non-empty.
init
the MSH envelope + typed PID/PV1/EVN content (patient required).
Returns
Throws
TypeError when event is empty or init.patient is absent.
Example
import { buildAdt, parseHL7 } from "@cosyte/hl7";
const msg = buildAdt("A01", {
sendingApp: "CLINIC",
receivingApp: "LAB",
patient: {
identifiers: { idNumber: "MRN001", identifierTypeCode: "MR" },
name: { familyName: "Test", givenName: "Ann" },
birthDateTime: "19880705",
administrativeSex: "F",
},
visit: { patientClass: "I", assignedLocation: { pointOfCare: "ICU", room: "1", bed: "A" } },
});
const round = parseHL7(msg.toString());
round.warnings.length; // 0
round.patient?.familyName; // "Test"
round.structure.missingGroups; // []
buildMessage()
buildMessage(
init):Hl7Message
Construct an outbound Hl7Message from semantic MSH fields (SER-06).
Synthesises a complete MSH RawSegment per D-10/D-11 and hands to
new Hl7Message({...}). Callers chain .addSegment(name, fields)
(mutation method) to append PID, OBX, etc.
Defaults applied when fields are omitted (D-10):
controlId→generateControlId()(D-12)timestamp→formatHl7Timestamp(new Date())(D-13)version→"2.5"processingId→"P"sendingApp/sendingFacility/receivingApp/receivingFacility→ empty
Encoding characters are always DEFAULT_ENCODING_CHARACTERS (D-14); no
option to customise in v1.
Empty string vs. omitted field (W1): at the HL7 wire level, passing
sendingApp: "" and omitting sendingApp produce IDENTICAL output
(both emit as absent: || at the MSH-3 position). If you need to
emit an HL7 explicit null ("", the two-char literal) at a specific
position, build the message first, then use setField:
const msg = buildMessage({ type: "ADT^A01" });
msg.setField("MSH.3", '""'); // sets RawField.isNull = true
// msg.toString() now emits MSH-3 as `""` (2 chars), not as absent.
BuildMessageInit carries the same note on the input shape.
Parameters
init
Returns
Example
import { buildMessage, parseHL7 } from "@cosyte/hl7";
const msg = buildMessage({
type: "ADT^A01",
sendingApp: "CLINIC",
sendingFacility: "MAIN",
receivingApp: "LAB",
receivingFacility: "REF",
}).addSegment("PID", ["", "", "MRN123", "", "Doe^John"]);
// Spec-clean HL7 string round-trips through parseHL7:
const round = parseHL7(msg.toString());
console.log(round.meta.type); // "ADT^A01"
buildOru()
buildOru(
init):Hl7Message
Build a spec-clean ORU^R01 observation-result message from typed inputs.
The result is a real Hl7Message: msg.toString() serialises it,
parseHL7(msg.toString()) round-trips with zero warnings, and
msg.observations() reads back the results supplied.
Parameters
init
the MSH envelope + typed PID/OBR/OBX content. patient and a
non-empty observations list are required.
Returns
Throws
TypeError when patient is absent or observations is empty.
Example
import { buildOru, parseHL7 } from "@cosyte/hl7";
const msg = buildOru({
sendingApp: "LAB",
receivingApp: "EHR",
patient: { identifiers: { idNumber: "MRN001", identifierTypeCode: "MR" },
name: { familyName: "Test", givenName: "Ann" } },
order: { universalServiceId: { identifier: "CBC", text: "Complete Blood Count", nameOfCodingSystem: "L" },
resultStatus: "F" },
observations: [
{ setId: "1", valueType: "NM",
identifier: { identifier: "WBC", text: "White Blood Cells", nameOfCodingSystem: "LN" },
value: "7.2", units: { identifier: "10*3/uL" }, observationResultStatus: "F" },
],
});
const round = parseHL7(msg.toString());
round.warnings.length; // 0
round.observations()[0]?.value; // "7.2"
canonicalCharset()
canonicalCharset(
raw):string
The canonical Table-0211 code for a label, for equality comparison (e.g. the
MSH-18-vs-options.charset ENCODING_MISMATCH check). Synonyms collapse to
one canonical string ("UNICODE UTF-8" and "UTF-8" → "UTF-8"), so a
synonym pair does not raise a false mismatch; an unrecognized label returns
its normalized form so two genuinely different labels still differ.
Parameters
raw
string
Returns
string
Example
import { canonicalCharset } from "@cosyte/hl7";
canonicalCharset("unicode utf-8") === canonicalCharset("UTF-8"); // true
codingSystem()
codingSystem(
id):CodingSystemInfo|undefined
Resolve a raw coding-system id (a CWE.3 / CE.3 "Name of Coding System"
value) to its provenance. Returns undefined when there is no claim to
resolve: id is undefined, empty, or whitespace-only.
Matching is case-insensitive and tolerant of surrounding whitespace, and
normalizes the well-known aliases in KNOWN_CODING_SYSTEMS (e.g.
"LOINC" → LN, "SNOMED" → SCT, "RxNorm" → RXN). An unrecognized
id is returned verbatim with known: false: never guessed.
Parameters
id
string | undefined
Returns
CodingSystemInfo | undefined
Example
import { codingSystem } from "@cosyte/hl7";
codingSystem("LN"); // { claimed: "LN", known: true, id: "LN", name: "LOINC" }
codingSystem("loinc"); // { claimed: "loinc", known: true, id: "LN", name: "LOINC" }
codingSystem("99zL"); // { claimed: "99zL", known: false }
codingSystem(undefined); // undefined
codingSystemOf()
codingSystemOf(
coded):CodingSystemInfo|undefined
Provenance of a coded element's primary coding system (CWE.3 / CE.3).
Returns undefined when the element claims no primary system.
Parameters
coded
Returns
CodingSystemInfo | undefined
Example
import { parseHL7, codingSystemOf } from "@cosyte/hl7";
const msg = parseHL7(raw);
for (const dg of msg.diagnoses()) {
const sys = dg.code && codingSystemOf(dg.code);
console.log(dg.code?.identifier, sys?.name ?? sys?.claimed ?? "(no system)");
}
decodeText()
decodeText(
input,enc?):string
Decode a field's escape-bearing HL7 text to its human value: the five
delimiter escapes (\F\ \S\ \T\ \R\ \E\), the truncation escape (\P),
\.br\ (→ newline), and hex (\Xdddd…) are resolved; presentational
escapes (\H\/\N, formatting, charset, vendor \Z..) are preserved
verbatim so nothing is lost. Never throws.
This is the one-call inverse of encodeText for delimiter-bearing content. It does not interpret formatting/highlight: for a normalized display string use renderText.
Parameters
input
string
the field's escape-bearing text.
enc?
EncodingCharacters = DEFAULT_ENCODING_CHARACTERS
encoding characters; defaults to the HL7 standard |^~\&.
Returns
string
the decoded value string.
Example
import { decodeText } from "@cosyte/hl7";
decodeText("Doe\\S\\John"); // "Doe^John": \S\ → component separator
decodeText("line1\\.br\\line2"); // "line1\nline2"
defineConformanceProfile()
defineConformanceProfile(
profile):ConformanceProfile
The fail-fast authoring gate for a conformance profile: a malformed
profile raises a typed ProfileDefinitionError at build time, before any
validation runs. Runs collectProfileDefects; on any defect, throws a
single ProfileDefinitionError listing every defect. On success,
returns the profile typed as ConformanceProfile.
This is optional: validateAgainstProfile tolerates a raw profile object and never throws: but it lets an author catch a typo when the profile is written rather than when it is run.
Parameters
profile
unknown
Returns
Throws
when the profile is structurally malformed.
Example
import { defineConformanceProfile } from "@cosyte/hl7";
const profile = defineConformanceProfile({
name: "example-adt-min",
segments: [{ segment: "PID", usage: "R" }],
});
// A typo throws at authoring time:
// defineConformanceProfile({ name: "x", segments: [{ segment: "pid" }] });
// → ProfileDefinitionError: segments[0].segment must be a valid segment name…
defineProfile()
defineProfile(
opts):Profile
Build a readonly Profile from a validated options object. Invalid
input throws ProfileDefinitionError with an actionable message
(PROF-02): bad Z-segment names (D-05), malformed date formats (D-08),
unknown top-level keys with typo hints (D-07), and missing/empty
name.
opts.extends accepts a single parent Profile or an array of them.
Parents are merged into the result: lineage is the parents' lineages
followed by this profile's own name, dateFormats and customSegments
are merged, description inherits when not supplied, and onWarning
handlers are composed. With no parent, lineage === [opts.name].
Parameters
opts
Returns
Example
import { defineProfile } from "@cosyte/hl7";
const epic = defineProfile({
name: "epic",
description: "Epic-specific quirks and ADT date formats",
dateFormats: ["MM/DD/YYYY HH:mm:ss", "MM/DD/YYYY"],
customSegments: {
ZDP: { fields: { departmentCode: 3, departmentName: 4 } },
ZRS: { fields: { resultStatus: 1, statusDateTime: 2 } },
},
});
console.log(epic.name); // "epic"
console.log(epic.lineage); // ["epic"]
console.log(epic.describe?.());
detectAckMode()
detectAckMode(
inbound):AckMode
Detect the HL7 acknowledgment mode of an inbound message from MSH-15 (accept acknowledgment type) and MSH-16 (application acknowledgment type), per HL7 v2 Chapter 2 §2.9: original when both are absent/empty; enhanced when either is present. This split is spec-exact (unlike the disposition mapping, which has no single correct model: see the package README).
Parameters
inbound
Returns
Example
import { detectAckMode, parseHL7 } from "@cosyte/hl7";
detectAckMode(parseHL7(raw)); // "original" | "enhanced"
downgradePositiveAck()
downgradePositiveAck(
code):AckCode
Downgrade a positive acknowledgment code to its matching error code,
AA → AE (original mode), CA → CE (enhanced mode). Every other code
passes through unchanged.
This is the single upstream source of truth for the fail-safe downgrade
pair: buildAck applies it when the inbound carries no MSH-10 correlation
id, and @cosyte/mllp's ack-from-hl7 adapter applies it when the inbound
cannot be parsed at all: neither ever fabricates an unverifiable positive
acknowledgment, and neither carries its own copy of the mapping.
Parameters
code
Returns
Example
import { downgradePositiveAck } from "@cosyte/hl7";
downgradePositiveAck("AA"); // "AE"
downgradePositiveAck("CA"); // "CE"
downgradePositiveAck("AR"); // "AR" (unchanged)
dtmToDate()
dtmToDate(
parts,options?):Date|undefined
Materialize an absolute-instant JS Date from DtmParts: only on
explicit caller request. Truncated fields fill to their lowest legal value
(month → January, day → 1, time → 0) for instant construction only; the
value's stated precision still tells the truth.
Timezone resolution is honest:
- has an offset → the exact instant, using that offset;
- no offset +
assumeOffsetMinutessupplied → that offset is applied; - no offset + nothing supplied →
undefined(never a silent UTC guess).
Returns undefined for an invalid value or an unresolvable zone; never
throws.
Parameters
parts
options?
DtmToDateOptions = {}
Returns
Date | undefined
Example
import { parseDtm, dtmToDate } from "@cosyte/hl7";
dtmToDate(parseDtm("20250102153045-0500"))?.toISOString();
// "2025-01-02T20:30:45.000Z": exact, offset-derived
dtmToDate(parseDtm("20250102")); // undefined: refuses to guess the zone
dtmToDate(parseDtm("20250102"), { assumeOffsetMinutes: 0 })?.toISOString();
// "2025-01-02T00:00:00.000Z": caller explicitly assumed UTC
duplicateRequiredSegment()
duplicateRequiredSegment(
position,segmentName):Hl7ParseWarning
Build a DUPLICATE_REQUIRED_SEGMENT warning. Emitted when a segment the
profile marks as singleton appears more than once (e.g. two MSH
segments). The parser keeps both; the warning alerts the consumer to
potential sender bugs.
Parameters
position
segmentName
string
Returns
Example
import { duplicateRequiredSegment } from "@cosyte/hl7";
const w = duplicateRequiredSegment({ segmentIndex: 1 }, "MSH");
encodeCe()
encodeCe(
v):RawField
Encode a CE (coded element, 6 modelled + preserved extraComponents).
Parameters
v
Returns
Example
import { encodeCe } from "@cosyte/hl7";
encodeCe({ identifier: "GLU", text: "Glucose", nameOfCodingSystem: "L" });
encodeComposite()
encodeComposite<
K>(kind,value):RawField
Encode any typed composite into a spec-clean RawField by its
CompositeKind. The single dispatcher setComposite and the typed
builders (buildAdt/buildOru) route through.
Encoding takes no encoding-characters argument on purpose: the field it produces carries the decoded component values, and the actual delimiter-escaping happens later in the serializer against the message's own encoding characters: so a composite is delimiter-independent to encode.
Type Parameters
K
K extends CompositeKind
Parameters
kind
K
value
Returns
Example
import { encodeComposite } from "@cosyte/hl7";
// A hostile family name cannot break framing on emit:
const f = encodeComposite("XPN", { familyName: "Smith^Jr", givenName: "Ann" });
encodeCompositeReps()
encodeCompositeReps<
K>(kind,values):RawField
Encode an array of typed composites into a single repeating RawField: one
HL7 repetition (~-joined on emit) per array element. Used for repeating
fields such as PID-3 (patient identifier list) and PID-11 (addresses). An
empty array yields an absent field.
Type Parameters
K
K extends CompositeKind
Parameters
kind
K
values
readonly CompositeValueByKind[K][]
Returns
Example
import { encodeCompositeReps } from "@cosyte/hl7";
const ids = encodeCompositeReps("CX", [
{ idNumber: "MRN001", identifierTypeCode: "MR" },
{ idNumber: "9990", identifierTypeCode: "SS" },
]);
encodeCwe()
encodeCwe(
v):RawField
Encode a CWE (coded element, 9 modelled + preserved extraComponents).
Parameters
v
Returns
Example
import { encodeCwe } from "@cosyte/hl7";
encodeCwe({ identifier: "GLU", text: "Glucose", nameOfCodingSystem: "LN" });
encodeCx()
encodeCx(
v):RawField
Encode a CX (identifier): component 4 is a nested HD (assigningAuthority).
Parameters
v
Returns
Example
import { encodeCx } from "@cosyte/hl7";
encodeCx({ idNumber: "MRN001", assigningAuthority: { namespaceId: "HOSP" }, identifierTypeCode: "MR" });
encodeHd()
encodeHd(
v):RawField
Encode an HD (hierarchic designator, 3 components) to a spec-clean field.
Parameters
v
Returns
Example
import { encodeHd } from "@cosyte/hl7";
encodeHd({ namespaceId: "EPIC", universalId: "1.2.840", universalIdType: "ISO" });
encodeNm()
encodeNm(
v):RawField
Encode an NM numeric. Accepts the typed NM (its raw string emitted
verbatim, preserving the sender's precision/formatting), a number, or a raw
string. A number is stringified with String(n); the value is never
reconciled or rounded.
Parameters
v
string | number | NM
Returns
Example
import { encodeNm } from "@cosyte/hl7";
encodeNm("120.50"); // precision preserved verbatim
encodePl()
encodePl(
v):RawField
Encode a PL (person location): component 4 is a nested HD (facility).
Parameters
v
Returns
Example
import { encodePl } from "@cosyte/hl7";
encodePl({ pointOfCare: "ICU", room: "101", bed: "A" });
encodeText()
encodeText(
input,enc?):string
Encode-safe direction: escape an arbitrary string so it can be placed in
an HL7 field as data without ever breaking framing. Every reserved character
: the escape char (escaped first, so decoding is unambiguous), the field,
component, subcomponent, and repetition separators, the declared truncation
char, and the framing-critical \n/\r: is replaced by its escape
sequence, so the value cannot inject a delimiter or forge a component /
subcomponent / repetition boundary.
The hard invariant, property-tested over arbitrary strings:
decodeText(encodeText(s, enc), enc) === s, and a message field carrying
encodeText(s) cannot forge a component / subcomponent / repetition
boundary or break framing: the value never escapes its field.
Two caveats are inherent to HL7 field encoding, not to this codec, and apply
to whole-field re-parse (they do not weaken the no-injection guarantee):
the two-character string "" is HL7's explicit-null token, so a field whose
entire value is "" re-parses as null; and the default parser trims field
whitespace, so a value with leading/trailing spaces re-parses trimmed. Encode
such values into a component/subcomponent position, or parse with trimming
off, to preserve them exactly.
Parameters
input
string
the arbitrary string to encode.
enc?
EncodingCharacters = DEFAULT_ENCODING_CHARACTERS
encoding characters; defaults to the HL7 standard |^~\&.
Returns
string
the spec-clean, delimiter-safe field body.
Example
import { encodeText, parseHL7 } from "@cosyte/hl7";
// A value full of delimiters cannot break out of its field:
const hostile = "a|b^c~d\\e&f";
const body = encodeText(hostile); // "a\\F\\b\\S\\c\\R\\d\\E\\e\\T\\f"
const msg = parseHL7(`MSH|^~\\&|A|B|C|D|20260101||ADT^A01|1|P|2.5\rNTE|1||${body}`);
msg.segments("NTE")[0]?.field(3).value === hostile; // true: round-trips exactly
encodeTs()
encodeTs(
v):RawField
Encode a TS/DTM timestamp. Accepts either the typed TS (its raw
string is emitted verbatim: the serializer never re-derives a timestamp
from parts) or a pre-formatted HL7 timestamp string.
Parameters
v
string | DtmParts
Returns
Example
import { encodeTs } from "@cosyte/hl7";
encodeTs("20260721101500");
encodeXad()
encodeXad(
v):RawField
Encode an XAD (12 components) to a spec-clean field.
Parameters
v
Returns
Example
import { encodeXad } from "@cosyte/hl7";
encodeXad({ street: "123 Main St", city: "Boston", stateOrProvince: "MA" });
encodeXcn()
encodeXcn(
v):RawField
Encode an XCN: component 9 is a nested HD (assigningAuthority).
Parameters
v
Returns
Example
import { encodeXcn } from "@cosyte/hl7";
encodeXcn({ idNumber: "1234567890", familyName: "Welby", identifierTypeCode: "NPI" });
encodeXpn()
encodeXpn(
v):RawField
Encode an XPN (14 components) to a spec-clean field.
Parameters
v
Returns
Example
import { encodeXpn } from "@cosyte/hl7";
encodeXpn({ familyName: "Doe", givenName: "Jane", prefix: "Dr" });
encodeXtn()
encodeXtn(
v):RawField
Encode an XTN (telecom, 12 components) to a spec-clean field.
Parameters
v
Returns
Example
import { encodeXtn } from "@cosyte/hl7";
encodeXtn({ telephoneNumber: "555-1234", telecommunicationUseCode: "PRN" });
encodingMismatch()
encodingMismatch(
position,detail):Hl7ParseWarning
Build an ENCODING_MISMATCH warning. Emitted when the MSH-2 encoding
characters declared by the sender do not match what the parser observed
downstream (e.g. the sender declares ^~\& but uses !@#$ as actual
separators in later segments).
Parameters
position
detail
string
Returns
Example
import { encodingMismatch } from "@cosyte/hl7";
const w = encodingMismatch({ segmentIndex: 0 }, "MSH-2 declares ^~\\& but segment used !@#$");
extraFields()
extraFields(
position,segmentName,extraCount):Hl7ParseWarning
Build an EXTRA_FIELDS warning. Emitted when a segment contains more
fields than the profile definition (or HL7 spec) declares: the extras
are preserved on RawSegment.fields but flagged for consumers.
Parameters
position
segmentName
string
extraCount
number
Returns
Example
import { extraFields } from "@cosyte/hl7";
const w = extraFields({ segmentIndex: 4 }, "PID", 3);
fieldWhitespaceTrimmed()
fieldWhitespaceTrimmed(
position,leadingCount,trailingCount):Hl7ParseWarning
Build a FIELD_WHITESPACE_TRIMMED warning. Emitted when the parser trims
leading or trailing whitespace from a field value (the trimFields
option, on by default). The message carries only the leading/trailing
character counts: NEVER the field value itself (before or after
trimming): so no PHI is exposed; the trimmed value is still preserved
verbatim in the parsed output.
Parameters
position
leadingCount
number
trailingCount
number
Returns
Example
import { fieldWhitespaceTrimmed } from "@cosyte/hl7";
const w = fieldWhitespaceTrimmed({ segmentIndex: 1, fieldIndex: 5 }, 2, 1);
formatDtm()
formatDtm(
parts):string
Reconstruct the HL7 DTM string from DtmParts. The inverse of
parseDtm for a strict HL7 parse: formatDtm(parseDtm(s)) === s for
any well-formed s, including the byte-preserving -0000 (which HL7, unlike
RFC 3339, treats as UTC but whose sign we retain for exact round-trip).
Returns the raw string unchanged when parts.valid is false.
Emits exactly the populated precision, no zero-fill, so a year-only value
re-serializes to four characters, never YYYY0101.
Parameters
parts
Returns
string
Example
import { parseDtm, formatDtm } from "@cosyte/hl7";
formatDtm(parseDtm("198807050000")); // "198807050000"
formatDtm(parseDtm("1970")); // "1970"
getDefaultProfile()
getDefaultProfile():
Profile|undefined
Return the current default profile, or undefined if none is
registered. Consistent with msg.profile convention: undefined
rather than null.
Returns
Profile | undefined
Example
import { getDefaultProfile } from "@cosyte/hl7";
const p = getDefaultProfile();
if (p !== undefined) console.log("default profile:", p.name);
interpretAck()
interpretAck(
msg):Acknowledgment
Interpret an Hl7Message as an acknowledgment. Never throws; a message with
no MSA segment yields an all-false, empty-errors view. The result is
deeply frozen.
Parameters
msg
Returns
Example
import { interpretAck, parseHL7 } from "@cosyte/hl7";
const view = interpretAck(parseHL7("MSH|^~\\&|...\rMSA|AA|MSG001"));
view.accepted; // true
view.controlId; // "MSG001"
isPositiveAck()
isPositiveAck(
code):boolean
True iff code is a positive accept (AA/CA). Unknown/absent → false.
Parameters
code
string | undefined
Returns
boolean
Example
isPositiveAck("AA"); // true
isPositiveAck("AE"); // false
mergeMissingPriorOrSurvivor()
mergeMissingPriorOrSurvivor(
position,eventType,missing):Hl7ParseWarning
Build a MERGE_MISSING_PRIOR_OR_SURVIVOR warning. Emitted
by identityEvents() (a read-side helper: it attaches to the returned
IdentityEvent.warnings, never to Hl7Message.warnings) when a merge/move
trigger event (A18/A34/A35/A36/A39/A40/A41/A42/A43/A44) is missing one side
of the spec-mandated MRG (prior) → PID (surviving) pair: or when that side
carries no usable identity field: no MRG segment in the patient group, no
PID for an orphaned MRG, or a PID/MRG whose identifier, account, and visit
fields are all empty or version-gated (a v2.7+ MRG whose only content was
the withdrawn MRG-4 must not read as "nothing to retire"). The helper
surfaces whatever IS present and never
guesses the merge direction: this warning is the signal that the pair is
incomplete.
The message carries only the structural facts (trigger event code + which role is missing): NEVER an identifier, name, or any other field value, so no PHI is exposed (HL7 v2 Ch. 3: the PID carries surviving and the MRG carries non-surviving identity information).
Parameters
position
eventType
string
missing
"prior" | "survivor"
Returns
Example
import { mergeMissingPriorOrSurvivor } from "@cosyte/hl7";
const w = mergeMissingPriorOrSurvivor({ segmentIndex: 1 }, "A40", "prior");
missingExpectedGroup()
missingExpectedGroup(
position,messageType,groupName,anchorSegments):Hl7ParseWarning
Build a MISSING_EXPECTED_GROUP warning. Emitted once per
absent Required segment group when the message's (MSH-9.1, MSH-9.2) type is
one the structure safety net recognizes and an expected group is entirely
missing: e.g. an ORU^R01 carrying no OBR/OBX result group, the
signature of a truncated or misrouted feed. Tier-2 and additive: lenient
parse never throws on it, strict mode may promote it. The message carries
only the structural fact (message type, group name, anchor segment names),
never a field value: the message type is shape-checked before it is
interpolated, and withheld if it does not match. position references MSH-9.
Parameters
position
messageType
string
groupName
string
anchorSegments
readonly string[]
Returns
Example
import { missingExpectedGroup } from "@cosyte/hl7";
const w = missingExpectedGroup(
{ segmentIndex: 0, fieldIndex: 9 },
"ORU^R01",
"result",
["OBR", "OBX"],
);
missingRequiredField()
missingRequiredField(
position,segmentName,fieldIndex):Hl7ParseWarning
Build a MISSING_REQUIRED_FIELD warning. Emitted when a field the active
profile marks as required is empty or missing. Distinct from the
NO_MSH_SEGMENT fatal, which escalates a missing MSH altogether.
Parameters
position
segmentName
string
fieldIndex
number
Returns
Example
import { missingRequiredField } from "@cosyte/hl7";
const w = missingRequiredField({ segmentIndex: 0, fieldIndex: 3 }, "MSH", 3);
mllpFramingStripped()
mllpFramingStripped(
position):Hl7ParseWarning
Build a MLLP_FRAMING_STRIPPED warning. Emitted once per parse when the
preprocessor detects and removes MLLP framing bytes (0x0B / 0x1C /
trailing 0x0D) from the input.
Parameters
position
Returns
Example
import { mllpFramingStripped } from "@cosyte/hl7";
const w = mllpFramingStripped({ segmentIndex: 0 });
outOfOrderSegment()
outOfOrderSegment(
position,segmentName):Hl7ParseWarning
Build an OUT_OF_ORDER_SEGMENT warning. Emitted when a segment appears
outside the order the active profile declares (e.g. EVN appearing
before MSH in a typical ADT message).
Parameters
position
segmentName
string
Returns
Example
import { outOfOrderSegment } from "@cosyte/hl7";
const w = outOfOrderSegment({ segmentIndex: 2 }, "EVN");
parseCe()
parseCe(
rep,enc):CE
Parse an HL7 v2 CE repetition into a structured CE object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics).
Parameters
rep
enc
Returns
Example
import { parseCe, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["GLU"] },
{ subcomponents: ["Glucose"] },
{ subcomponents: ["LN"] },
] };
const ce = parseCe(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(ce.identifier); // "GLU"
parseCwe()
parseCwe(
rep,enc):CWE
Parse an HL7 v2 CWE repetition into a structured CWE object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics).
Parameters
rep
enc
Returns
Example
import { parseCwe, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["GLU"] },
{ subcomponents: ["Glucose"] },
{ subcomponents: ["LN"] },
] };
const cwe = parseCwe(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(cwe.identifier); // "GLU"
parseCx()
parseCx(
rep,enc):CX
Parse an HL7 v2 CX repetition into a structured CX object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics). Component 4
(assigningAuthority) is parsed as a nested HD; see component table in
the CX interface JSDoc for the v1 simplifications on components 6/9/10.
Parameters
rep
enc
Returns
Example
import { parseCx, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["123"] },
{ subcomponents: [""] },
{ subcomponents: [""] },
{ subcomponents: ["EPIC", "1.2.840.114350", "ISO"] },
{ subcomponents: ["MR"] },
] };
const cx = parseCx(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(cx.idNumber); // "123"
console.log(cx.assigningAuthority?.namespaceId); // "EPIC"
parseDtm()
parseDtm(
raw):DtmParts
Parse an HL7 v2 TS/DTM string into typed DtmParts, preserving the
stated precision and timezone fidelity. Never zero-fills a truncation,
never coerces to a Date, and never assumes UTC for a missing offset.
Returns { raw, valid: false, hasTimezone: false } (no parts) for empty,
malformed, or calendar-out-of-range input: never throws. A fractional
component is only accepted at full second precision.
Parameters
raw
string
Returns
Example
import { parseDtm } from "@cosyte/hl7";
parseDtm("1970");
// { raw: "1970", valid: true, precision: "year", year: 1970, hasTimezone: false }
parseDtm("20250102153045.5-0500");
// precision "fraction", fractionalSeconds "5", hasTimezone true, offsetMinutes -300
parseDtm("not-a-date");
// { raw: "not-a-date", valid: false, hasTimezone: false }
parseHd()
parseHd(
rep,enc):HD
Parse an HL7 v2 HD repetition into a structured HD object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics).
Parameters
rep
enc
Returns
Example
import { parseHd, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["EPIC"] },
{ subcomponents: ["1.2.840.114350"] },
{ subcomponents: ["ISO"] },
] };
const hd = parseHd(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(hd.namespaceId); // "EPIC"
console.log(hd.universalIdType); // "ISO"
parseHL7()
Internal
implementation signature; overload signatures above carry the public JSDoc + @example.
Call Signature
parseHL7(
raw):Hl7Message
Parse a raw HL7 v2 message (string or Buffer) into an Hl7Message.
The parser is lenient by default: recoverable deviations from the HL7
spec are reported via msg.warnings and (optionally)
options.onWarning but do not throw. Four unrecoverable structural
errors throw Hl7ParseError: NO_MSH_SEGMENT, MSH_TOO_SHORT,
INVALID_ENCODING_CHARACTERS, EMPTY_INPUT. Opt into strict mode
with { strict: true } to escalate every Tier-2 warning into an
Hl7ParseError.
Parameters
raw
string | Buffer<ArrayBufferLike>
Returns
Example
import { parseHL7, WARNING_CODES } from "@cosyte/hl7";
const msg = parseHL7(
"MSH|^~\\&|APP|FAC|APP|FAC|20250101||ADT^A01|1|P|2.5\rPID|||123",
);
console.log(msg.version); // "2.5"
console.log(msg.warnings.length); // 0
for (const w of msg.warnings) {
if (w.code === WARNING_CODES.MLLP_FRAMING_STRIPPED) {
// handle MLLP-framed sender
}
}
Call Signature
parseHL7(
raw,profile):Hl7Message
Parse a raw HL7 v2 message (string or Buffer) into an Hl7Message.
The parser is lenient by default: recoverable deviations from the HL7
spec are reported via msg.warnings and (optionally)
options.onWarning but do not throw. Four unrecoverable structural
errors throw Hl7ParseError: NO_MSH_SEGMENT, MSH_TOO_SHORT,
INVALID_ENCODING_CHARACTERS, EMPTY_INPUT. Opt into strict mode
with { strict: true } to escalate every Tier-2 warning into an
Hl7ParseError.
Parameters
raw
string | Buffer<ArrayBufferLike>
profile
Returns
Example
import { parseHL7, WARNING_CODES } from "@cosyte/hl7";
const msg = parseHL7(
"MSH|^~\\&|APP|FAC|APP|FAC|20250101||ADT^A01|1|P|2.5\rPID|||123",
);
console.log(msg.version); // "2.5"
console.log(msg.warnings.length); // 0
for (const w of msg.warnings) {
if (w.code === WARNING_CODES.MLLP_FRAMING_STRIPPED) {
// handle MLLP-framed sender
}
}
Call Signature
parseHL7(
raw,options):Hl7Message
Parse a raw HL7 v2 message (string or Buffer) into an Hl7Message.
The parser is lenient by default: recoverable deviations from the HL7
spec are reported via msg.warnings and (optionally)
options.onWarning but do not throw. Four unrecoverable structural
errors throw Hl7ParseError: NO_MSH_SEGMENT, MSH_TOO_SHORT,
INVALID_ENCODING_CHARACTERS, EMPTY_INPUT. Opt into strict mode
with { strict: true } to escalate every Tier-2 warning into an
Hl7ParseError.
Parameters
raw
string | Buffer<ArrayBufferLike>
options
Returns
Example
import { parseHL7, WARNING_CODES } from "@cosyte/hl7";
const msg = parseHL7(
"MSH|^~\\&|APP|FAC|APP|FAC|20250101||ADT^A01|1|P|2.5\rPID|||123",
);
console.log(msg.version); // "2.5"
console.log(msg.warnings.length); // 0
for (const w of msg.warnings) {
if (w.code === WARNING_CODES.MLLP_FRAMING_STRIPPED) {
// handle MLLP-framed sender
}
}
parseNm()
parseNm(
rep,_enc):NM
Parse an HL7 v2 NM repetition into { raw, value }. Uses Number(raw)
for strict numeric parsing: trailing non-numeric characters produce
NaN, normalized to undefined. Empty raw also produces undefined.
Number("") is 0 in JS: which is the wrong answer for an empty HL7
numeric field. The explicit empty-string check below returns
{ raw: "", value: undefined } so empty inputs match missing inputs.
Parameters
rep
_enc
Returns
Example
import { parseNm, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [{ subcomponents: ["120.5"] }] };
const nm = parseNm(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(nm.value); // 120.5
parsePath()
parsePath(
path):DotPath
Parse an HL7 dot-path string into a DotPath descriptor. Accepts shapes
like SEG, SEG[n], SEG.N, SEG.N[r], SEG.N.C, SEG.N.C.S, and all
combinations up to SEG[n].N[r].C.S. Throws TypeError with the offending
path string on any malformed input.
Parameters
path
string
Returns
Example
import { parsePath } from "@cosyte/hl7";
parsePath("PID.5.1"); // { segmentType: "PID", segmentIndex: 0, fieldIndex: 5, componentIndex: 1 }
parsePath("OBX[2].5"); // { segmentType: "OBX", segmentIndex: 2, fieldIndex: 5 }
parsePath("PID.3[1].1"); // { segmentType: "PID", segmentIndex: 0, fieldIndex: 3, repetitionIndex: 1, componentIndex: 1 }
parsePl()
parsePl(
rep,enc):PL
Parse an HL7 v2 PL repetition into a structured PL object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics). Component 4
(facility) is parsed as a nested HD; see component table in the PL
interface JSDoc for the v1 simplifications.
Parameters
rep
enc
Returns
Example
import { parsePl, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["ICU"] },
{ subcomponents: ["101"] },
{ subcomponents: ["A"] },
{ subcomponents: ["HOSP", "1.2.3", "UUID"] },
] };
const pl = parsePl(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(pl.pointOfCare); // "ICU"
console.log(pl.facility?.namespaceId); // "HOSP"
parseSn()
parseSn(
rep,enc):SN|undefined
Parse an HL7 v2 SN repetition into a structured SN, or undefined when the
field carries no usable structured-numeric content (empty, or so malformed
that no comparator, number, or separator can be recovered). Components are
returned verbatim (already decoded by the tokenizer); num1/num2 use strict Number() parsing.
Fail-safe: a non-operator value in the comparator slot (SN.1) is dropped
rather than surfaced as a relation, and a non-numeric SN.2/SN.4 becomes
undefined: the parser never emits a confident wrong comparator or number.
Parameters
rep
enc
Returns
SN | undefined
Example
import { parseSn, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: [">"] },
{ subcomponents: ["90"] },
] };
const sn = parseSn(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(sn?.comparator, sn?.num1); // ">" 90
parseStream()
Internal
implementation signature; overloads above carry the public JSDoc.
Call Signature
parseStream(
source):AsyncGenerator<StreamMessageEntry,void,void>
Incrementally parse a chunked HL7 v2 byte / text stream, yielding one
StreamMessageEntry per MSH-delimited message as its boundary
completes, with O(one-message) memory: the whole stream is never
retained. Demarcates by MSH boundaries inside the optional
[FHS] { [BHS] { MSH… } [BTS] } [FTS] batch frame (HL7 v2 Ch. 2 §2.10.3):
- a message split across chunk boundaries (mid-segment, mid-field, even
mid-
MSH|^~\&) is reassembled correctly: feeding the same bytes in 1-byte chunks vs. one chunk yields identical messages; \r,\r\n, and\nsegment terminators are all tolerated (a\r\nsplit across a chunk boundary is not mistaken for a bare\r);- each message is parsed by the shipped parseHL7 (no second grammar),
okentries carry theHl7Message, a Tier-3 fatal is an isolated failure entry; a malformed message never suppresses later messages; - batch-envelope segments (
FHS/BHS/BTS/FTS) are treated as boundaries and never yielded as messages, soyielded count == MSH count(envelope count reconciliation is splitBatch's job); - a final message with no trailing terminator is still yielded, flagged with a stream-level unterminatedStreamMessage warning: never a throw, the tail is never dropped.
The second argument, when given, is forwarded verbatim to parseHL7 for
each message (profile, strict, charset, dateFormats, …), exactly as
splitBatch forwards it.
Parameters
source
Returns
AsyncGenerator<StreamMessageEntry, void, void>
Example
import { parseStream } from "@cosyte/hl7";
async function* chunks() {
yield "MSH|^~\\&|A|F|B|G|20260101||ADT^A01|1|P|2.5\rPID|||1\r";
yield "MSH|^~\\&|A|F|B|G|20260101||ADT^A01|2|P|2.5\rPID|||2\r";
}
let count = 0;
for await (const entry of parseStream(chunks())) {
if (entry.ok) count += 1; // 2: one per MSH, streamed, released each time
}
Call Signature
parseStream(
source,profile):AsyncGenerator<StreamMessageEntry,void,void>
Incrementally parse a chunked HL7 v2 byte / text stream, yielding one
StreamMessageEntry per MSH-delimited message as its boundary
completes, with O(one-message) memory: the whole stream is never
retained. Demarcates by MSH boundaries inside the optional
[FHS] { [BHS] { MSH… } [BTS] } [FTS] batch frame (HL7 v2 Ch. 2 §2.10.3):
- a message split across chunk boundaries (mid-segment, mid-field, even
mid-
MSH|^~\&) is reassembled correctly: feeding the same bytes in 1-byte chunks vs. one chunk yields identical messages; \r,\r\n, and\nsegment terminators are all tolerated (a\r\nsplit across a chunk boundary is not mistaken for a bare\r);- each message is parsed by the shipped parseHL7 (no second grammar),
okentries carry theHl7Message, a Tier-3 fatal is an isolated failure entry; a malformed message never suppresses later messages; - batch-envelope segments (
FHS/BHS/BTS/FTS) are treated as boundaries and never yielded as messages, soyielded count == MSH count(envelope count reconciliation is splitBatch's job); - a final message with no trailing terminator is still yielded, flagged with a stream-level unterminatedStreamMessage warning: never a throw, the tail is never dropped.
The second argument, when given, is forwarded verbatim to parseHL7 for
each message (profile, strict, charset, dateFormats, …), exactly as
splitBatch forwards it.
Parameters
source
profile
Returns
AsyncGenerator<StreamMessageEntry, void, void>
Example
import { parseStream } from "@cosyte/hl7";
async function* chunks() {
yield "MSH|^~\\&|A|F|B|G|20260101||ADT^A01|1|P|2.5\rPID|||1\r";
yield "MSH|^~\\&|A|F|B|G|20260101||ADT^A01|2|P|2.5\rPID|||2\r";
}
let count = 0;
for await (const entry of parseStream(chunks())) {
if (entry.ok) count += 1; // 2: one per MSH, streamed, released each time
}
Call Signature
parseStream(
source,options):AsyncGenerator<StreamMessageEntry,void,void>
Incrementally parse a chunked HL7 v2 byte / text stream, yielding one
StreamMessageEntry per MSH-delimited message as its boundary
completes, with O(one-message) memory: the whole stream is never
retained. Demarcates by MSH boundaries inside the optional
[FHS] { [BHS] { MSH… } [BTS] } [FTS] batch frame (HL7 v2 Ch. 2 §2.10.3):
- a message split across chunk boundaries (mid-segment, mid-field, even
mid-
MSH|^~\&) is reassembled correctly: feeding the same bytes in 1-byte chunks vs. one chunk yields identical messages; \r,\r\n, and\nsegment terminators are all tolerated (a\r\nsplit across a chunk boundary is not mistaken for a bare\r);- each message is parsed by the shipped parseHL7 (no second grammar),
okentries carry theHl7Message, a Tier-3 fatal is an isolated failure entry; a malformed message never suppresses later messages; - batch-envelope segments (
FHS/BHS/BTS/FTS) are treated as boundaries and never yielded as messages, soyielded count == MSH count(envelope count reconciliation is splitBatch's job); - a final message with no trailing terminator is still yielded, flagged with a stream-level unterminatedStreamMessage warning: never a throw, the tail is never dropped.
The second argument, when given, is forwarded verbatim to parseHL7 for
each message (profile, strict, charset, dateFormats, …), exactly as
splitBatch forwards it.
Parameters
source
options
Returns
AsyncGenerator<StreamMessageEntry, void, void>
Example
import { parseStream } from "@cosyte/hl7";
async function* chunks() {
yield "MSH|^~\\&|A|F|B|G|20260101||ADT^A01|1|P|2.5\rPID|||1\r";
yield "MSH|^~\\&|A|F|B|G|20260101||ADT^A01|2|P|2.5\rPID|||2\r";
}
let count = 0;
for await (const entry of parseStream(chunks())) {
if (entry.ok) count += 1; // 2: one per MSH, streamed, released each time
}
parseTs()
parseTs(
rep,_enc):DtmParts
Parse an HL7 v2 TS/DTM repetition into fidelity TS parts. Delegates
to parseDtm: the same structural parser that backs every timestamp in the
library. No user dateFormats at this layer; non-composite callers (e.g.
msg.meta.timestamp) that know ParseOptions.dateFormats use
parseDtmCascade directly.
The result is frozen so the immutability guarantee holds for callers that destructure or retain it.
Parameters
rep
_enc
Returns
Example
import { parseTs, DEFAULT_ENCODING_CHARACTERS, dtmToDate } from "@cosyte/hl7";
const rep = { components: [{ subcomponents: ["20250102153045-0500"] }] };
const ts = parseTs(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(ts.raw); // "20250102153045-0500"
console.log(ts.precision, ts.hasTimezone); // "second" true
console.log(dtmToDate(ts)?.toISOString()); // "2025-01-02T20:30:45.000Z"
parseXad()
parseXad(
rep,enc):XAD
Parse an HL7 v2 XAD repetition into a structured XAD object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics).
Parameters
rep
enc
Returns
Example
import { parseXad, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["123 Main St"] },
{ subcomponents: ["Apt 4"] },
{ subcomponents: ["Boston"] },
{ subcomponents: ["MA"] },
{ subcomponents: ["02101"] },
] };
const addr = parseXad(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(addr.city); // "Boston"
parseXcn()
parseXcn(
rep,enc):XCN
Parse an HL7 v2 XCN repetition into a structured XCN object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics). Component 9
(assigningAuthority) is parsed as a nested HD; see component table in
the XCN interface JSDoc for the v1 trimming.
Parameters
rep
enc
Returns
Example
import { parseXcn, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["1234567890"] },
{ subcomponents: ["Smith"] },
{ subcomponents: ["Jane"] },
{ subcomponents: [""] },
{ subcomponents: [""] },
{ subcomponents: [""] },
{ subcomponents: [""] },
{ subcomponents: [""] },
{ subcomponents: ["HOSP", "1.2.3", "ISO"] },
{ subcomponents: ["L"] },
{ subcomponents: [""] },
{ subcomponents: [""] },
{ subcomponents: ["NPI"] },
] };
const xcn = parseXcn(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(xcn.idNumber); // "1234567890"
console.log(xcn.familyName); // "Smith"
console.log(xcn.assigningAuthority?.namespaceId); // "HOSP"
console.log(xcn.identifierTypeCode); // "NPI"
parseXpn()
parseXpn(
rep,enc):XPN
Parse an HL7 v2 XPN repetition into a structured XPN object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics).
Parameters
rep
enc
Returns
Example
import { parseXpn, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["Smith"] },
{ subcomponents: ["Jane"] },
] };
const xpn = parseXpn(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(xpn.familyName); // "Smith"
console.log(xpn.givenName); // "Jane"
parseXtn()
parseXtn(
rep,enc):XTN
Parse an HL7 v2 XTN repetition into a structured XTN object. Components
are returned verbatim (already decoded once by the tokenizer: never
re-unescaped). Absent / empty components are OMITTED
from the result (exactOptionalPropertyTypes semantics). Components past
position 12 are silently ignored in v1.
Parameters
rep
enc
Returns
Example
import { parseXtn, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const rep = { components: [
{ subcomponents: ["555-1234"] },
{ subcomponents: ["PRN"] },
{ subcomponents: ["PH"] },
] };
const xtn = parseXtn(rep, DEFAULT_ENCODING_CHARACTERS);
console.log(xtn.telephoneNumber); // "555-1234"
pickMrn()
pickMrn(
identifiers):string|undefined
Pick the Medical Record Number string from a list of PID-3 CX identifiers.
D-07: prefer the first CX whose identifierTypeCode === "MR" (HL7 v2.5+
canonical MRN marker). D-10: the match is case-SENSITIVE: lowercase
"mr" does NOT match; spec mandates uppercase.
D-08: when no MR-typed identifier is found, fall back to the first CX's
idNumber. Returns undefined when the first CX has no idNumber (even
if later CXs do: the fallback is strictly "first CX", not "first CX with
idNumber", because we need a deterministic answer).
D-21: no warning emitted. Callers who need strict MR resolution can walk
patient.identifiers themselves.
Parameters
identifiers
readonly CX[]
Returns
string | undefined
Example
import { pickMrn } from "@cosyte/hl7";
pickMrn([
{ idNumber: "X1" },
{ idNumber: "MRN001", identifierTypeCode: "MR" },
]);
// → "MRN001"
pickMrn([{ idNumber: "X1" }]);
// → "X1" (fallback: no MR entry)
pickMrn([]);
// → undefined
reescape()
reescape(
input,enc):string
Re-escape reserved characters back into their \X\ forms so the serializer
can emit spec-clean HL7. This is the inverse of unescape for every
delimiter-bearing character; round-trip cleanliness
(unescape(reescape(x, enc), enc, emit, pos) === x) is a documented
property covered by tests.
The characters re-escaped:
enc.escape → \E
enc.field → \F
enc.component → \S
enc.subcomponent → \T
enc.repetition → \R
enc.truncation → \P\ (only when MSH-2 declared one, v2.7+)
"\n" (LF) → .br
"\r" (CR) → \X0D\ (a decoded CR is the HL7 segment separator;
emitting it raw would corrupt wire framing, so
it re-encodes to its hex escape: see below)
Lossy by construction for the non-delimiter escape families. reescape
only knows about the reserved characters above: it cannot reconstruct a
recognize-and-preserve escape (\H\, formatting, charset, \Z..) or the
original bytes of a hex escape (\X41\ decoded to A; casing of \X0d),
because those decode to ordinary characters that carry no "I was an escape"
marker. Byte-verbatim emit for those families is the serializer's job via
the RawComponent.rawSubcomponents overlay (see escapeFidelityRaw);
reescape is the fallback for content that has no overlay (constructed
values, Field-level re-escapes).
Iteration uses for..of, which walks Unicode code points (not UTF-16 code
units), so user-supplied content containing non-BMP characters round-trips
correctly without special surrogate-pair handling.
Parameters
input
string
enc
Returns
string
Example
import { reescape, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
reescape("Smith|John", DEFAULT_ENCODING_CHARACTERS); // "Smith\\F\\John"
reescape("line1\nline2", DEFAULT_ENCODING_CHARACTERS); // "line1\\.br\\line2"
renderText()
renderText(
input,enc?,opts?):RenderedText
Render an HL7 v2 escape/formatting-bearing string into a normalized display model (plain text + highlight-aware runs). A read projection: it never mutates the underlying field, and it never fabricates: an escape it cannot render is preserved as literal characters and flagged in RenderedText.unrenderedSequences.
input is the field's escape-bearing text. Pass the wire form (e.g.
Field.text, byte-verbatim for parsed content) for the most faithful
result: a wire \E\H\E\ (an escaped literal backslash-H-backslash) then
renders as the three literal characters \H, never as a highlight. Passing
an already-decoded Field.value also works: its \.br\ is already a
newline (rendered as one line break) and its \H/formatting sentinels are
still recognized.
Never throws for any input.
Parameters
input
string
the escape-bearing field text to render.
enc?
EncodingCharacters = DEFAULT_ENCODING_CHARACTERS
the message's encoding characters (for \F\/\S/… targets);
defaults to the HL7 standard |^~\&.
opts?
see RenderTextOptions.
Returns
the normalized RenderedText.
Example
import { renderText } from "@cosyte/hl7";
const r = renderText("Specimen received.\\.br\\Gross exam \\H\\normal\\N\\.");
r.text;
// "Specimen received.\nGross exam normal."
r.runs;
// [ { text: "Specimen received.\nGross exam ", highlighted: false },
// { text: "normal", highlighted: true },
// { text: ".", highlighted: false } ]
r.unrenderedSequences; // []
resolveCharset()
resolveCharset(
raw):CharsetResolution
Resolve an MSH-18 label (or an options.charset override) to a
CharsetResolution. A blank / undefined label resolves to the ASCII
default. A recognized Table-0211 code returns its registry entry. An
unrecognized label resolves to a verbatim, recognized: false outcome so
the caller preserves bytes and emits UNKNOWN_CHARSET.
Parameters
raw
string | undefined
Returns
Example
import { resolveCharset } from "@cosyte/hl7";
resolveCharset("UNICODE UTF-8"); // { canonical: "UTF-8", treatment: "decode", ... }
resolveCharset("ISO IR87"); // { canonical: "ISO IR87", treatment: "verbatim", recognized: true }
resolveCharset("WINDOWS-1252"); // { canonical: "WINDOWS-1252", treatment: "verbatim", recognized: false }
resolvePath()
resolvePath(
path,segments,_enc):string|undefined
Resolve a dot-path string against a raw segment tree to its decoded
leaf value. Returns undefined whenever the path does not resolve (missing
segment, out-of-range field/component/subcomponent/repetition), and never
throws on a missing value. Throws TypeError only when path itself is
malformed: callers relying on "never throws" should pre-validate or wrap
in try/catch.
Parameters
path
string
segments
readonly RawSegment[]
_enc
Returns
string | undefined
Example
import { parseHL7, resolvePath } from "@cosyte/hl7";
const msg = parseHL7(raw);
resolvePath("PID.5.1", msg.rawSegments, msg.encodingCharacters); // "Smith"
resolvePath("NOT.9", msg.rawSegments, msg.encodingCharacters); // undefined
segmentCase()
segmentCase(
position,observed):Hl7ParseWarning
Build a SEGMENT_CASE warning. Emitted when a segment identifier is not
all-uppercase (e.g. pid instead of PID). The parser accepts the
segment; the warning alerts consumers to non-conforming senders.
Parameters
position
observed
string
Returns
Example
import { segmentCase } from "@cosyte/hl7";
const w = segmentCase({ segmentIndex: 3 }, "pid");
setDefaultProfile()
setDefaultProfile(
profile):void
Register a process-scoped default profile. parseHL7(raw) (with no
explicit profile arg) consults getDefaultProfile() and applies the
returned profile if any. Pass null (or undefined) to clear.
Effects are IDENTICAL to passing the profile explicitly as the second
arg of parseHL7 (D-20): customSegments, dateFormats, onWarning chain,
and profile attribution all apply the same way.
Explicit args ALWAYS win: parseHL7(raw, myProfile) uses myProfile
regardless of the default; parseHL7(raw, { profile: null }) opts out
of the default for a single call without changing the registered
default.
Test hygiene: This is the ONLY mutable module-scoped state in the
library. Tests that call setDefaultProfile MUST clean up in
afterEach (setDefaultProfile(null)) or default-profile bleed will
infect subsequent tests.
Parameters
profile
Profile | null
Returns
void
Example
import { setDefaultProfile, profiles, parseHL7 } from "@cosyte/hl7";
// Set once at app startup
setDefaultProfile(profiles.epic);
// Every parseHL7 call in the app now uses profiles.epic unless
// another profile is passed explicitly.
const msg = parseHL7(raw);
console.log(msg.profile?.name); // "epic"
// Clear when done (or in test teardown):
setDefaultProfile(null);
splitBatch()
Internal
implementation signature; overloads above carry the public JSDoc.
Call Signature
splitBatch(
raw):BatchSplitResult
Split a raw HL7 v2 batch / file stream into its individual messages plus
the envelope metadata. Demarcates by MSH boundaries inside the optional
[FHS] { [BHS] { MSH… } [BTS] } [FTS] frame (HL7 v2 Ch. 2 §2.10.3):
- handles a file with multiple batches and a batch with multiple messages;
- a bare single message (no envelope) passes straight through as one entry;
- a malformed message mid-batch is isolated (returned as a typed failure entry): its siblings are still returned, the tail is never dropped;
- reconciles BTS-1 (batch message count) and FTS-1 (file batch count) and emits batchCountMismatch on a mismatch: counts only, never PHI;
- emits batchMissingTrailer when a
BHS/FHSheader opens a scope noBTS/FTScloses: a warning, never a throw (the caller decides to reject).
The second argument, when given, is forwarded verbatim to parseHL7
for each message (profile, strict, charset, dateFormats, …). Under
strict, a message that would warn surfaces as a failure entry (still
isolated). splitBatch itself never throws: an empty stream yields an empty
result.
Parameters
raw
string | Buffer<ArrayBufferLike>
Returns
Example
import { splitBatch } from "@cosyte/hl7";
const { messages, warnings } = splitBatch(
"FHS|^~\\&|SENDER\r" +
"BHS|^~\\&|SENDER\r" +
"MSH|^~\\&|A|F|B|G|20260101||ADT^A01|1|P|2.5\rPID|||1\r" +
"MSH|^~\\&|A|F|B|G|20260101||ADT^A01|2|P|2.5\rPID|||2\r" +
"BTS|2\rFTS|1\r",
);
console.log(messages.length); // 2
for (const entry of messages) {
if (entry.ok) console.log(entry.message.version); // "2.5"
}
console.log(warnings.length); // 0: declared counts match
Call Signature
splitBatch(
raw,profile):BatchSplitResult
Split a raw HL7 v2 batch / file stream into its individual messages plus
the envelope metadata. Demarcates by MSH boundaries inside the optional
[FHS] { [BHS] { MSH… } [BTS] } [FTS] frame (HL7 v2 Ch. 2 §2.10.3):
- handles a file with multiple batches and a batch with multiple messages;
- a bare single message (no envelope) passes straight through as one entry;
- a malformed message mid-batch is isolated (returned as a typed failure entry): its siblings are still returned, the tail is never dropped;
- reconciles BTS-1 (batch message count) and FTS-1 (file batch count) and emits batchCountMismatch on a mismatch: counts only, never PHI;
- emits batchMissingTrailer when a
BHS/FHSheader opens a scope noBTS/FTScloses: a warning, never a throw (the caller decides to reject).
The second argument, when given, is forwarded verbatim to parseHL7
for each message (profile, strict, charset, dateFormats, …). Under
strict, a message that would warn surfaces as a failure entry (still
isolated). splitBatch itself never throws: an empty stream yields an empty
result.
Parameters
raw
string | Buffer<ArrayBufferLike>
profile
Returns
Example
import { splitBatch } from "@cosyte/hl7";
const { messages, warnings } = splitBatch(
"FHS|^~\\&|SENDER\r" +
"BHS|^~\\&|SENDER\r" +
"MSH|^~\\&|A|F|B|G|20260101||ADT^A01|1|P|2.5\rPID|||1\r" +
"MSH|^~\\&|A|F|B|G|20260101||ADT^A01|2|P|2.5\rPID|||2\r" +
"BTS|2\rFTS|1\r",
);
console.log(messages.length); // 2
for (const entry of messages) {
if (entry.ok) console.log(entry.message.version); // "2.5"
}
console.log(warnings.length); // 0: declared counts match
Call Signature
splitBatch(
raw,options):BatchSplitResult
Split a raw HL7 v2 batch / file stream into its individual messages plus
the envelope metadata. Demarcates by MSH boundaries inside the optional
[FHS] { [BHS] { MSH… } [BTS] } [FTS] frame (HL7 v2 Ch. 2 §2.10.3):
- handles a file with multiple batches and a batch with multiple messages;
- a bare single message (no envelope) passes straight through as one entry;
- a malformed message mid-batch is isolated (returned as a typed failure entry): its siblings are still returned, the tail is never dropped;
- reconciles BTS-1 (batch message count) and FTS-1 (file batch count) and emits batchCountMismatch on a mismatch: counts only, never PHI;
- emits batchMissingTrailer when a
BHS/FHSheader opens a scope noBTS/FTScloses: a warning, never a throw (the caller decides to reject).
The second argument, when given, is forwarded verbatim to parseHL7
for each message (profile, strict, charset, dateFormats, …). Under
strict, a message that would warn surfaces as a failure entry (still
isolated). splitBatch itself never throws: an empty stream yields an empty
result.
Parameters
raw
string | Buffer<ArrayBufferLike>
options
Returns
Example
import { splitBatch } from "@cosyte/hl7";
const { messages, warnings } = splitBatch(
"FHS|^~\\&|SENDER\r" +
"BHS|^~\\&|SENDER\r" +
"MSH|^~\\&|A|F|B|G|20260101||ADT^A01|1|P|2.5\rPID|||1\r" +
"MSH|^~\\&|A|F|B|G|20260101||ADT^A01|2|P|2.5\rPID|||2\r" +
"BTS|2\rFTS|1\r",
);
console.log(messages.length); // 2
for (const entry of messages) {
if (entry.ok) console.log(entry.message.version); // "2.5"
}
console.log(warnings.length); // 0: declared counts match
timestampFallbackFormat()
timestampFallbackFormat(
position,matchedFormat):Hl7ParseWarning
Build a TIMESTAMP_FALLBACK_FORMAT warning. Emitted when a date/time
field could not be parsed with its primary (strict HL7) format but a
fallback format from ParseOptions.dateFormats or built-in fallbacks
succeeded.
Parameters
position
matchedFormat
string
Returns
Example
import { timestampFallbackFormat } from "@cosyte/hl7";
const w = timestampFallbackFormat(
{ segmentIndex: 1, fieldIndex: 7 },
"YYYY-MM-DD",
);
unescape()
unescape(
input,enc,emit,position):string
Expand HL7 escape sequences (\F\, \S, \T\, \R, \E\, \.br,
\X..\, vendor-specific \Z..) inside a field, component, or
subcomponent string. The escape delimiter comes from enc.escape (default
``), so senders using a non-default escape character are handled
transparently.
Unknown or malformed sequences are preserved VERBATIM in the output and
emit an UNKNOWN_ESCAPE_SEQUENCE warning via emit. Unterminated escapes
(an escape character with no closing partner before end-of-input) are also
preserved in full and warn once: the scan is strictly O(n) and cannot
infinite-loop on malformed input.
Parameters
input
string
enc
emit
(w) => void
position
Returns
string
Example
import { unescape, DEFAULT_ENCODING_CHARACTERS } from "@cosyte/hl7";
const warnings: Array<unknown> = [];
const out = unescape(
"patient\\F\\name\\.br\\DOB",
DEFAULT_ENCODING_CHARACTERS,
(w) => warnings.push(w),
{ segmentIndex: 1, fieldIndex: 5 },
);
// out === "patient|name\nDOB"
unknownCharset()
unknownCharset(
position,requested):Hl7ParseWarning
Build an UNKNOWN_CHARSET warning. Emitted when MSH-18 (or an
options.charset override) declares a value that is not a recognized HL7
Table-0211 character set. The parser never guesses an encoding: it reads the
raw bytes as latin1 (a 1:1 byte mapping) so single-byte content stays
recoverable, rather than corrupting it with replacement characters. The
message carries the charset code only, never a decoded field value: the code
is shape-checked before it is interpolated, and withheld if it does not match.
Parameters
position
requested
string
Returns
Example
import { unknownCharset } from "@cosyte/hl7";
const w = unknownCharset({ segmentIndex: 0, fieldIndex: 18 }, "ISO IR 999");
unknownEscapeSequence()
unknownEscapeSequence(
position,body):Hl7ParseWarning
Build an UNKNOWN_ESCAPE_SEQUENCE warning for a terminated escape
sequence (\..\) whose body is not a recognized HL7 escape. The message
NEVER embeds the escape body: only its length and, when the body's first
character is a recognized HL7 escape-identifier letter (structural HL7
grammar, not PHI: e.g. Z for a vendor escape, . for a formatting
escape), that single letter. A body that doesn't start with a known escape
letter is untrusted field text and names no character at all. The sequence
itself is still preserved verbatim in the parsed output: only the WARNING
message is PHI-safe.
Parameters
position
body
string
Returns
Example
import { unknownEscapeSequence } from "@cosyte/hl7";
const w = unknownEscapeSequence({ segmentIndex: 2, fieldIndex: 3 }, "Z99");
// message: `Unknown HL7 escape sequence of type "Z" (3 chars) preserved verbatim.`
unknownSegment()
unknownSegment(
position,segmentName):Hl7ParseWarning
Build an UNKNOWN_SEGMENT warning. Emitted when a segment identifier is
not in the HL7 spec's standard set and not registered in the active
profile's customSegments.
Parameters
position
segmentName
string
Returns
Example
import { unknownSegment } from "@cosyte/hl7";
const w = unknownSegment({ segmentIndex: 7 }, "ZZZ");
unsupportedCharset()
unsupportedCharset(
position,code):Hl7ParseWarning
Build an UNSUPPORTED_CHARSET warning. Emitted when a recognized
Table-0211 character set is not decoded into text: either because this parser
does not decode it (the multibyte / ISO-2022-switched East-Asian sets: JIS,
GB 18030, KS X 1001, CNS 11643, BIG-5: and the wide Unicode transforms
UTF-16 / UTF-32), or because a strict decode of a decodable set failed (a
byte invalid / undefined for the declared set, or an ICU build lacking the
label). In every case the raw bytes are read as latin1, never guessed at, so
the parser does not emit replacement-char-corrupted text. Single-byte content
stays byte-recoverable; multibyte content is best-effort (a content byte can
coincide with a structural delimiter: see the parser's known-limitations).
The switch escapes (\Cxxyy\ / \Mxxyyzz) are recognized and preserved by
the escape layer; full stateful decoding is a documented non-goal. The message
carries the charset code only, never a decoded field value: the code is
shape-checked before it is interpolated, and withheld if it does not match.
Parameters
position
code
string
Returns
Example
import { unsupportedCharset } from "@cosyte/hl7";
const w = unsupportedCharset({ segmentIndex: 0, fieldIndex: 18 }, "ISO IR87");
unterminatedEscapeSequence()
unterminatedEscapeSequence(
position):Hl7ParseWarning
Build an UNKNOWN_ESCAPE_SEQUENCE warning for an unterminated escape
(an escape character with no closing partner before end-of-input). The
"body" in this case is the entire remainder of the field, so the message
carries NEITHER the body NOR its length (the length of a truncated tail is
itself derivable field-shape information): just the fact that an
unterminated escape was found, and the position. The remainder is still
preserved verbatim in the parsed output.
Parameters
position
Returns
Example
import { unterminatedEscapeSequence } from "@cosyte/hl7";
const w = unterminatedEscapeSequence({ segmentIndex: 2, fieldIndex: 3 });
// message: `Unterminated HL7 escape sequence preserved verbatim.`
unterminatedStreamMessage()
unterminatedStreamMessage(
position):Hl7ParseWarning
Build an UNTERMINATED_STREAM_MESSAGE warning. Emitted by
parseStream(): attached to a StreamMessageEntry's streamWarnings,
NOT to Hl7Message.warnings: when the final message in a stream ends
without a segment terminator (its last segment ran to end-of-stream with no
trailing \r/\r\n/\n). The message is still parsed and yielded in
full; this is the fail-safe signal that the tail may have been truncated
mid-message (a cut-off feed), never a reason to drop it and never a throw.
Only the last message can be unterminated: every earlier message is closed by
the terminator that precedes the next MSH/envelope boundary.
The message carries only the structural fact, NEVER a field value, so no PHI
is exposed. position references the message's MSH segment index.
Parameters
position
Returns
Example
import { unterminatedStreamMessage } from "@cosyte/hl7";
const w = unterminatedStreamMessage({ segmentIndex: 6 });
validateAgainstProfile()
validateAgainstProfile(
message,profile):ConformanceResult
Validate a parsed HL7 v2 message against a user-authored declarative conformance profile and return typed findings.
Never throws. A well-formed profile is evaluated rule by rule; a
malformed profile is reported as PROFILE_MALFORMED findings (the engine
does not validate against a profile it cannot trust: never a silent pass).
Either way you get a ConformanceResult. For fail-fast authoring, run
defineConformanceProfile first: it throws on a malformed profile.
findings.length === 0 is NOT a conformance attestation. It means every
rule the profile declared was satisfied: nothing about the parts of the
message the profile did not cover, and nothing about clinical correctness.
No PHI in findings: each finding names the structural locus (segment / field / component / repetition) and the rule, never the offending value.
Read-only: the message is never mutated.
Parameters
message
a parsed message from parseHL7.
profile
the consumer's declarative ConformanceProfile.
Returns
the profile name and the ordered findings (empty ⇒ no declared rule violated).
Example
import { parseHL7, validateAgainstProfile, type ConformanceProfile } from "@cosyte/hl7";
const profile: ConformanceProfile = {
name: "example-adt-min",
segments: [
{ segment: "PID", usage: "R", fields: [
{ field: 3, name: "Patient Identifiers", usage: "R" },
{ field: 8, name: "Administrative Sex", usage: "RE", valueSet: ["M", "F", "U"] },
] },
],
};
const msg = parseHL7(raw);
const { findings } = validateAgainstProfile(msg, profile);
for (const f of findings) console.log(f.severity, f.code, f.message);
// findings.length === 0 ⇒ no declared rule violated (NOT an attestation)
versionMismatch()
versionMismatch(
position,declared,expected):Hl7ParseWarning
Build a VERSION_MISMATCH warning. Emitted when MSH-12 declares an HL7
version that does not match what the active profile or ParseOptions
expected.
Parameters
position
declared
string
expected
string
Returns
Example
import { versionMismatch } from "@cosyte/hl7";
const w = versionMismatch({ segmentIndex: 0, fieldIndex: 12 }, "2.9", "2.5");