A TypeScript/JavaScript library to work with HL7 messages in a simple and intuitive way.
PID-5.1)[] operator (message.PID[5])[] and set() methods support writingnpm install tosch-hl7
The library automatically parses HL7 messages into an optimized structure:
message.MSH, message.PID)message.OBX[])[] 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
}
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 conversions
// 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] }
// 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)
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 observation
[] 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 exist
// 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()}`);
});
// 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).
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^A
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}`);
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}`);
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 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'
// 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();
}
| 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 to PID[5][1][2][1] in both cases[1] in both cases// 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 component
The library supports all standard HL7 segments, including:
MSH - Message HeaderPID - Patient IdentificationPV1 - Patient VisitOBX - Observation/ResultOBR - Observation RequestEVN - Event TypeHL7Message Classnew HL7Message(rawHL7: string)
get(path: string): string - Gets a value using path notationgetAllSegments(name: string): Segment[] - Gets all segments of a typegetSegments(): Segment[] - Gets all segmentsmessage['SEGMENT'] - Gets the first segment of the specified typeSegment ClassgetName(): string - Gets the segment namegetField(index: number): string - Gets a specific fieldgetComponent(fieldIndex: number, componentIndex: number): string - Gets a specific componentsegment[INDEX] - Gets the field at the specified indexThe 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.
MSH segment.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());
MSH|^~\&|||||||ACK^A01|MSGID12345|P|2.3
PID||123456789|||DOE^JOHN
addSegment(name: string, fields: { [key: number]: string }): HL7BuilderAdds 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(): HL7MessageBuilds the HL7 message and returns an instance of HL7Message.
The CustomValidator class provides comprehensive validation capabilities for HL7 messages, allowing you to define rules to ensure data integrity and compliance with HL7 standards.
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}`);
});
}
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
}
ST - String: Alphanumeric textNM - Numeric: Numbers (integer or decimal)DT - Date: YYYYMMDD formatTS - Timestamp: YYYYMMDDHHMMSS formatconst 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 missing
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
];
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']
}
];
const rules: ValidationRule[] = [
{
path: 'PID-7',
regex: '^\\d{8}$', // YYYYMMDD format
dataType: 'DT'
},
{
path: 'MSH-7',
regex: '^\\d{14}$' // YYYYMMDDHHMMSS format
}
];
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}`);
});
}
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
}
The validator provides descriptive error messages for different validation failures:
"Field MSH-3 is required""Field PID-7 must be a valid date (DT format)""Field PID-5.1 exceeds maximum length of 50 characters""Field PID-8 must be one of: M, F, O, U""Field PID-7 does not match required pattern"isValid and process errors appropriatelynew CustomValidator(rules: ValidationRule[])
validate(message: HL7Message): ValidationResult
The validate method returns a ValidationResult object containing:
isValid: Boolean indicating if all validations passederrors: Array of ValidationError objects with path and message detailsMIT