@pritiranjan/hl7v2 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +434 -0
- package/dist/chunk-Q6NISXHR.js +130 -0
- package/dist/chunk-Q6NISXHR.js.map +1 -0
- package/dist/index.cjs +344 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +309 -0
- package/dist/index.d.ts +309 -0
- package/dist/index.js +200 -0
- package/dist/index.js.map +1 -0
- package/dist/schema-qr-scwmL.d.cts +106 -0
- package/dist/schema-qr-scwmL.d.ts +106 -0
- package/dist/segments/index.cjs +636 -0
- package/dist/segments/index.cjs.map +1 -0
- package/dist/segments/index.d.cts +457 -0
- package/dist/segments/index.d.ts +457 -0
- package/dist/segments/index.js +532 -0
- package/dist/segments/index.js.map +1 -0
- package/package.json +100 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Pritiranjan Swain <priti2chand@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# hl7v2
|
|
2
|
+
|
|
3
|
+
> Parse, query, build and re-encode HL7 v2.x messages — zero dependencies, TypeScript native.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@pritiranjan/hl7v2)
|
|
6
|
+
[](https://github.com/hkpritiranjan/hl7v2/actions)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
HL7 v2 is the most widely deployed healthcare messaging standard in the world — driving lab orders, ADT admissions, radiology reports, billing workflows, and much more. Yet every existing JavaScript library for it is either unmaintained, poorly typed, or missing key capabilities.
|
|
10
|
+
|
|
11
|
+
**hl7v2** provides a production-quality TypeScript implementation with:
|
|
12
|
+
|
|
13
|
+
- **Strict TypeScript** — `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, full generics
|
|
14
|
+
- **Round-trip fidelity** — `encode(parse(raw)) === raw`, critical for ACK generation and message forwarding
|
|
15
|
+
- **Zero runtime dependencies** — nothing to audit, nothing to break
|
|
16
|
+
- **1-based HL7 addressing** — `get(msg, 'PID', 5, 1)` maps directly to the HL7 spec's `PID.5.1`
|
|
17
|
+
- **Typed segment helpers** — `new PID(seg, msg.encoding).patientName()` returns `{ family, given, middle, ... }` not a raw string
|
|
18
|
+
- **ESM + CJS** — works in Node.js, Deno, Bun, and bundlers (Webpack, Vite, esbuild)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Table of contents
|
|
23
|
+
|
|
24
|
+
- [Installation](#installation)
|
|
25
|
+
- [Quick start](#quick-start)
|
|
26
|
+
- [Supported HL7 versions](#supported-hl7-versions)
|
|
27
|
+
- [API reference](#api-reference)
|
|
28
|
+
- [parse()](#parse)
|
|
29
|
+
- [encode()](#encode)
|
|
30
|
+
- [Query API](#query-api)
|
|
31
|
+
- [Typed segment helpers](#typed-segment-helpers)
|
|
32
|
+
- [Escape sequences](#escape-sequences)
|
|
33
|
+
- [Date / time utilities](#date--time-utilities)
|
|
34
|
+
- [HL7Message structure](#hl7message-structure)
|
|
35
|
+
- [Real-world examples](#real-world-examples)
|
|
36
|
+
- [Contributing](#contributing)
|
|
37
|
+
- [License](#license)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @pritiranjan/hl7v2
|
|
45
|
+
# or
|
|
46
|
+
yarn add @pritiranjan/hl7v2
|
|
47
|
+
# or
|
|
48
|
+
pnpm add @pritiranjan/hl7v2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Requires Node.js ≥ 18.** No runtime dependencies.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { parse, get, segment, segments, encode } from '@pritiranjan/hl7v2';
|
|
59
|
+
import { PID, OBX } from '@pritiranjan/hl7v2/segments';
|
|
60
|
+
|
|
61
|
+
const raw = `MSH|^~\\&|LAB|HOSPITAL|EHR|FACILITY|20240315150055||ORU^R01^ORU_R01|MSG001|P|2.5.1
|
|
62
|
+
PID|1||MRN123^^^HOSPITAL^MR||Doe^John^A||19800305|M
|
|
63
|
+
OBX|1|NM|718-7^Hemoglobin^LN||13.5|g/dL|13.5-17.5|N|||F`;
|
|
64
|
+
|
|
65
|
+
const msg = parse(raw);
|
|
66
|
+
|
|
67
|
+
// Convenience accessors on the top-level message object
|
|
68
|
+
console.log(msg.version); // '2.5.1'
|
|
69
|
+
console.log(msg.messageType); // { type: 'ORU', event: 'R01', structure: 'ORU_R01' }
|
|
70
|
+
console.log(msg.messageControlId); // 'MSG001'
|
|
71
|
+
console.log(msg.sendingApplication); // 'LAB'
|
|
72
|
+
|
|
73
|
+
// Generic query API — field numbers follow the HL7 spec directly
|
|
74
|
+
const mrn = get(msg, 'PID', 3, 1); // PID.3.1 → 'MRN123'
|
|
75
|
+
const lastName = get(msg, 'PID', 5, 1); // PID.5.1 → 'Doe'
|
|
76
|
+
const dob = get(msg, 'PID', 7); // PID.7 → '19800305'
|
|
77
|
+
|
|
78
|
+
// Typed segment helpers — IDE-friendly, no field numbers to memorise
|
|
79
|
+
const pid = new PID(segment(msg, 'PID'), msg.encoding);
|
|
80
|
+
const name = pid.patientName();
|
|
81
|
+
// → { family: 'Doe', given: 'John', middle: 'A', suffix: '', prefix: '', degree: '' }
|
|
82
|
+
|
|
83
|
+
// Iterate multiple OBX results
|
|
84
|
+
const obxList = segments(msg, 'OBX').map(s => new OBX(s, msg.encoding));
|
|
85
|
+
for (const obx of obxList) {
|
|
86
|
+
console.log(obx.observationIdentifier().description); // 'Hemoglobin'
|
|
87
|
+
console.log(obx.numericValue()); // 13.5
|
|
88
|
+
console.log(obx.units()); // 'g/dL'
|
|
89
|
+
console.log(obx.isFinal()); // true
|
|
90
|
+
console.log(obx.isCritical()); // false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Round-trip encode — byte-identical to the original input
|
|
94
|
+
const reEncoded = encode(msg);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Supported HL7 versions
|
|
100
|
+
|
|
101
|
+
All HL7 v2.x versions from **v2.1** through **v2.8** are supported. The parser reads the encoding characters from MSH.1/MSH.2 and handles any valid separator configuration, including non-default characters.
|
|
102
|
+
|
|
103
|
+
Common message types handled: ADT, ORU, ORM, OML, SIU, MDM, DFT, BAR, ACK, QRY, RSP.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## API reference
|
|
108
|
+
|
|
109
|
+
### `parse()`
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
function parse(raw: string, options?: ParseOptions): HL7Message
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Parse an HL7 v2 message string into a structured object.
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
|
|
119
|
+
| Parameter | Type | Description |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `raw` | `string` | The raw HL7 message. Supports `\r`, `\n`, or `\r\n` segment terminators. |
|
|
122
|
+
| `options.decodeEscapes` | `boolean` | Decode HL7 escape sequences in field values. Default: `false`. |
|
|
123
|
+
|
|
124
|
+
**Line endings**: `\r` (canonical HL7), `\n` (Unix), and `\r\n` (Windows) are all accepted and normalised internally. The detected line ending is preserved in `msg.lineEnding` and used by `encode()` to restore byte-identical output.
|
|
125
|
+
|
|
126
|
+
**Throws:**
|
|
127
|
+
- `InvalidHL7Error` — when the input is empty or does not start with `MSH`
|
|
128
|
+
- `HL7ParseError` — when a segment identifier or structure is malformed
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { parse } from '@pritiranjan/hl7v2';
|
|
132
|
+
|
|
133
|
+
const msg = parse(rawString);
|
|
134
|
+
// msg.version, msg.messageType, msg.segments, ...
|
|
135
|
+
|
|
136
|
+
// With escape decoding (values become human-readable; NOT safe for re-encoding)
|
|
137
|
+
const decoded = parse(rawString, { decodeEscapes: true });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### `encode()`
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
function encode(msg: HL7Message, options?: EncodeOptions): string
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Encode a parsed message back into a raw HL7 string.
|
|
149
|
+
|
|
150
|
+
**Options:**
|
|
151
|
+
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `lineEnding` | `'\r' \| '\n' \| '\r\n'` | `msg.lineEnding` | Segment separator |
|
|
155
|
+
| `trailingNewline` | `boolean` | `false` | Append a line ending after the final segment |
|
|
156
|
+
|
|
157
|
+
**Round-trip guarantee**: when called on a message parsed with default options (`decodeEscapes` not set to `true`), `encode(parse(raw))` is byte-identical to `raw.trim()`.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { parse, encode } from '@pritiranjan/hl7v2';
|
|
161
|
+
|
|
162
|
+
const msg = parse(rawString);
|
|
163
|
+
const back = encode(msg); // identical to input
|
|
164
|
+
const crlf = encode(msg, { lineEnding: '\r\n' }); // Windows style
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Query API
|
|
170
|
+
|
|
171
|
+
All field addresses are **1-based**, matching the HL7 specification directly.
|
|
172
|
+
|
|
173
|
+
#### `segment(msg, id, options?)`
|
|
174
|
+
|
|
175
|
+
Return the first matching segment. Throws `SegmentNotFoundError` if absent.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { segment } from '@pritiranjan/hl7v2';
|
|
179
|
+
|
|
180
|
+
const pid = segment(msg, 'PID');
|
|
181
|
+
|
|
182
|
+
// Access a specific occurrence when segment repeats (1-based)
|
|
183
|
+
const obx2 = segment(msg, 'OBX', { segmentIndex: 2 });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### `segments(msg, id)`
|
|
187
|
+
|
|
188
|
+
Return all matching segments as an array. Returns `[]`, never throws.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { segments } from '@pritiranjan/hl7v2';
|
|
192
|
+
|
|
193
|
+
const allOBX = segments(msg, 'OBX');
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### `hasSegment(msg, id)`
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { hasSegment } from '@pritiranjan/hl7v2';
|
|
200
|
+
|
|
201
|
+
if (hasSegment(msg, 'PV1')) { /* ... */ }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### `get(msg, segmentId, field, component?, subComponent?, options?)`
|
|
205
|
+
|
|
206
|
+
Extract a scalar string value. Returns `''` for missing elements — never throws.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { get } from '@pritiranjan/hl7v2';
|
|
210
|
+
|
|
211
|
+
get(msg, 'PID', 3) // PID.3 — patient identifier
|
|
212
|
+
get(msg, 'PID', 5, 1) // PID.5.1 — family name
|
|
213
|
+
get(msg, 'PID', 5, 2) // PID.5.2 — given name
|
|
214
|
+
get(msg, 'PID', 11, 5) // PID.11.5 — postal code
|
|
215
|
+
|
|
216
|
+
// Decode HL7 escape sequences in the returned value
|
|
217
|
+
get(msg, 'PID', 5, 1, 1, { decode: true })
|
|
218
|
+
|
|
219
|
+
// Second repetition (1-based)
|
|
220
|
+
get(msg, 'PID', 3, 1, 1, { repetition: 2 })
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### `getFromSegment(seg, msg, field, component?, subComponent?, options?)`
|
|
224
|
+
|
|
225
|
+
Like `get()` but operates on a pre-fetched `HL7Segment` — useful when iterating repeating segments.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { segments, getFromSegment } from '@pritiranjan/hl7v2';
|
|
229
|
+
|
|
230
|
+
for (const obx of segments(msg, 'OBX')) {
|
|
231
|
+
const value = getFromSegment(obx, msg, 5); // OBX.5
|
|
232
|
+
const loinc = getFromSegment(obx, msg, 3, 1); // OBX.3.1 — LOINC code
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### `getRepetitions(msg, segmentId, field, options?)`
|
|
237
|
+
|
|
238
|
+
Return all repetitions of a field as a `string[][][]` — one entry per repetition preserving the `[component][subComponent]` structure.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { getRepetitions } from '@pritiranjan/hl7v2';
|
|
242
|
+
|
|
243
|
+
// PID.3 repeats for multiple patient identifiers
|
|
244
|
+
const ids = getRepetitions(msg, 'PID', 3);
|
|
245
|
+
ids[0]?.[0]?.[0] // → 'MRN123' (first identifier value)
|
|
246
|
+
ids[0]?.[3]?.[0] // → 'HOSPITAL' (assigning authority, 0-indexed)
|
|
247
|
+
ids[1]?.[0]?.[0] // → 'SSN456' (second identifier value)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### Typed segment helpers
|
|
253
|
+
|
|
254
|
+
Import from `hl7v2/segments`. Each class wraps a raw `HL7Segment` and exposes named, documented accessor methods.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { parse, segment, segments } from '@pritiranjan/hl7v2';
|
|
258
|
+
import { MSH, PID, PV1, OBX } from '@pritiranjan/hl7v2/segments';
|
|
259
|
+
|
|
260
|
+
const msg = parse(raw);
|
|
261
|
+
|
|
262
|
+
// MSH — Message Header
|
|
263
|
+
const msh = new MSH(segment(msg, 'MSH'), msg.encoding);
|
|
264
|
+
msh.sendingApplication() // 'HOSPITAL_ADT'
|
|
265
|
+
msh.messageType() // { type: 'ADT', event: 'A01', structure: 'ADT_A01' }
|
|
266
|
+
msh.messageControlId() // 'MSG000001'
|
|
267
|
+
msh.version() // '2.5.1'
|
|
268
|
+
msh.processingId() // 'P'
|
|
269
|
+
|
|
270
|
+
// PID — Patient Identification
|
|
271
|
+
const pid = new PID(segment(msg, 'PID'), msg.encoding);
|
|
272
|
+
pid.patientName()
|
|
273
|
+
// → { family: 'Doe', given: 'John', middle: 'A', suffix: 'Jr', prefix: 'Mr', degree: 'MD' }
|
|
274
|
+
pid.patientIdentifiers()
|
|
275
|
+
// → [{ id: 'MRN123', assigningAuthority: 'HOSPITAL', identifierTypeCode: 'MR' }]
|
|
276
|
+
pid.dateOfBirth() // Date | undefined
|
|
277
|
+
pid.sex() // 'M' | 'F' | 'O' | 'U' | ...
|
|
278
|
+
pid.address()
|
|
279
|
+
// → { streetAddress: '123 Main St', city: 'Boston', state: 'MA', postalCode: '02101', ... }
|
|
280
|
+
|
|
281
|
+
// PV1 — Patient Visit
|
|
282
|
+
const pv1 = new PV1(segment(msg, 'PV1'), msg.encoding);
|
|
283
|
+
pv1.patientClass() // 'I' (Inpatient)
|
|
284
|
+
pv1.assignedLocation()
|
|
285
|
+
// → { pointOfCare: 'CARDIOLOGY', room: '4A', bed: '101', facility: 'HOSPITAL' }
|
|
286
|
+
pv1.attendingDoctor()
|
|
287
|
+
// → { id: '1234567', family: 'Smith', given: 'Richard', credential: 'MD' }
|
|
288
|
+
pv1.admitDateTime() // Date | undefined
|
|
289
|
+
|
|
290
|
+
// OBX — Observation / Lab Result
|
|
291
|
+
const obxList = segments(msg, 'OBX').map(s => new OBX(s, msg.encoding));
|
|
292
|
+
const obx = obxList[0]!;
|
|
293
|
+
obx.observationIdentifier()
|
|
294
|
+
// → { code: '718-7', description: 'Hemoglobin [Mass/volume] in Blood', codingSystem: 'LN' }
|
|
295
|
+
obx.numericValue() // 13.5 (only when valueType() === 'NM')
|
|
296
|
+
obx.units() // 'g/dL'
|
|
297
|
+
obx.referenceRange() // '13.5-17.5'
|
|
298
|
+
obx.abnormalFlags() // ['H'] | ['HH'] | ['N'] | []
|
|
299
|
+
obx.resultStatus() // 'F' | 'P' | 'C' | ...
|
|
300
|
+
obx.isFinal() // true
|
|
301
|
+
obx.isCritical() // false (true for HH or LL flags)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### Escape sequences
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { decodeEscapes, encodeEscapes } from '@pritiranjan/hl7v2';
|
|
310
|
+
import { DEFAULT_ENCODING } from '@pritiranjan/hl7v2';
|
|
311
|
+
|
|
312
|
+
// Decode HL7 escape sequences to their plain-text equivalents
|
|
313
|
+
decodeEscapes('Dr\\S\\Smith', DEFAULT_ENCODING) // 'Dr^Smith'
|
|
314
|
+
decodeEscapes('line1\\.br\\line2', DEFAULT_ENCODING) // 'line1\nline2'
|
|
315
|
+
decodeEscapes('\\X48656c6c6f\\', DEFAULT_ENCODING) // 'Hello'
|
|
316
|
+
|
|
317
|
+
// Encode a plain string for safe inclusion in an HL7 field
|
|
318
|
+
encodeEscapes('value|with^seps', DEFAULT_ENCODING) // 'value\\F\\with\\S\\seps'
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Supported sequences: `\F\`, `\S\`, `\T\`, `\R\`, `\E\`, `\H\`, `\N\`, `\.br\`, `\Xhh...\`.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
### Date / time utilities
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { parseHL7DateTime, formatHL7DateTime, formatHL7Date } from '@pritiranjan/hl7v2';
|
|
329
|
+
|
|
330
|
+
// Parse any HL7 date/time format → Date in UTC
|
|
331
|
+
parseHL7DateTime('20240315143022') // Date: 2024-03-15 14:30:22 UTC
|
|
332
|
+
parseHL7DateTime('20240315143022.456') // with milliseconds
|
|
333
|
+
parseHL7DateTime('20240315143022+0530') // with timezone offset → UTC
|
|
334
|
+
parseHL7DateTime('20240315') // date-only → midnight UTC
|
|
335
|
+
parseHL7DateTime('') // → undefined (empty = absent)
|
|
336
|
+
|
|
337
|
+
// Format a Date to HL7 strings
|
|
338
|
+
formatHL7DateTime(new Date()) // '20240315143022'
|
|
339
|
+
formatHL7Date(new Date()) // '20240315'
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## HL7Message structure
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
interface HL7Message {
|
|
348
|
+
version: string; // '2.5.1'
|
|
349
|
+
messageType: MessageType; // { type: 'ADT', event: 'A01', structure: 'ADT_A01' }
|
|
350
|
+
messageControlId: string; // 'MSG000001'
|
|
351
|
+
timestamp: Date | undefined;
|
|
352
|
+
sendingApplication: string; // MSH.3
|
|
353
|
+
sendingFacility: string; // MSH.4
|
|
354
|
+
receivingApplication: string; // MSH.5
|
|
355
|
+
receivingFacility: string; // MSH.6
|
|
356
|
+
processingId: string; // 'P' | 'D' | 'T'
|
|
357
|
+
segments: HL7Segment[];
|
|
358
|
+
encoding: EncodingChars;
|
|
359
|
+
raw: string; // original input, trimmed
|
|
360
|
+
lineEnding: '\r' | '\n' | '\r\n';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// A segment field: [repetition][component][subComponent]
|
|
364
|
+
type HL7Field = string[][][];
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Real-world examples
|
|
370
|
+
|
|
371
|
+
### Parse a lab result and alert on critical values
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { parse, segments } from '@pritiranjan/hl7v2';
|
|
375
|
+
import { OBX } from '@pritiranjan/hl7v2/segments';
|
|
376
|
+
|
|
377
|
+
function findCriticalResults(rawMessage: string) {
|
|
378
|
+
const msg = parse(rawMessage);
|
|
379
|
+
return segments(msg, 'OBX')
|
|
380
|
+
.map(s => new OBX(s, msg.encoding))
|
|
381
|
+
.filter(obx => obx.isCritical())
|
|
382
|
+
.map(obx => ({
|
|
383
|
+
code: obx.observationIdentifier().code,
|
|
384
|
+
description: obx.observationIdentifier().description,
|
|
385
|
+
value: obx.observationValue(),
|
|
386
|
+
units: obx.units(),
|
|
387
|
+
flag: obx.primaryAbnormalFlag(),
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Build an ACK response
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { parse, encode, get } from '@pritiranjan/hl7v2';
|
|
396
|
+
|
|
397
|
+
function buildAck(rawMessage: string, ackCode: 'AA' | 'AE' | 'AR'): string {
|
|
398
|
+
const msg = parse(rawMessage);
|
|
399
|
+
const now = new Date();
|
|
400
|
+
const ts = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, '0')}${String(now.getUTCDate()).padStart(2, '0')}`;
|
|
401
|
+
|
|
402
|
+
return [
|
|
403
|
+
`MSH|^~\\&|EHR|FACILITY|${msg.sendingApplication}|${msg.sendingFacility}|${ts}||ACK^${msg.messageType.event}|ACK${msg.messageControlId}|P|${msg.version}`,
|
|
404
|
+
`MSA|${ackCode}|${msg.messageControlId}`,
|
|
405
|
+
].join(msg.lineEnding);
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Extract all patient identifiers
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import { parse, getRepetitions } from '@pritiranjan/hl7v2';
|
|
413
|
+
|
|
414
|
+
function getPatientIds(rawMessage: string) {
|
|
415
|
+
const msg = parse(rawMessage);
|
|
416
|
+
return getRepetitions(msg, 'PID', 3).map(rep => ({
|
|
417
|
+
id: rep[0]?.[0] ?? '',
|
|
418
|
+
assigningAuthority: rep[3]?.[0] ?? '',
|
|
419
|
+
typeCode: rep[4]?.[0] ?? '',
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Contributing
|
|
427
|
+
|
|
428
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding conventions, and the pull request process.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## License
|
|
433
|
+
|
|
434
|
+
MIT © [Pritiranjan Swain](https://github.com/hkpritiranjan)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var HL7ParseError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "HL7ParseError";
|
|
6
|
+
this.line = options?.line;
|
|
7
|
+
this.segmentId = options?.segmentId;
|
|
8
|
+
if (options?.cause !== void 0) {
|
|
9
|
+
Object.defineProperty(this, "cause", {
|
|
10
|
+
value: options.cause,
|
|
11
|
+
writable: true,
|
|
12
|
+
configurable: true
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var SegmentNotFoundError = class extends Error {
|
|
18
|
+
constructor(segmentId) {
|
|
19
|
+
super(
|
|
20
|
+
`Segment '${segmentId}' not found in message. Use hasSegment() to check before calling segment(), or segments() to get an empty array when absent.`
|
|
21
|
+
);
|
|
22
|
+
this.name = "SegmentNotFoundError";
|
|
23
|
+
this.segmentId = segmentId;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var InvalidHL7Error = class extends Error {
|
|
27
|
+
constructor(reason) {
|
|
28
|
+
super(`Input is not a valid HL7 v2 message: ${reason}`);
|
|
29
|
+
this.name = "InvalidHL7Error";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/datetime.ts
|
|
34
|
+
function parseHL7DateTime(raw) {
|
|
35
|
+
const s = raw.trim();
|
|
36
|
+
if (!s) return void 0;
|
|
37
|
+
if (s.length < 4) {
|
|
38
|
+
throw new HL7ParseError(`Cannot parse HL7 date/time: "${s}" \u2014 minimum format is YYYY`);
|
|
39
|
+
}
|
|
40
|
+
let base = s;
|
|
41
|
+
let offsetMin = 0;
|
|
42
|
+
const tzMatch = /([+-])(\d{2})(\d{2})$/.exec(s);
|
|
43
|
+
if (tzMatch) {
|
|
44
|
+
const sign = tzMatch[1] === "+" ? 1 : -1;
|
|
45
|
+
const tzHours = parseInt(tzMatch[2] ?? "0", 10);
|
|
46
|
+
const tzMins = parseInt(tzMatch[3] ?? "0", 10);
|
|
47
|
+
offsetMin = sign * (tzHours * 60 + tzMins);
|
|
48
|
+
base = s.slice(0, s.length - 5);
|
|
49
|
+
}
|
|
50
|
+
let fracMs = 0;
|
|
51
|
+
const dotIdx = base.indexOf(".");
|
|
52
|
+
if (dotIdx !== -1) {
|
|
53
|
+
const fracStr = base.slice(dotIdx);
|
|
54
|
+
fracMs = Math.round(parseFloat(fracStr) * 1e3);
|
|
55
|
+
base = base.slice(0, dotIdx);
|
|
56
|
+
}
|
|
57
|
+
const year = parseInt(base.slice(0, 4), 10);
|
|
58
|
+
const month = parseInt(base.slice(4, 6) || "01", 10) - 1;
|
|
59
|
+
const day = parseInt(base.slice(6, 8) || "01", 10);
|
|
60
|
+
const hour = parseInt(base.slice(8, 10) || "00", 10);
|
|
61
|
+
const min = parseInt(base.slice(10, 12) || "00", 10);
|
|
62
|
+
const sec = parseInt(base.slice(12, 14) || "00", 10);
|
|
63
|
+
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
64
|
+
throw new HL7ParseError(`Cannot parse HL7 date/time: "${s}"`);
|
|
65
|
+
}
|
|
66
|
+
const utcMs = Date.UTC(year, month, day, hour, min, sec, fracMs) - offsetMin * 6e4;
|
|
67
|
+
const result = new Date(utcMs);
|
|
68
|
+
if (isNaN(result.getTime())) {
|
|
69
|
+
throw new HL7ParseError(`Cannot parse HL7 date/time: "${s}"`);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
function formatHL7DateTime(date) {
|
|
74
|
+
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
75
|
+
return pad(date.getUTCFullYear(), 4) + pad(date.getUTCMonth() + 1) + pad(date.getUTCDate()) + pad(date.getUTCHours()) + pad(date.getUTCMinutes()) + pad(date.getUTCSeconds());
|
|
76
|
+
}
|
|
77
|
+
function formatHL7Date(date) {
|
|
78
|
+
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
79
|
+
return pad(date.getUTCFullYear(), 4) + pad(date.getUTCMonth() + 1) + pad(date.getUTCDate());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/escape.ts
|
|
83
|
+
function escRx(s) {
|
|
84
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
85
|
+
}
|
|
86
|
+
function decodeEscapes(value, enc) {
|
|
87
|
+
const esc = enc.escape;
|
|
88
|
+
if (!value.includes(esc)) return value;
|
|
89
|
+
const pattern = new RegExp(`${escRx(esc)}([^${escRx(esc)}]*)${escRx(esc)}`, "g");
|
|
90
|
+
return value.replace(pattern, (match, seq) => {
|
|
91
|
+
switch (seq) {
|
|
92
|
+
case "F":
|
|
93
|
+
return enc.field;
|
|
94
|
+
case "S":
|
|
95
|
+
return enc.component;
|
|
96
|
+
case "T":
|
|
97
|
+
return enc.subComponent;
|
|
98
|
+
case "R":
|
|
99
|
+
return enc.repetition;
|
|
100
|
+
case "E":
|
|
101
|
+
return enc.escape;
|
|
102
|
+
case "H":
|
|
103
|
+
return "\x1B[1m";
|
|
104
|
+
case "N":
|
|
105
|
+
return "\x1B[0m";
|
|
106
|
+
case ".br":
|
|
107
|
+
return "\n";
|
|
108
|
+
default: {
|
|
109
|
+
if (seq.startsWith("X") && seq.length > 1) {
|
|
110
|
+
try {
|
|
111
|
+
const hex = seq.slice(1);
|
|
112
|
+
const bytes = new Uint8Array(hex.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? []);
|
|
113
|
+
return new TextDecoder().decode(bytes);
|
|
114
|
+
} catch {
|
|
115
|
+
return match;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return match;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function encodeEscapes(value, enc) {
|
|
124
|
+
const esc = enc.escape;
|
|
125
|
+
return value.replace(new RegExp(escRx(esc), "g"), `${esc}E${esc}`).replace(new RegExp(escRx(enc.field), "g"), `${esc}F${esc}`).replace(new RegExp(escRx(enc.component), "g"), `${esc}S${esc}`).replace(new RegExp(escRx(enc.repetition), "g"), `${esc}R${esc}`).replace(new RegExp(escRx(enc.subComponent), "g"), `${esc}T${esc}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { HL7ParseError, InvalidHL7Error, SegmentNotFoundError, decodeEscapes, encodeEscapes, formatHL7Date, formatHL7DateTime, parseHL7DateTime };
|
|
129
|
+
//# sourceMappingURL=chunk-Q6NISXHR.js.map
|
|
130
|
+
//# sourceMappingURL=chunk-Q6NISXHR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/datetime.ts","../src/escape.ts"],"names":[],"mappings":"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","file":"chunk-Q6NISXHR.js","sourcesContent":["/**\n * Thrown when an HL7 v2 message string cannot be parsed.\n *\n * The `line` and `segmentId` properties help pinpoint the exact failure location\n * when debugging multi-segment messages from production systems.\n */\nexport class HL7ParseError extends Error {\n /** 1-based line number in the source string where the error occurred. */\n readonly line: number | undefined;\n /** Segment identifier that was being parsed when the error occurred. */\n readonly segmentId: string | undefined;\n\n constructor(\n message: string,\n options?: { line?: number; segmentId?: string; cause?: unknown },\n ) {\n super(message);\n this.name = 'HL7ParseError';\n this.line = options?.line;\n this.segmentId = options?.segmentId;\n if (options?.cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: options.cause,\n writable: true,\n configurable: true,\n });\n }\n }\n}\n\n/**\n * Thrown when a required segment is not present in the message.\n *\n * @example\n * // Thrown by segment() when the segment is not found\n * const pid = segment(msg, 'PID'); // throws if no PID segment\n */\nexport class SegmentNotFoundError extends Error {\n readonly segmentId: string;\n\n constructor(segmentId: string) {\n super(\n `Segment '${segmentId}' not found in message. ` +\n `Use hasSegment() to check before calling segment(), ` +\n `or segments() to get an empty array when absent.`,\n );\n this.name = 'SegmentNotFoundError';\n this.segmentId = segmentId;\n }\n}\n\n/**\n * Thrown when an HL7 message cannot be identified as valid HL7 v2.\n * Usually means the input is empty, truncated, or a completely different format.\n */\nexport class InvalidHL7Error extends Error {\n constructor(reason: string) {\n super(`Input is not a valid HL7 v2 message: ${reason}`);\n this.name = 'InvalidHL7Error';\n }\n}\n","import { HL7ParseError } from './errors.js';\n\n/**\n * Parse an HL7 v2 Date/Time string into a JavaScript `Date`.\n *\n * Supported formats (per HL7 v2 spec section 2.8.21):\n * - `YYYY`\n * - `YYYYMM`\n * - `YYYYMMDD`\n * - `YYYYMMDDHHMM`\n * - `YYYYMMDDHHMMSS`\n * - `YYYYMMDDHHMMSS.S[SSS]`\n * - Any of the above with `+HHMM` or `-HHMM` timezone offset suffix\n *\n * @returns A `Date` object in UTC, or `undefined` if the value is empty.\n * @throws {HL7ParseError} If the string is non-empty but unparseable.\n */\nexport function parseHL7DateTime(raw: string): Date | undefined {\n const s = raw.trim();\n if (!s) return undefined;\n\n // Minimum valid length is 4 (year only)\n if (s.length < 4) {\n throw new HL7ParseError(`Cannot parse HL7 date/time: \"${s}\" — minimum format is YYYY`);\n }\n\n // Extract optional timezone offset: +HHMM or -HHMM at end\n let base = s;\n let offsetMin = 0;\n\n const tzMatch = /([+-])(\\d{2})(\\d{2})$/.exec(s);\n if (tzMatch) {\n const sign = tzMatch[1] === '+' ? 1 : -1;\n const tzHours = parseInt(tzMatch[2] ?? '0', 10);\n const tzMins = parseInt(tzMatch[3] ?? '0', 10);\n offsetMin = sign * (tzHours * 60 + tzMins);\n base = s.slice(0, s.length - 5);\n }\n\n // Parse fractional seconds: YYYYMMDDHHMMSS.SSSS\n let fracMs = 0;\n const dotIdx = base.indexOf('.');\n if (dotIdx !== -1) {\n const fracStr = base.slice(dotIdx);\n fracMs = Math.round(parseFloat(fracStr) * 1000);\n base = base.slice(0, dotIdx);\n }\n\n const year = parseInt(base.slice(0, 4), 10);\n const month = parseInt(base.slice(4, 6) || '01', 10) - 1; // 0-indexed\n const day = parseInt(base.slice(6, 8) || '01', 10);\n const hour = parseInt(base.slice(8, 10) || '00', 10);\n const min = parseInt(base.slice(10, 12)|| '00', 10);\n const sec = parseInt(base.slice(12, 14)|| '00', 10);\n\n if (isNaN(year) || isNaN(month) || isNaN(day)) {\n throw new HL7ParseError(`Cannot parse HL7 date/time: \"${s}\"`);\n }\n\n // Build UTC epoch, then subtract the timezone offset to normalise to UTC\n const utcMs = Date.UTC(year, month, day, hour, min, sec, fracMs) - offsetMin * 60_000;\n const result = new Date(utcMs);\n\n if (isNaN(result.getTime())) {\n throw new HL7ParseError(`Cannot parse HL7 date/time: \"${s}\"`);\n }\n\n return result;\n}\n\n/**\n * Format a JavaScript `Date` as an HL7 v2 timestamp string in UTC.\n * Output format: `YYYYMMDDHHMMSS`\n */\nexport function formatHL7DateTime(date: Date): string {\n const pad = (n: number, len = 2): string => String(n).padStart(len, '0');\n return (\n pad(date.getUTCFullYear(), 4) +\n pad(date.getUTCMonth() + 1) +\n pad(date.getUTCDate()) +\n pad(date.getUTCHours()) +\n pad(date.getUTCMinutes()) +\n pad(date.getUTCSeconds())\n );\n}\n\n/**\n * Format a JavaScript `Date` as an HL7 v2 date-only string in UTC.\n * Output format: `YYYYMMDD`\n */\nexport function formatHL7Date(date: Date): string {\n const pad = (n: number, len = 2): string => String(n).padStart(len, '0');\n return (\n pad(date.getUTCFullYear(), 4) +\n pad(date.getUTCMonth() + 1) +\n pad(date.getUTCDate())\n );\n}\n","import type { EncodingChars } from './schema.js';\n\n/** Escape a string for use in a regex character class. */\nfunction escRx(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Decode HL7 v2 escape sequences within a field value.\n *\n * Handles all standard sequences defined in the HL7 v2.x specification:\n * - `\\F\\` → field separator\n * - `\\S\\` → component separator\n * - `\\T\\` → sub-component separator\n * - `\\R\\` → repetition separator\n * - `\\E\\` → escape character\n * - `\\H\\` → start highlighting (stripped — presentation concern)\n * - `\\N\\` → normal text / end highlighting (stripped)\n * - `\\.br\\` → carriage return / line break → `\\n`\n * - `\\Xhh…\\` → hex-encoded bytes → UTF-8 string\n *\n * Unknown or locally-defined sequences (`\\Zxxx\\`) are preserved as-is.\n *\n * @param value - Raw field value that may contain escape sequences\n * @param enc - Encoding characters from the message's MSH segment\n */\nexport function decodeEscapes(value: string, enc: EncodingChars): string {\n const esc = enc.escape;\n if (!value.includes(esc)) return value;\n\n const pattern = new RegExp(`${escRx(esc)}([^${escRx(esc)}]*)${escRx(esc)}`, 'g');\n\n return value.replace(pattern, (match: string, seq: string): string => {\n switch (seq) {\n case 'F': return enc.field;\n case 'S': return enc.component;\n case 'T': return enc.subComponent;\n case 'R': return enc.repetition;\n case 'E': return enc.escape;\n case 'H': return '\\x1b[1m';\n case 'N': return '\\x1b[0m';\n case '.br': return '\\n';\n default: {\n if (seq.startsWith('X') && seq.length > 1) {\n try {\n const hex = seq.slice(1);\n const bytes = new Uint8Array(hex.match(/.{1,2}/g)?.map(b => parseInt(b, 16)) ?? []);\n return new TextDecoder().decode(bytes);\n } catch {\n return match;\n }\n }\n // Preserve unknown / locally-defined sequences unchanged\n return match;\n }\n }\n });\n}\n\n/**\n * Encode a plain string value for safe inclusion in an HL7 v2 field,\n * escaping any characters that have special meaning in the given encoding.\n *\n * Characters are escaped in this order to avoid double-escaping:\n * 1. Escape character itself (`\\E\\`)\n * 2. Field separator (`\\F\\`)\n * 3. Component separator (`\\S\\`)\n * 4. Repetition separator(`\\R\\`)\n * 5. Sub-component sep (`\\T\\`)\n *\n * @param value - Plain string to encode\n * @param enc - Encoding characters from the target message\n */\nexport function encodeEscapes(value: string, enc: EncodingChars): string {\n const esc = enc.escape;\n // Escape the escape character first to avoid double-escaping\n return value\n .replace(new RegExp(escRx(esc), 'g'), `${esc}E${esc}`)\n .replace(new RegExp(escRx(enc.field), 'g'), `${esc}F${esc}`)\n .replace(new RegExp(escRx(enc.component), 'g'), `${esc}S${esc}`)\n .replace(new RegExp(escRx(enc.repetition), 'g'), `${esc}R${esc}`)\n .replace(new RegExp(escRx(enc.subComponent), 'g'), `${esc}T${esc}`);\n}\n"]}
|