Package Exports
- @atomic-ehr/hl7v2
- @atomic-ehr/hl7v2/src/index.ts
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@atomic-ehr/hl7v2) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@atomic-ehr/hl7v2
Type-safe HL7v2 parsing and generation for TypeScript.
Design
Internal Representation
At the core is a minimal, JSON-friendly data structure that captures HL7v2 semantics without wire format details:
type FieldValue = string | FieldValue[] | { [key: number]: FieldValue };
interface HL7v2Segment {
segment: string; // "MSH", "PID", etc.
fields: Record<number, FieldValue>; // 1-indexed field values
}
type HL7v2Message = HL7v2Segment[];This representation:
- Preserves position information (field 3, component 1, etc.)
- Handles repeating fields as arrays
- Handles components/subcomponents as nested objects
- Is serializable to JSON
- Is independent of wire format delimiters
Field Naming: $N_name
Generated interfaces use positional field names with $N_name pattern:
interface PID {
$1_setIdPid?: string;
$3_identifier: CX[]; // Required (minOccurs=1)
$5_name: XPN[]; // Required
$7_birthDate?: string;
$8_gender?: string;
}
interface XPN {
$1_family?: FN;
$2_given?: string;
$3_additionalGiven?: string;
}Why $N_name:
- Position preserved -
$3_identifiertells you it's PID-3 - Readable - semantic name follows the number
- Compact - shorter than alternatives like
field_3_identifier - Neutral - same property for read and write (no
get_/set_confusion)
Symmetric API
Building and parsing use symmetric function pairs:
Wire Format ←→ Internal ←→ Typed Objects
parseMessage() fromPID() ← Reading
formatMessage() toADT_A01() ← WritingArchitecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Wire Format │────▶│ Internal │────▶│ Wire Format │
│ (pipe-delim) │ │ Representation │ │ (pipe-delim) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
parseMessage() HL7v2Message formatMessage()
│
┌────────────┴────────────┐
│ │
fromPID() toADT_A01()
fromMSH() ADT_A01Builder
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Typed Objects │ │ Typed Objects │
│ PID, MSH... │ │ ADT_A01_Input│
└───────────────┘ └───────────────┘Installation
bun installUsage
Building Messages
Option 1: Input Object (simplest)
import { toADT_A01, type ADT_A01_Input } from "./messages";
import { formatMessage } from "../src/hl7v2/format";
const input: ADT_A01_Input = {
type: "ADT_A01",
MSH: {
$3_sendingApplication: { $1_namespace: "HOSPITAL" },
$7_messageDateTime: "20251210120000",
$9_messageType: { $1_code: "ADT", $2_event: "A01" },
$10_messageControlId: "MSG001",
$11_processingId: { $1_processingId: "P" },
$12_version: { $1_version: "2.5.1" }
},
EVN: { $2_recordedDateTime: "20251210120000" },
PID: {
$3_identifier: [{ $1_value: "12345", $4_system: { $1_namespace: "MRN" } }],
$5_name: [{ $1_family: { $1_family: "Smith" }, $2_given: "John" }]
},
PV1: { $2_class: "I" },
DG1: [{ $1_setIdDg1: "1", $3_diagnosisCodeDg1: { $1_code: "J18.9" }, $6_diagnosisType: "A" }],
PROCEDURE: [{ PR1: { $1_setIdPr1: "1", $3_procedureCode: { $1_code: "99213" }, $5_procedureDateTime: "20251210" } }]
};
const message = toADT_A01(input);
const wire = formatMessage(message);Option 2: Fluent Builder (for incremental construction)
import { ADT_A01Builder } from "./messages";
const message = new ADT_A01Builder()
.msh({ $7_messageDateTime: "20251210120000", /* ... */ })
.evn({ $2_recordedDateTime: "20251210120000" })
.pid({ $3_identifier: [{ $1_value: "12345" }], $5_name: [{ $1_family: { $1_family: "Smith" } }] })
.pv1({ $2_class: "I" })
.addDG1({ $1_setIdDg1: "1", $3_diagnosisCodeDg1: { $1_code: "J18.9" }, $6_diagnosisType: "A" })
.build();Parsing Messages
import { parseMessage } from "../src/hl7v2/parse";
import { fromPID, fromMSH } from "./fields";
const wire = `MSH|^~\\&|HOSPITAL|FAC|||20231201||ADT^A01|MSG001|P|2.5.1
PID|1||12345^^^HOSP^MR||Smith^John||19800515|M`;
// Parse to internal representation
const message = parseMessage(wire);
// Convert to typed objects
const pid = fromPID(message.find(s => s.segment === "PID")!);
console.log(pid.$3_identifier?.[0]?.$1_value); // "12345"
console.log(pid.$5_name?.[0]?.$1_family?.$1_family); // "Smith"
console.log(pid.$5_name?.[0]?.$2_given); // "John"
console.log(pid.$8_gender); // "M"Code Generation
Generate type-safe code from HL7v2 schema:
bun src/hl7v2/codegen.ts ./output ADT_A01 BAR_P01This generates self-contained files:
| File | Contents |
|---|---|
types.ts |
Core types: FieldValue, HL7v2Segment, HL7v2Message |
tables.ts |
Constants: AdministrativeSex, PatientClass, etc. |
fields.ts |
DataTypes (XPN, CX), Segments (PID, MSH), Getters (fromPID) |
messages.ts |
Input interfaces (ADT_A01_Input), Converters (toADT_A01), Builders |
Generated code is standalone - no runtime dependency on src/hl7v2/.
Project Structure
src/hl7v2/
├── types.ts # Core types
├── parse.ts # Wire → Internal
├── format.ts # Internal → Wire
└── codegen.ts # Schema → TypeScript
schema/
├── messages/ # Message structures (ADT_A01.json, etc.)
├── segments/ # Segment definitions (PID.json, etc.)
├── fields/ # Field metadata
└── dataTypes/ # Complex types (XPN, CX, etc.)
example/
├── adt-a01-example.ts # Builder pattern
├── input-example.ts # Input object pattern
├── parse-example.ts # Parsing with getters
└── *.ts # Generated codeExamples
bun example/adt-a01-example.ts # Build with builder
bun example/input-example.ts # Build with input object
bun example/parse-example.ts # Parse and extractTesting
bun testCredits
Created by Nikolai Ryzhikov. Sponsored by Health Samurai.
HL7v2 schema from redox-hl7-v2.
License
MIT