tosch-hl7
v1.1.0
Published
A TypeScript/JavaScript library to work with HL7 messages in a simple and intuitive way. Features optimized internal structure, consistent 1-based indexing, field modification, and minimal optional chaining.
Maintainers
Readme
HL7 Library
A TypeScript/JavaScript library to work with HL7 messages in a simple and intuitive way.
Documentation
📖 View Complete API Documentation
Features
- ✅ Automatic HL7 message parsing
- ✅ Optimized internal structure: Segments organized as key-value object
- ✅ Smart handling: Single object for unique segments, array for multiples
- ✅ Field access using path notation (
PID-5.1) - ✅ Direct access using
[]operator (message.PID[5]) - ✅ Field modification and creation: Both
[]andset()methods support writing - ✅ Consistent 1-based indexing: Same numbering for both access methods
- ✅ Segment search by name
- ✅ Support for field components and subcomponents
- ✅ Segment existence verification
- ✅ Message validation: CustomValidator with comprehensive HL7 field validation rules
- ✅ TypeScript friendly
Installation
npm install tosch-hl7Internal Structure
The library automatically parses HL7 messages into an optimized structure:
- Unique segments: Stored as individual objects (
message.MSH,message.PID) - Multiple segments of the same type: Stored as arrays (
message.OBX[]) - Uniform access: The
[]operator always returns the first element for compatibility
// Example of internal structure:
{
MSH: Segment, // Single object
PID: Segment, // Single object
PV1: Segment, // Single object
OBX: [Segment, Segment] // Array of multiple segments
}Basic Usage
import { HL7Message } from 'tosch-hl7';
const hl7Data = `MSH|^~\\&|SYSTEM|HOSPITAL|LAB|HOSPITAL|20240101120000||ADT^A01|12345|P|2.4
PID|1||123456789^^^HOSPITAL^MR||DOE^JOHN^MIDDLE||19800101|M|||123 MAIN ST^^ANYTOWN^ST^12345^USA
PV1|1|I|ICU^101^1|||ATTENDING^DOCTOR^A|||SUR||||19|VIP|ATTENDING^DOCTOR^A
OBX|1|TX|NOTE||First observation
OBX|2|TX|DIAG||Second observation`;
// No type conversion required!
const message = new HL7Message(hl7Data);
// Direct access to segments with full typing
console.log(message.PID[5]); // TypeScript recognizes the type automatically
console.log(message.MSH[9]); // No need for 'as any' or conversionsAccess Methods
1. Existence Verification
// Verify if a segment exists
console.log(message.hasSegment('PID')); // true
console.log(message.hasSegment('NK1')); // false
// Get complete structure
const parsed = message.getParsedSegments();
console.log(parsed); // { MSH: Segment, PID: Segment, PV1: Segment, OBX: [Segment, Segment] }2. Direct Segment Access
// Access to unique segments
const pidSegment = message.getSegment('PID');
const mshSegment = message.getSegment('MSH');
// Access to multiple segments
const allObx = message.getAllSegments('OBX'); // Array of OBX segments
console.log(`There are ${allObx.length} observations`);// Get complete field
console.log(message.get('PID-5')); // "DOE^JOHN^MIDDLE"
// Get specific component
console.log(message.get('PID-5.1')); // "DOE"
console.log(message.get('PID-5.2')); // "JOHN"
console.log(message.get('PID-5.3')); // "MIDDLE"
// Other examples
console.log(message.get('PID-8')); // "M" (sex)
console.log(message.get('MSH-9')); // "ADT^A01" (message type)3. get() Method - Path Notation
// Get complete field
console.log(message.get('PID-5')); // "DOE^JOHN^MIDDLE"
// Get specific component
console.log(message.get('PID-5.1')); // "DOE"
console.log(message.get('PID-5.2')); // "JOHN"
console.log(message.get('PID-5.3')); // "MIDDLE"
// Other examples
console.log(message.get('PID-8')); // "M" (sex)
console.log(message.get('MSH-9')); // "ADT^A01" (message type)
// For multiple segments, gets the first one
console.log(message.get('OBX-5')); // Content of the first observation4. [] Operator - Direct Access
// Access to unique segments
const pidSegment = message.PID; // Equivalent to message.getSegment('PID')
const mshSegment = message.MSH; // Equivalent to message.getSegment('MSH')
// Field access
console.log(message.PID[5].toString()); // "DOE^JOHN^MIDDLE"
console.log(message.PID[8].toString()); // "M"
console.log(message.MSH[9].toString()); // "ADT^A01"
// For multiple segments, accesses the first one automatically
console.log(message.OBX[5].toString()); // Content of the first observation
// Check existence with optional chaining
console.log(message.NK1?.[1]); // undefined if NK1 doesn't exist5. Multiple Segments Handling
// Get all segments of a type
const pidSegments = message.getAllSegments('PID');
console.log(`Found ${pidSegments.length} PID segments`);
// Get all segments from the message
const allSegments = message.getSegments();
allSegments.forEach(segment => {
console.log(`Segment: ${segment.getName()}`);
});6. Get the Complete Message as Text
// Convert the entire HL7 message to text in HL7 format
const hl7Text = message.toString();
console.log(hl7Text);The toString() method returns the entire HL7 message as a string, with segments separated by \r\n (the HL7 standard).
Example Output
MSH|^~\&|SYSTEM|HOSPITAL|LAB|HOSPITAL|20240101120000||ADT^A01|12345|P|2.4
PID|1||123456789^^^HOSPITAL^MR||DOE^JOHN^MIDDLE||19800101|M|||123 MAIN ST^^ANYTOWN^ST^12345^USA||(555)123-4567|||S||987654321|||U
PV1|1|I|ICU^101^1|||ATTENDING^DOCTOR^A|||SUR||||19|VIP|ATTENDING^DOCTOR^APractical Examples
Extract Patient Information
const patientName = message['PID'][5].toString(); // "DOE^JOHN^MIDDLE"
const patientSex = message['PID'][8].toString(); // "M"
const birthDate = message['PID'][7].toString(); // "19800101"
const patientId = message['PID'][3].toString(); // "123456789^^^HOSPITAL^MR"
console.log(`Patient: ${patientName}, Sex: ${patientSex}, Birth: ${birthDate}`);Message Information
const messageType = message['MSH'][9].toString(); // "ADT^A01"
const sendingApp = message['MSH'][3].toString(); // "HOSPITAL"
const timestamp = message['MSH'][7].toString(); // "20240101120000"
console.log(`Message ${messageType} from ${sendingApp} at ${timestamp}`);Visit Information
if (message['PV1']) {
const patientClass = message['PV1'][2].toString(); // "I" (Inpatient)
const location = message['PV1'][3].toString(); // "ICU^101^1"
console.log(`Patient ${patientClass} at location ${location}`);
}Modify and Create Fields/Components/Subcomponents
// Modify a field using []
message.PID[5] = 'SMITH^JANE^A';
console.log(message.PID[5].toString()); // "SMITH^JANE^A"
// Modify a component using [] - No need for multiple ?.
message.PID[5][2] = 'ALICE';
console.log(message.PID[5][2].toString()); // "ALICE"
// Modify a subcomponent using [] - Clean syntax
message.PID[5][2][1] = 'ALICE-SUB';
console.log(message.PID[5][2][1].toString()); // "ALICE-SUB"
// Create a field if it does not exist
message.PID[20] = 'NEWFIELD';
console.log(message.PID[20].toString()); // "NEWFIELD"
// Use set() to modify field, component, and subcomponent
message.set('PID-6', 'NEWFIELD6');
console.log(message.PID[6].toString()); // "NEWFIELD6"
message.set('PID-6.1', 'COMPONENT1');
console.log(message.PID[6][1].toString()); // "COMPONENT1"
message.set('PID-6.1.2', 'SUBCOMP2');
console.log(message.PID[6][1][2].toString()); // "SUBCOMP2"
// Non-existent fields return null (not empty objects)
console.log(message.PID[99]); // null
console.log(message.PID[5][99]); // null
// Both reading and writing use the same consistent 1-based indexing
console.log(message.get('PID-5.2')); // Same as message.PID[5][2]
message.set('PID-5.2', 'NEWNAME'); // Same as message.PID[5][2] = 'NEWNAME'Cleaner Syntax
// BEFORE: Too much optional chaining
const value = message.PID?.[5]?.[2]?.[1]?.toString();
// NOW: Only need ? for the segment
const value = message.PID?.[5][2][1]?.toString();
// Even cleaner with validation
if (message.PID) {
const value = message.PID[5][2][1]?.toString();
}Methods Comparison
| Method | Sintax | Example | Result |
|--------|----------|---------|-----------|
| get() | get('SEGMENT-FIELD.COMPONENT.SUBCOMPONENT') | message.get('PID-5.1.2') | "Sub1" |
| [] | ['SEGMENT'][FIELD][COMPONENT][SUBCOMPONENT] | message['PID'][5][1][2] | "Sub1" |
✅ Consistent Indices: Both methods now use the same 1-based numbering:
get('PID-5.1.2')is exactly equivalent toPID[5][1][2]- The first component is
[1]in both cases - The first subcomponent is
[1]in both cases
Equivalence Examples
// These expressions are completely equivalent:
message.get('PID-5.1') === message.PID[5][1] // "DOE"
message.get('PID-5.2') === message.PID[5][2] // "JOHN"
message.get('OBX-5.1.1') === message.OBX[5][1][1] // First subcomponent
message.get('PV1-3.4') === message.PV1[3][4] // Fourth componentSupported HL7 Segments
The library supports all standard HL7 segments, including:
MSH- Message HeaderPID- Patient IdentificationPV1- Patient VisitOBX- Observation/ResultOBR- Observation RequestEVN- Event Type- And many more...
API Reference
HL7Message Class
Constructor
new HL7Message(rawHL7: string)Methods
get(path: string): string- Gets a value using path notationgetAllSegments(name: string): Segment[]- Gets all segments of a typegetSegments(): Segment[]- Gets all segments
Operator Access
message['SEGMENT']- Gets the first segment of the specified type
Segment Class
Methods
getName(): string- Gets the segment namegetField(index: number): string- Gets a specific fieldgetComponent(fieldIndex: number, componentIndex: number): string- Gets a specific component
Operator Access
segment[INDEX]- Gets the field at the specified index
HL7Builder Class
The HL7Builder class allows you to programmatically construct HL7 messages in a simple and intuitive way. This is especially useful when you need to generate HL7 messages dynamically.
Features
- Add segments with fields, components, and subcomponents.
- Automatically handles field separators and encoding characters for the
MSHsegment. - Ensures proper alignment of fields, even when some are empty.
- Outputs a valid HL7 message string.
Example Usage
import { HL7Builder } from 'tosch-hl7';
const builder = new HL7Builder();
const message = builder
.addSegment('MSH', {
8: 'ACK^A01',
9: 'MSGID12345',
10: 'P',
11: '2.3'
})
.addSegment('PID', {
2: '123456789',
5: 'DOE^JOHN'
})
.build();
console.log(message.toString());Output
MSH|^~\&|||||||ACK^A01|MSGID12345|P|2.3
PID||123456789|||DOE^JOHNMethods
addSegment(name: string, fields: { [key: number]: string }): HL7Builder
Adds a new segment to the HL7 message.
name: The name of the segment (e.g.,MSH,PID).fields: An object where keys are field indices (1-based) and values are the field values.
build(): HL7Message
Builds the HL7 message and returns an instance of HL7Message.
CustomValidator Class
The CustomValidator class provides comprehensive validation capabilities for HL7 messages, allowing you to define rules to ensure data integrity and compliance with HL7 standards.
Features
- ✅ Required field validation: Ensure mandatory fields are present
- ✅ Data type validation: Validate against HL7 data types (ST, NM, DT, TS)
- ✅ Maximum length validation: Enforce field length limits
- ✅ Allowed values validation: Restrict fields to specific value sets
- ✅ Regular expression validation: Custom pattern matching
- ✅ Multiple rule validation: Apply multiple rules to the same field
- ✅ Comprehensive error reporting: Detailed validation results with specific error messages
Basic Usage
import { CustomValidator, ValidationRule } from 'tosch-hl7';
// Create validation rules
const rules: ValidationRule[] = [
{
path: 'MSH-9',
required: true,
allowedValues: ['ADT^A01', 'ADT^A04', 'ADT^A08']
},
{
path: 'PID-5.1',
required: true,
dataType: 'ST',
maxLength: 50
},
{
path: 'PID-7',
dataType: 'DT',
regex: '^\\d{8}$'
}
];
// Create validator and validate message
const validator = new CustomValidator(rules);
const result = validator.validate(message);
if (!result.isValid) {
result.errors.forEach(error => {
console.log(`Error in ${error.path}: ${error.message}`);
});
}Validation Rules
ValidationRule Interface
interface ValidationRule {
path: string; // HL7 field path (e.g., "PID-5.1")
required?: boolean; // Field is mandatory
dataType?: 'ST' | 'NM' | 'DT' | 'TS'; // HL7 data type
maxLength?: number; // Maximum field length
allowedValues?: string[]; // Permitted values
regex?: string; // Regular expression pattern
}Supported Data Types
ST- String: Alphanumeric textNM- Numeric: Numbers (integer or decimal)DT- Date: YYYYMMDD formatTS- Timestamp: YYYYMMDDHHMMSS format
Validation Examples
Required Fields
const rules: ValidationRule[] = [
{ path: 'MSH-3', required: true },
{ path: 'MSH-4', required: true },
{ path: 'PID-5.1', required: true }
];
const validator = new CustomValidator(rules);
const result = validator.validate(message);
// Will fail if any required field is missingData Type Validation
const rules: ValidationRule[] = [
{ path: 'PID-7', dataType: 'DT' }, // Date field
{ path: 'PV1-44', dataType: 'TS' }, // Timestamp field
{ path: 'PID-5.1', dataType: 'ST' }, // String field
{ path: 'PV1-19', dataType: 'NM' } // Numeric field
];Length and Value Constraints
const rules: ValidationRule[] = [
{
path: 'PID-5.1',
maxLength: 50,
dataType: 'ST'
},
{
path: 'MSH-9',
allowedValues: ['ADT^A01', 'ADT^A04', 'ADT^A08', 'ORM^O01']
},
{
path: 'PID-8',
allowedValues: ['M', 'F', 'O', 'U']
}
];Regular Expression Validation
const rules: ValidationRule[] = [
{
path: 'PID-7',
regex: '^\\d{8}$', // YYYYMMDD format
dataType: 'DT'
},
{
path: 'MSH-7',
regex: '^\\d{14}$' // YYYYMMDDHHMMSS format
}
];Complex Validation Scenarios
const comprehensiveRules: ValidationRule[] = [
// Message header validation
{
path: 'MSH-3',
required: true,
maxLength: 20,
dataType: 'ST'
},
{
path: 'MSH-9',
required: true,
allowedValues: ['ADT^A01', 'ADT^A04', 'ADT^A08']
},
// Patient identification validation
{
path: 'PID-3',
required: true,
regex: '^\\d+\\^\\^\\^.+\\^MR$' // Patient ID format
},
{
path: 'PID-5.1',
required: true,
dataType: 'ST',
maxLength: 50
},
{
path: 'PID-7',
dataType: 'DT',
regex: '^\\d{8}$'
},
{
path: 'PID-8',
allowedValues: ['M', 'F', 'O', 'U']
}
];
const validator = new CustomValidator(comprehensiveRules);
const result = validator.validate(message);
console.log(`Validation result: ${result.isValid ? 'PASSED' : 'FAILED'}`);
if (!result.isValid) {
console.log(`Found ${result.errors.length} validation errors:`);
result.errors.forEach((error, index) => {
console.log(`${index + 1}. ${error.path}: ${error.message}`);
});
}ValidationResult Interface
interface ValidationResult {
isValid: boolean; // Overall validation status
errors: ValidationError[]; // Array of validation errors
}
interface ValidationError {
path: string; // Field path where error occurred
message: string; // Descriptive error message
}Error Messages
The validator provides descriptive error messages for different validation failures:
- Required field:
"Field MSH-3 is required" - Invalid data type:
"Field PID-7 must be a valid date (DT format)" - Maximum length:
"Field PID-5.1 exceeds maximum length of 50 characters" - Allowed values:
"Field PID-8 must be one of: M, F, O, U" - Regular expression:
"Field PID-7 does not match required pattern"
Best Practices
- Start with required fields: Validate mandatory fields first
- Use appropriate data types: Match HL7 standard data types
- Combine validation rules: Use multiple constraints for comprehensive validation
- Handle validation results: Always check
isValidand process errors appropriately - Document validation rules: Maintain clear documentation of your validation requirements
API Reference
Constructor
new CustomValidator(rules: ValidationRule[])Methods
validate(message: HL7Message): ValidationResultThe validate method returns a ValidationResult object containing:
isValid: Boolean indicating if all validations passederrors: Array ofValidationErrorobjects with path and message details
License
MIT
