@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 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
+ [![npm version](https://img.shields.io/npm/v/@pritiranjan/hl7v2.svg)](https://www.npmjs.com/package/@pritiranjan/hl7v2)
6
+ [![CI](https://github.com/hkpritiranjan/hl7v2/actions/workflows/ci.yml/badge.svg)](https://github.com/hkpritiranjan/hl7v2/actions)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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"]}