@cosyte/hl7 0.0.3 → 0.0.5
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/CHANGELOG.md +209 -0
- package/README.md +16 -5
- package/dist/index.cjs +147 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -11
- package/dist/index.d.ts +71 -11
- package/dist/index.mjs +147 -81
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -248,7 +248,8 @@ declare function missingRequiredField(position: Hl7Position, segmentName: string
|
|
|
248
248
|
* signature of a truncated or misrouted feed. Tier-2 and additive: lenient
|
|
249
249
|
* parse never throws on it, `strict` mode may promote it. The message carries
|
|
250
250
|
* only the structural fact (message type, group name, anchor segment names),
|
|
251
|
-
*
|
|
251
|
+
* never a field value: the message type is shape-checked before it is
|
|
252
|
+
* interpolated, and withheld if it does not match. `position` references MSH-9.
|
|
252
253
|
*
|
|
253
254
|
* @example
|
|
254
255
|
* ```ts
|
|
@@ -292,8 +293,8 @@ declare function versionMismatch(position: Hl7Position, declared: string, expect
|
|
|
292
293
|
* Table-0211 character set**. The parser never guesses an encoding: it reads the
|
|
293
294
|
* raw bytes as `latin1` (a 1:1 byte mapping) so single-byte content stays
|
|
294
295
|
* recoverable, rather than corrupting it with replacement characters. The
|
|
295
|
-
* message carries the charset code only, never a decoded field value
|
|
296
|
-
*
|
|
296
|
+
* message carries the charset code only, never a decoded field value: the code
|
|
297
|
+
* is shape-checked before it is interpolated, and withheld if it does not match.
|
|
297
298
|
*
|
|
298
299
|
* @example
|
|
299
300
|
* ```ts
|
|
@@ -315,8 +316,8 @@ declare function unknownCharset(position: Hl7Position, requested: string): Hl7Pa
|
|
|
315
316
|
* coincide with a structural delimiter: see the parser's known-limitations).
|
|
316
317
|
* The switch escapes (`\Cxxyy\` / `\Mxxyyzz\`) are recognized and preserved by
|
|
317
318
|
* the escape layer; full stateful decoding is a documented non-goal. The message
|
|
318
|
-
* carries the charset code only, never a decoded field value
|
|
319
|
-
*
|
|
319
|
+
* carries the charset code only, never a decoded field value: the code is
|
|
320
|
+
* shape-checked before it is interpolated, and withheld if it does not match.
|
|
320
321
|
*
|
|
321
322
|
* @example
|
|
322
323
|
* ```ts
|
|
@@ -2314,7 +2315,24 @@ declare class Field {
|
|
|
2314
2315
|
* ```
|
|
2315
2316
|
*/
|
|
2316
2317
|
declare class Segment {
|
|
2317
|
-
/**
|
|
2318
|
+
/**
|
|
2319
|
+
* Segment identifier: three characters with a leading letter, e.g. `"PID"`,
|
|
2320
|
+
* `"OBX"`, `"ZPI"`.
|
|
2321
|
+
*
|
|
2322
|
+
* **Bounded, and it is `"<withheld>"` when the raw name is not that shape.**
|
|
2323
|
+
* A line with no field separator has its whole content read as a segment
|
|
2324
|
+
* name, so an unescaped line break inside a narrative field forges a
|
|
2325
|
+
* "segment" whose name is clinical text. This field presents itself as a
|
|
2326
|
+
* structural identifier and consumers interpolate it into labels, loci and
|
|
2327
|
+
* reports, so it never carries that text. `""` still means absent, which is
|
|
2328
|
+
* a different fact from withheld.
|
|
2329
|
+
*
|
|
2330
|
+
* Use {@link Segment.raw}`.name` when you need the verbatim text: it is the
|
|
2331
|
+
* unbounded value, and it is what serialization emits, so the byte-verbatim
|
|
2332
|
+
* round-trip is unaffected. One consequence worth knowing: a segment whose
|
|
2333
|
+
* raw name fails the shape (a 4-character vendor Z-segment, which HL7 v2
|
|
2334
|
+
* Ch. 2 §2.5 does not permit) is not matched by `msg.segments(name)`.
|
|
2335
|
+
*/
|
|
2318
2336
|
readonly type: string;
|
|
2319
2337
|
/** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
|
|
2320
2338
|
readonly fields: readonly RawField[];
|
|
@@ -3602,9 +3620,19 @@ interface StructureGroup {
|
|
|
3602
3620
|
interface MessageStructure {
|
|
3603
3621
|
/** `true` when a `MESSAGE_STRUCTURE_DEFINITIONS` entry matched the type. */
|
|
3604
3622
|
readonly recognized: boolean;
|
|
3605
|
-
/**
|
|
3623
|
+
/**
|
|
3624
|
+
* MSH-9.1 message code observed on the message. `""` when absent, and
|
|
3625
|
+
* `"<withheld>"` when `recognized` is `false` and the observed value is not
|
|
3626
|
+
* identifier-shaped (MSH-9 can hold a data field on a malformed message).
|
|
3627
|
+
*/
|
|
3606
3628
|
readonly messageCode: string;
|
|
3607
|
-
/**
|
|
3629
|
+
/**
|
|
3630
|
+
* MSH-9.2 trigger event observed on the message. `""` when absent, and
|
|
3631
|
+
* `"<withheld>"` when `recognized` is `false` and the observed value is not
|
|
3632
|
+
* identifier-shaped. Note that on the `recognized` branch this is echoed
|
|
3633
|
+
* verbatim, which for a definition matching on message code alone (`ACK`)
|
|
3634
|
+
* means an arbitrary MSH-9.2.
|
|
3635
|
+
*/
|
|
3608
3636
|
readonly triggerEvent: string;
|
|
3609
3637
|
/** Per-expected-group presence verdicts (empty when unrecognized). */
|
|
3610
3638
|
readonly expectedGroups: readonly StructureGroup[];
|
|
@@ -3985,6 +4013,16 @@ declare class Hl7Message {
|
|
|
3985
4013
|
*/
|
|
3986
4014
|
readonly rawSegments: readonly RawSegment[];
|
|
3987
4015
|
readonly encodingCharacters: EncodingCharacters;
|
|
4016
|
+
/**
|
|
4017
|
+
* HL7 version the model asserts for this message, from MSH-12.1.1, e.g.
|
|
4018
|
+
* `"2.5"` or `"2.5.1"`.
|
|
4019
|
+
*
|
|
4020
|
+
* **Bounded, and it is `"<withheld>"` when MSH-12 does not hold a version.**
|
|
4021
|
+
* Same reasoning as {@link Segment.type}: it presents as an identifier and
|
|
4022
|
+
* consumers label with it. `meta.version` keeps MSH-12 exactly as it
|
|
4023
|
+
* arrived, and is what version-scoped field selection reads, so this bound
|
|
4024
|
+
* changes no parsing behaviour.
|
|
4025
|
+
*/
|
|
3988
4026
|
readonly version: string;
|
|
3989
4027
|
readonly warnings: readonly Hl7ParseWarning[];
|
|
3990
4028
|
readonly profile: {
|
|
@@ -4646,9 +4684,31 @@ type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
|
|
|
4646
4684
|
* snippet of the offending input so consumers can log actionable errors.
|
|
4647
4685
|
*
|
|
4648
4686
|
* @remarks
|
|
4649
|
-
*
|
|
4650
|
-
* the call site if required by your
|
|
4651
|
-
*
|
|
4687
|
+
* `snippet` may contain PHI when parsing real clinical messages, and the
|
|
4688
|
+
* library does not redact it: redact at the call site if required by your
|
|
4689
|
+
* compliance posture. **It is the only field carrying input verbatim and
|
|
4690
|
+
* unfiltered** (capped at 40 characters plus an ellipsis, but not shape-checked
|
|
4691
|
+
* in any way), so it is the one to redact first.
|
|
4692
|
+
*
|
|
4693
|
+
* `message` is bounded but not absolutely content-free, and the difference is
|
|
4694
|
+
* worth stating because `message` is what a logger prints by default and what
|
|
4695
|
+
* `stack` embeds. A token lifted from the input is echoed only when it matches
|
|
4696
|
+
* the form the spec defines for it: a three-character segment identifier, an
|
|
4697
|
+
* MSH-9 type, an MSH-12 version, or a charset label the closed Table 0211
|
|
4698
|
+
* actually contains. Anything else becomes `<withheld>`. So a message cannot
|
|
4699
|
+
* carry a field's value, but it can carry a residue of up to three characters
|
|
4700
|
+
* when a malformed line happens to look like a segment identifier. The other
|
|
4701
|
+
* shapes are narrower in practice than their patterns allow, because the
|
|
4702
|
+
* library only ever feeds them registry-matched values, but the patterns
|
|
4703
|
+
* themselves admit more (a message type up to 26 characters, a version up to
|
|
4704
|
+
* 14), which matters if you construct warnings yourself.
|
|
4705
|
+
*
|
|
4706
|
+
* Under `{ strict: true }` an escalated Tier-2 warning is thrown as this
|
|
4707
|
+
* error, carrying the warning's own bounded message and a `snippet` of the
|
|
4708
|
+
* **first 40 characters of the input**. That snippet is the head of the
|
|
4709
|
+
* message rather than the deviation's own segment, so it is usually the MSH
|
|
4710
|
+
* header; it is deliberately not re-pointed at the offending segment, since
|
|
4711
|
+
* doing so would move more clinical content into the unredacted field.
|
|
4652
4712
|
*
|
|
4653
4713
|
* @example
|
|
4654
4714
|
* ```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -248,7 +248,8 @@ declare function missingRequiredField(position: Hl7Position, segmentName: string
|
|
|
248
248
|
* signature of a truncated or misrouted feed. Tier-2 and additive: lenient
|
|
249
249
|
* parse never throws on it, `strict` mode may promote it. The message carries
|
|
250
250
|
* only the structural fact (message type, group name, anchor segment names),
|
|
251
|
-
*
|
|
251
|
+
* never a field value: the message type is shape-checked before it is
|
|
252
|
+
* interpolated, and withheld if it does not match. `position` references MSH-9.
|
|
252
253
|
*
|
|
253
254
|
* @example
|
|
254
255
|
* ```ts
|
|
@@ -292,8 +293,8 @@ declare function versionMismatch(position: Hl7Position, declared: string, expect
|
|
|
292
293
|
* Table-0211 character set**. The parser never guesses an encoding: it reads the
|
|
293
294
|
* raw bytes as `latin1` (a 1:1 byte mapping) so single-byte content stays
|
|
294
295
|
* recoverable, rather than corrupting it with replacement characters. The
|
|
295
|
-
* message carries the charset code only, never a decoded field value
|
|
296
|
-
*
|
|
296
|
+
* message carries the charset code only, never a decoded field value: the code
|
|
297
|
+
* is shape-checked before it is interpolated, and withheld if it does not match.
|
|
297
298
|
*
|
|
298
299
|
* @example
|
|
299
300
|
* ```ts
|
|
@@ -315,8 +316,8 @@ declare function unknownCharset(position: Hl7Position, requested: string): Hl7Pa
|
|
|
315
316
|
* coincide with a structural delimiter: see the parser's known-limitations).
|
|
316
317
|
* The switch escapes (`\Cxxyy\` / `\Mxxyyzz\`) are recognized and preserved by
|
|
317
318
|
* the escape layer; full stateful decoding is a documented non-goal. The message
|
|
318
|
-
* carries the charset code only, never a decoded field value
|
|
319
|
-
*
|
|
319
|
+
* carries the charset code only, never a decoded field value: the code is
|
|
320
|
+
* shape-checked before it is interpolated, and withheld if it does not match.
|
|
320
321
|
*
|
|
321
322
|
* @example
|
|
322
323
|
* ```ts
|
|
@@ -2314,7 +2315,24 @@ declare class Field {
|
|
|
2314
2315
|
* ```
|
|
2315
2316
|
*/
|
|
2316
2317
|
declare class Segment {
|
|
2317
|
-
/**
|
|
2318
|
+
/**
|
|
2319
|
+
* Segment identifier: three characters with a leading letter, e.g. `"PID"`,
|
|
2320
|
+
* `"OBX"`, `"ZPI"`.
|
|
2321
|
+
*
|
|
2322
|
+
* **Bounded, and it is `"<withheld>"` when the raw name is not that shape.**
|
|
2323
|
+
* A line with no field separator has its whole content read as a segment
|
|
2324
|
+
* name, so an unescaped line break inside a narrative field forges a
|
|
2325
|
+
* "segment" whose name is clinical text. This field presents itself as a
|
|
2326
|
+
* structural identifier and consumers interpolate it into labels, loci and
|
|
2327
|
+
* reports, so it never carries that text. `""` still means absent, which is
|
|
2328
|
+
* a different fact from withheld.
|
|
2329
|
+
*
|
|
2330
|
+
* Use {@link Segment.raw}`.name` when you need the verbatim text: it is the
|
|
2331
|
+
* unbounded value, and it is what serialization emits, so the byte-verbatim
|
|
2332
|
+
* round-trip is unaffected. One consequence worth knowing: a segment whose
|
|
2333
|
+
* raw name fails the shape (a 4-character vendor Z-segment, which HL7 v2
|
|
2334
|
+
* Ch. 2 §2.5 does not permit) is not matched by `msg.segments(name)`.
|
|
2335
|
+
*/
|
|
2318
2336
|
readonly type: string;
|
|
2319
2337
|
/** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
|
|
2320
2338
|
readonly fields: readonly RawField[];
|
|
@@ -3602,9 +3620,19 @@ interface StructureGroup {
|
|
|
3602
3620
|
interface MessageStructure {
|
|
3603
3621
|
/** `true` when a `MESSAGE_STRUCTURE_DEFINITIONS` entry matched the type. */
|
|
3604
3622
|
readonly recognized: boolean;
|
|
3605
|
-
/**
|
|
3623
|
+
/**
|
|
3624
|
+
* MSH-9.1 message code observed on the message. `""` when absent, and
|
|
3625
|
+
* `"<withheld>"` when `recognized` is `false` and the observed value is not
|
|
3626
|
+
* identifier-shaped (MSH-9 can hold a data field on a malformed message).
|
|
3627
|
+
*/
|
|
3606
3628
|
readonly messageCode: string;
|
|
3607
|
-
/**
|
|
3629
|
+
/**
|
|
3630
|
+
* MSH-9.2 trigger event observed on the message. `""` when absent, and
|
|
3631
|
+
* `"<withheld>"` when `recognized` is `false` and the observed value is not
|
|
3632
|
+
* identifier-shaped. Note that on the `recognized` branch this is echoed
|
|
3633
|
+
* verbatim, which for a definition matching on message code alone (`ACK`)
|
|
3634
|
+
* means an arbitrary MSH-9.2.
|
|
3635
|
+
*/
|
|
3608
3636
|
readonly triggerEvent: string;
|
|
3609
3637
|
/** Per-expected-group presence verdicts (empty when unrecognized). */
|
|
3610
3638
|
readonly expectedGroups: readonly StructureGroup[];
|
|
@@ -3985,6 +4013,16 @@ declare class Hl7Message {
|
|
|
3985
4013
|
*/
|
|
3986
4014
|
readonly rawSegments: readonly RawSegment[];
|
|
3987
4015
|
readonly encodingCharacters: EncodingCharacters;
|
|
4016
|
+
/**
|
|
4017
|
+
* HL7 version the model asserts for this message, from MSH-12.1.1, e.g.
|
|
4018
|
+
* `"2.5"` or `"2.5.1"`.
|
|
4019
|
+
*
|
|
4020
|
+
* **Bounded, and it is `"<withheld>"` when MSH-12 does not hold a version.**
|
|
4021
|
+
* Same reasoning as {@link Segment.type}: it presents as an identifier and
|
|
4022
|
+
* consumers label with it. `meta.version` keeps MSH-12 exactly as it
|
|
4023
|
+
* arrived, and is what version-scoped field selection reads, so this bound
|
|
4024
|
+
* changes no parsing behaviour.
|
|
4025
|
+
*/
|
|
3988
4026
|
readonly version: string;
|
|
3989
4027
|
readonly warnings: readonly Hl7ParseWarning[];
|
|
3990
4028
|
readonly profile: {
|
|
@@ -4646,9 +4684,31 @@ type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
|
|
|
4646
4684
|
* snippet of the offending input so consumers can log actionable errors.
|
|
4647
4685
|
*
|
|
4648
4686
|
* @remarks
|
|
4649
|
-
*
|
|
4650
|
-
* the call site if required by your
|
|
4651
|
-
*
|
|
4687
|
+
* `snippet` may contain PHI when parsing real clinical messages, and the
|
|
4688
|
+
* library does not redact it: redact at the call site if required by your
|
|
4689
|
+
* compliance posture. **It is the only field carrying input verbatim and
|
|
4690
|
+
* unfiltered** (capped at 40 characters plus an ellipsis, but not shape-checked
|
|
4691
|
+
* in any way), so it is the one to redact first.
|
|
4692
|
+
*
|
|
4693
|
+
* `message` is bounded but not absolutely content-free, and the difference is
|
|
4694
|
+
* worth stating because `message` is what a logger prints by default and what
|
|
4695
|
+
* `stack` embeds. A token lifted from the input is echoed only when it matches
|
|
4696
|
+
* the form the spec defines for it: a three-character segment identifier, an
|
|
4697
|
+
* MSH-9 type, an MSH-12 version, or a charset label the closed Table 0211
|
|
4698
|
+
* actually contains. Anything else becomes `<withheld>`. So a message cannot
|
|
4699
|
+
* carry a field's value, but it can carry a residue of up to three characters
|
|
4700
|
+
* when a malformed line happens to look like a segment identifier. The other
|
|
4701
|
+
* shapes are narrower in practice than their patterns allow, because the
|
|
4702
|
+
* library only ever feeds them registry-matched values, but the patterns
|
|
4703
|
+
* themselves admit more (a message type up to 26 characters, a version up to
|
|
4704
|
+
* 14), which matters if you construct warnings yourself.
|
|
4705
|
+
*
|
|
4706
|
+
* Under `{ strict: true }` an escalated Tier-2 warning is thrown as this
|
|
4707
|
+
* error, carrying the warning's own bounded message and a `snippet` of the
|
|
4708
|
+
* **first 40 characters of the input**. That snippet is the head of the
|
|
4709
|
+
* message rather than the deviation's own segment, so it is usually the MSH
|
|
4710
|
+
* header; it is deliberately not re-pointed at the offending segment, since
|
|
4711
|
+
* doing so would move more clinical content into the unredacted field.
|
|
4652
4712
|
*
|
|
4653
4713
|
* @example
|
|
4654
4714
|
* ```ts
|
package/dist/index.mjs
CHANGED
|
@@ -253,7 +253,104 @@ var KNOWN_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
253
253
|
"CSS"
|
|
254
254
|
]);
|
|
255
255
|
|
|
256
|
+
// src/parser/charset.ts
|
|
257
|
+
function normalizeLabel(raw) {
|
|
258
|
+
return raw.trim().replace(/\s+/gu, " ").toUpperCase();
|
|
259
|
+
}
|
|
260
|
+
function decodeEntry(canonical, decoder) {
|
|
261
|
+
return { canonical, treatment: "decode", decoder };
|
|
262
|
+
}
|
|
263
|
+
function verbatimEntry(canonical) {
|
|
264
|
+
return { canonical, treatment: "verbatim", decoder: "" };
|
|
265
|
+
}
|
|
266
|
+
function buildTable() {
|
|
267
|
+
const table = /* @__PURE__ */ new Map();
|
|
268
|
+
const add = (aliases, entry) => {
|
|
269
|
+
for (const alias of aliases) table.set(normalizeLabel(alias), entry);
|
|
270
|
+
};
|
|
271
|
+
add(["ASCII", "US-ASCII", "ISO IR6"], decodeEntry("ASCII", "utf-8"));
|
|
272
|
+
add(["UTF-8", "UTF8", "UNICODE UTF-8", "UNICODE"], decodeEntry("UTF-8", "utf-8"));
|
|
273
|
+
add(["8859/1", "ISO-8859-1", "ISO 8859-1", "ISO8859-1"], decodeEntry("8859/1", "latin1"));
|
|
274
|
+
for (const n of [2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16]) {
|
|
275
|
+
add(
|
|
276
|
+
[`8859/${n}`, `ISO-8859-${n}`, `ISO 8859-${n}`, `ISO8859-${n}`],
|
|
277
|
+
decodeEntry(`8859/${n}`, `iso-8859-${n}`)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
add(["8859/9", "ISO-8859-9", "ISO 8859-9", "ISO8859-9"], verbatimEntry("8859/9"));
|
|
281
|
+
add(["8859/11", "ISO-8859-11", "ISO 8859-11", "ISO8859-11"], verbatimEntry("8859/11"));
|
|
282
|
+
add(["ISO IR14", "JIS X 0201"], verbatimEntry("ISO IR14"));
|
|
283
|
+
add(["ISO IR87", "JIS X 0208"], verbatimEntry("ISO IR87"));
|
|
284
|
+
add(["ISO IR159", "JIS X 0212"], verbatimEntry("ISO IR159"));
|
|
285
|
+
add(["GB 18030-2000", "GB18030", "GB 18030"], verbatimEntry("GB 18030-2000"));
|
|
286
|
+
add(["KS X 1001", "KSX1001", "KS C 5601"], verbatimEntry("KS X 1001"));
|
|
287
|
+
add(["CNS 11643-1992", "CNS 11643"], verbatimEntry("CNS 11643-1992"));
|
|
288
|
+
add(["BIG-5", "BIG5"], verbatimEntry("BIG-5"));
|
|
289
|
+
add(["UNICODE UTF-16", "UTF-16"], verbatimEntry("UNICODE UTF-16"));
|
|
290
|
+
add(["UNICODE UTF-32", "UTF-32"], verbatimEntry("UNICODE UTF-32"));
|
|
291
|
+
return table;
|
|
292
|
+
}
|
|
293
|
+
var CHARSET_TABLE = buildTable();
|
|
294
|
+
var DEFAULT_RESOLUTION = Object.freeze({
|
|
295
|
+
canonical: "ASCII",
|
|
296
|
+
treatment: "decode",
|
|
297
|
+
decoder: "utf-8",
|
|
298
|
+
recognized: true
|
|
299
|
+
});
|
|
300
|
+
function resolveCharset(raw) {
|
|
301
|
+
if (raw === void 0) return DEFAULT_RESOLUTION;
|
|
302
|
+
const key = normalizeLabel(raw);
|
|
303
|
+
if (key === "") return DEFAULT_RESOLUTION;
|
|
304
|
+
const entry = CHARSET_TABLE.get(key);
|
|
305
|
+
if (entry !== void 0) {
|
|
306
|
+
return {
|
|
307
|
+
canonical: entry.canonical,
|
|
308
|
+
treatment: entry.treatment,
|
|
309
|
+
decoder: entry.decoder,
|
|
310
|
+
recognized: true
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return { canonical: key, treatment: "verbatim", decoder: "", recognized: false };
|
|
314
|
+
}
|
|
315
|
+
function canonicalCharset(raw) {
|
|
316
|
+
return resolveCharset(raw).canonical;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/parser/tokens.ts
|
|
320
|
+
var DERIVED_TOKEN_SHAPES = {
|
|
321
|
+
/**
|
|
322
|
+
* HL7 v2 Ch. 2 §2.5 segment identifier: three characters, the first
|
|
323
|
+
* alphabetic.
|
|
324
|
+
*
|
|
325
|
+
* Two deliberate tightenings, each measured rather than assumed. The first
|
|
326
|
+
* version allowed four characters as "vendor tolerance", which was invented
|
|
327
|
+
* rather than grounded: all 91 `KNOWN_SEGMENTS` are exactly three, and
|
|
328
|
+
* `defineProfile` independently validates custom Z-segments to
|
|
329
|
+
* `/^Z[A-Z0-9]{2}$/`, so nothing legitimate is longer. The second requires a
|
|
330
|
+
* leading letter, which every known segment and every legal Z-segment
|
|
331
|
+
* satisfies, and which excludes a **bare numeric** residue. That matters out
|
|
332
|
+
* of proportion to its size: a forged line reading `120` is a glucose, a
|
|
333
|
+
* blood pressure or a dose, which is the most clinical meaning three
|
|
334
|
+
* characters can carry.
|
|
335
|
+
*/
|
|
336
|
+
segmentId: /^[A-Za-z][A-Za-z0-9]{2}$/,
|
|
337
|
+
/** MSH-9 message type / trigger, e.g. `ADT^A01` or `A40`. */
|
|
338
|
+
messageType: /^[A-Za-z0-9]{1,8}(?:\^[A-Za-z0-9]{1,8}){0,2}$/,
|
|
339
|
+
/** MSH-12 version ID, e.g. `2.5` or `2.5.1`. */
|
|
340
|
+
version: /^[0-9]{1,2}(?:\.[0-9]{1,3}){0,3}$/
|
|
341
|
+
};
|
|
342
|
+
function safeDerivedToken(value, kind) {
|
|
343
|
+
return DERIVED_TOKEN_SHAPES[kind].test(value) ? value : WITHHELD;
|
|
344
|
+
}
|
|
345
|
+
var WITHHELD = "<withheld>";
|
|
346
|
+
function boundedIdentifier(value, kind) {
|
|
347
|
+
return value === "" ? "" : safeDerivedToken(value, kind);
|
|
348
|
+
}
|
|
349
|
+
|
|
256
350
|
// src/parser/warnings.ts
|
|
351
|
+
function safeCharsetLabel(value) {
|
|
352
|
+
return resolveCharset(value).recognized ? value : WITHHELD;
|
|
353
|
+
}
|
|
257
354
|
var WARNING_CODES = {
|
|
258
355
|
MLLP_FRAMING_STRIPPED: "MLLP_FRAMING_STRIPPED",
|
|
259
356
|
FIELD_WHITESPACE_TRIMMED: "FIELD_WHITESPACE_TRIMMED",
|
|
@@ -338,28 +435,28 @@ function timestampFallbackFormat(position, matchedFormat) {
|
|
|
338
435
|
function segmentCase(position, observed) {
|
|
339
436
|
return {
|
|
340
437
|
code: WARNING_CODES.SEGMENT_CASE,
|
|
341
|
-
message: `Segment identifier "${observed}" is not uppercase; normalized to "${observed.toUpperCase()}".`,
|
|
438
|
+
message: `Segment identifier "${safeDerivedToken(observed, "segmentId")}" is not uppercase; normalized to "${safeDerivedToken(observed.toUpperCase(), "segmentId")}".`,
|
|
342
439
|
position
|
|
343
440
|
};
|
|
344
441
|
}
|
|
345
442
|
function extraFields(position, segmentName2, extraCount) {
|
|
346
443
|
return {
|
|
347
444
|
code: WARNING_CODES.EXTRA_FIELDS,
|
|
348
|
-
message: `Segment "${segmentName2}" has ${String(extraCount)} more field(s) than the profile declares.`,
|
|
445
|
+
message: `Segment "${safeDerivedToken(segmentName2, "segmentId")}" has ${String(extraCount)} more field(s) than the profile declares.`,
|
|
349
446
|
position
|
|
350
447
|
};
|
|
351
448
|
}
|
|
352
449
|
function unknownSegment(position, segmentName2) {
|
|
353
450
|
return {
|
|
354
451
|
code: WARNING_CODES.UNKNOWN_SEGMENT,
|
|
355
|
-
message: `Unknown segment "${segmentName2}": not in HL7 spec and no profile claim.`,
|
|
452
|
+
message: `Unknown segment "${safeDerivedToken(segmentName2, "segmentId")}": not in HL7 spec and no profile claim.`,
|
|
356
453
|
position
|
|
357
454
|
};
|
|
358
455
|
}
|
|
359
456
|
function duplicateRequiredSegment(position, segmentName2) {
|
|
360
457
|
return {
|
|
361
458
|
code: WARNING_CODES.DUPLICATE_REQUIRED_SEGMENT,
|
|
362
|
-
message: `Required singleton segment "${segmentName2}" appears more than once.`,
|
|
459
|
+
message: `Required singleton segment "${safeDerivedToken(segmentName2, "segmentId")}" appears more than once.`,
|
|
363
460
|
position
|
|
364
461
|
};
|
|
365
462
|
}
|
|
@@ -373,42 +470,42 @@ function encodingMismatch(position, detail) {
|
|
|
373
470
|
function missingRequiredField(position, segmentName2, fieldIndex) {
|
|
374
471
|
return {
|
|
375
472
|
code: WARNING_CODES.MISSING_REQUIRED_FIELD,
|
|
376
|
-
message: `Required field ${segmentName2}-${String(fieldIndex)} is missing or empty.`,
|
|
473
|
+
message: `Required field ${safeDerivedToken(segmentName2, "segmentId")}-${String(fieldIndex)} is missing or empty.`,
|
|
377
474
|
position
|
|
378
475
|
};
|
|
379
476
|
}
|
|
380
477
|
function missingExpectedGroup(position, messageType, groupName, anchorSegments) {
|
|
381
478
|
return {
|
|
382
479
|
code: WARNING_CODES.MISSING_EXPECTED_GROUP,
|
|
383
|
-
message: `Message type "${messageType}" is missing its expected "${groupName}" segment group (no ${anchorSegments.join("/")}); message may be truncated or misrouted.`,
|
|
480
|
+
message: `Message type "${safeDerivedToken(messageType, "messageType")}" is missing its expected "${groupName}" segment group (no ${anchorSegments.join("/")}); message may be truncated or misrouted.`,
|
|
384
481
|
position
|
|
385
482
|
};
|
|
386
483
|
}
|
|
387
484
|
function outOfOrderSegment(position, segmentName2) {
|
|
388
485
|
return {
|
|
389
486
|
code: WARNING_CODES.OUT_OF_ORDER_SEGMENT,
|
|
390
|
-
message: `Segment "${segmentName2}" appears out of the order declared by the active profile.`,
|
|
487
|
+
message: `Segment "${safeDerivedToken(segmentName2, "segmentId")}" appears out of the order declared by the active profile.`,
|
|
391
488
|
position
|
|
392
489
|
};
|
|
393
490
|
}
|
|
394
491
|
function versionMismatch(position, declared, expected) {
|
|
395
492
|
return {
|
|
396
493
|
code: WARNING_CODES.VERSION_MISMATCH,
|
|
397
|
-
message: `HL7 version mismatch: message declares "${declared}", expected "${expected}".`,
|
|
494
|
+
message: `HL7 version mismatch: message declares "${safeDerivedToken(declared, "version")}", expected "${expected}".`,
|
|
398
495
|
position
|
|
399
496
|
};
|
|
400
497
|
}
|
|
401
498
|
function unknownCharset(position, requested) {
|
|
402
499
|
return {
|
|
403
500
|
code: WARNING_CODES.UNKNOWN_CHARSET,
|
|
404
|
-
message: `Unrecognized character set "${requested}"; not decoded, bytes read as latin1.`,
|
|
501
|
+
message: `Unrecognized character set "${safeCharsetLabel(requested)}"; not decoded, bytes read as latin1.`,
|
|
405
502
|
position
|
|
406
503
|
};
|
|
407
504
|
}
|
|
408
505
|
function unsupportedCharset(position, code) {
|
|
409
506
|
return {
|
|
410
507
|
code: WARNING_CODES.UNSUPPORTED_CHARSET,
|
|
411
|
-
message: `Character set "${code}" was not decoded; bytes read as latin1.`,
|
|
508
|
+
message: `Character set "${safeCharsetLabel(code)}" was not decoded; bytes read as latin1.`,
|
|
412
509
|
position
|
|
413
510
|
};
|
|
414
511
|
}
|
|
@@ -423,7 +520,7 @@ function mergeMissingPriorOrSurvivor(position, eventType, missing) {
|
|
|
423
520
|
const detail = missing === "prior" ? "no MRG segment carries a usable prior (non-surviving) identifier" : "no PID segment carries a usable surviving identifier";
|
|
424
521
|
return {
|
|
425
522
|
code: WARNING_CODES.MERGE_MISSING_PRIOR_OR_SURVIVOR,
|
|
426
|
-
message: `Identity event "${eventType}" is missing its ${missing} role: ${detail}. Surfacing what is present; the MRG->PID merge direction is not guessed.`,
|
|
523
|
+
message: `Identity event "${safeDerivedToken(eventType, "messageType")}" is missing its ${missing} role: ${detail}. Surfacing what is present; the MRG->PID merge direction is not guessed.`,
|
|
427
524
|
position
|
|
428
525
|
};
|
|
429
526
|
}
|
|
@@ -469,69 +566,6 @@ function emitIfFramed(result, emit, position) {
|
|
|
469
566
|
}
|
|
470
567
|
}
|
|
471
568
|
|
|
472
|
-
// src/parser/charset.ts
|
|
473
|
-
function normalizeLabel(raw) {
|
|
474
|
-
return raw.trim().replace(/\s+/gu, " ").toUpperCase();
|
|
475
|
-
}
|
|
476
|
-
function decodeEntry(canonical, decoder) {
|
|
477
|
-
return { canonical, treatment: "decode", decoder };
|
|
478
|
-
}
|
|
479
|
-
function verbatimEntry(canonical) {
|
|
480
|
-
return { canonical, treatment: "verbatim", decoder: "" };
|
|
481
|
-
}
|
|
482
|
-
function buildTable() {
|
|
483
|
-
const table = /* @__PURE__ */ new Map();
|
|
484
|
-
const add = (aliases, entry) => {
|
|
485
|
-
for (const alias of aliases) table.set(normalizeLabel(alias), entry);
|
|
486
|
-
};
|
|
487
|
-
add(["ASCII", "US-ASCII", "ISO IR6"], decodeEntry("ASCII", "utf-8"));
|
|
488
|
-
add(["UTF-8", "UTF8", "UNICODE UTF-8", "UNICODE"], decodeEntry("UTF-8", "utf-8"));
|
|
489
|
-
add(["8859/1", "ISO-8859-1", "ISO 8859-1", "ISO8859-1"], decodeEntry("8859/1", "latin1"));
|
|
490
|
-
for (const n of [2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16]) {
|
|
491
|
-
add(
|
|
492
|
-
[`8859/${n}`, `ISO-8859-${n}`, `ISO 8859-${n}`, `ISO8859-${n}`],
|
|
493
|
-
decodeEntry(`8859/${n}`, `iso-8859-${n}`)
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
add(["8859/9", "ISO-8859-9", "ISO 8859-9", "ISO8859-9"], verbatimEntry("8859/9"));
|
|
497
|
-
add(["8859/11", "ISO-8859-11", "ISO 8859-11", "ISO8859-11"], verbatimEntry("8859/11"));
|
|
498
|
-
add(["ISO IR14", "JIS X 0201"], verbatimEntry("ISO IR14"));
|
|
499
|
-
add(["ISO IR87", "JIS X 0208"], verbatimEntry("ISO IR87"));
|
|
500
|
-
add(["ISO IR159", "JIS X 0212"], verbatimEntry("ISO IR159"));
|
|
501
|
-
add(["GB 18030-2000", "GB18030", "GB 18030"], verbatimEntry("GB 18030-2000"));
|
|
502
|
-
add(["KS X 1001", "KSX1001", "KS C 5601"], verbatimEntry("KS X 1001"));
|
|
503
|
-
add(["CNS 11643-1992", "CNS 11643"], verbatimEntry("CNS 11643-1992"));
|
|
504
|
-
add(["BIG-5", "BIG5"], verbatimEntry("BIG-5"));
|
|
505
|
-
add(["UNICODE UTF-16", "UTF-16"], verbatimEntry("UNICODE UTF-16"));
|
|
506
|
-
add(["UNICODE UTF-32", "UTF-32"], verbatimEntry("UNICODE UTF-32"));
|
|
507
|
-
return table;
|
|
508
|
-
}
|
|
509
|
-
var CHARSET_TABLE = buildTable();
|
|
510
|
-
var DEFAULT_RESOLUTION = Object.freeze({
|
|
511
|
-
canonical: "ASCII",
|
|
512
|
-
treatment: "decode",
|
|
513
|
-
decoder: "utf-8",
|
|
514
|
-
recognized: true
|
|
515
|
-
});
|
|
516
|
-
function resolveCharset(raw) {
|
|
517
|
-
if (raw === void 0) return DEFAULT_RESOLUTION;
|
|
518
|
-
const key = normalizeLabel(raw);
|
|
519
|
-
if (key === "") return DEFAULT_RESOLUTION;
|
|
520
|
-
const entry = CHARSET_TABLE.get(key);
|
|
521
|
-
if (entry !== void 0) {
|
|
522
|
-
return {
|
|
523
|
-
canonical: entry.canonical,
|
|
524
|
-
treatment: entry.treatment,
|
|
525
|
-
decoder: entry.decoder,
|
|
526
|
-
recognized: true
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
return { canonical: key, treatment: "verbatim", decoder: "", recognized: false };
|
|
530
|
-
}
|
|
531
|
-
function canonicalCharset(raw) {
|
|
532
|
-
return resolveCharset(raw).canonical;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
569
|
// src/parser/normalize.ts
|
|
536
570
|
var CHARSET_POSITION = { segmentIndex: 0, fieldIndex: 18 };
|
|
537
571
|
function preserveVerbatim(input) {
|
|
@@ -926,8 +960,8 @@ function analyzeMessageStructure(messageCode, triggerEvent, presentSegmentNames)
|
|
|
926
960
|
if (def === void 0) {
|
|
927
961
|
return Object.freeze({
|
|
928
962
|
recognized: false,
|
|
929
|
-
messageCode,
|
|
930
|
-
triggerEvent,
|
|
963
|
+
messageCode: boundedIdentifier(messageCode, "segmentId"),
|
|
964
|
+
triggerEvent: boundedIdentifier(triggerEvent, "segmentId"),
|
|
931
965
|
expectedGroups: Object.freeze([]),
|
|
932
966
|
missingGroups: Object.freeze([])
|
|
933
967
|
});
|
|
@@ -2159,7 +2193,24 @@ var EMPTY_REP = Object.freeze({
|
|
|
2159
2193
|
|
|
2160
2194
|
// src/model/segment.ts
|
|
2161
2195
|
var Segment = class {
|
|
2162
|
-
/**
|
|
2196
|
+
/**
|
|
2197
|
+
* Segment identifier: three characters with a leading letter, e.g. `"PID"`,
|
|
2198
|
+
* `"OBX"`, `"ZPI"`.
|
|
2199
|
+
*
|
|
2200
|
+
* **Bounded, and it is `"<withheld>"` when the raw name is not that shape.**
|
|
2201
|
+
* A line with no field separator has its whole content read as a segment
|
|
2202
|
+
* name, so an unescaped line break inside a narrative field forges a
|
|
2203
|
+
* "segment" whose name is clinical text. This field presents itself as a
|
|
2204
|
+
* structural identifier and consumers interpolate it into labels, loci and
|
|
2205
|
+
* reports, so it never carries that text. `""` still means absent, which is
|
|
2206
|
+
* a different fact from withheld.
|
|
2207
|
+
*
|
|
2208
|
+
* Use {@link Segment.raw}`.name` when you need the verbatim text: it is the
|
|
2209
|
+
* unbounded value, and it is what serialization emits, so the byte-verbatim
|
|
2210
|
+
* round-trip is unaffected. One consequence worth knowing: a segment whose
|
|
2211
|
+
* raw name fails the shape (a 4-character vendor Z-segment, which HL7 v2
|
|
2212
|
+
* Ch. 2 §2.5 does not permit) is not matched by `msg.segments(name)`.
|
|
2213
|
+
*/
|
|
2163
2214
|
type;
|
|
2164
2215
|
/** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
|
|
2165
2216
|
fields;
|
|
@@ -2192,7 +2243,7 @@ var Segment = class {
|
|
|
2192
2243
|
*/
|
|
2193
2244
|
constructor(raw, enc, absoluteIndex, customFields) {
|
|
2194
2245
|
this.raw = raw;
|
|
2195
|
-
this.type = raw.name;
|
|
2246
|
+
this.type = boundedIdentifier(raw.name, "segmentId");
|
|
2196
2247
|
this.fields = raw.fields;
|
|
2197
2248
|
this.enc = enc;
|
|
2198
2249
|
this.absoluteIndex = absoluteIndex;
|
|
@@ -3883,6 +3934,16 @@ var Hl7Message = class {
|
|
|
3883
3934
|
*/
|
|
3884
3935
|
rawSegments;
|
|
3885
3936
|
encodingCharacters;
|
|
3937
|
+
/**
|
|
3938
|
+
* HL7 version the model asserts for this message, from MSH-12.1.1, e.g.
|
|
3939
|
+
* `"2.5"` or `"2.5.1"`.
|
|
3940
|
+
*
|
|
3941
|
+
* **Bounded, and it is `"<withheld>"` when MSH-12 does not hold a version.**
|
|
3942
|
+
* Same reasoning as {@link Segment.type}: it presents as an identifier and
|
|
3943
|
+
* consumers label with it. `meta.version` keeps MSH-12 exactly as it
|
|
3944
|
+
* arrived, and is what version-scoped field selection reads, so this bound
|
|
3945
|
+
* changes no parsing behaviour.
|
|
3946
|
+
*/
|
|
3886
3947
|
version;
|
|
3887
3948
|
warnings;
|
|
3888
3949
|
profile;
|
|
@@ -3953,7 +4014,7 @@ var Hl7Message = class {
|
|
|
3953
4014
|
constructor(init) {
|
|
3954
4015
|
this.rawSegments = init.segments;
|
|
3955
4016
|
this.encodingCharacters = init.encodingCharacters;
|
|
3956
|
-
this.version = init.version;
|
|
4017
|
+
this.version = boundedIdentifier(init.version, "version");
|
|
3957
4018
|
this.warnings = Object.freeze(init.warnings.slice());
|
|
3958
4019
|
this.profile = init.profile;
|
|
3959
4020
|
if (init.customSegments !== void 0) this._customSegments = init.customSegments;
|
|
@@ -4791,7 +4852,12 @@ function resolveBufferCharset(raw, options, emit) {
|
|
|
4791
4852
|
emit(
|
|
4792
4853
|
encodingMismatch(
|
|
4793
4854
|
{ segmentIndex: 0 },
|
|
4794
|
-
`
|
|
4855
|
+
// `declared` is MSH-18 read off the wire. On a message with no
|
|
4856
|
+
// segment terminators it is not MSH-18 at all but the 18th
|
|
4857
|
+
// `|`-token of the flattened message, commonly a data field, so it
|
|
4858
|
+
// is shape-checked before it reaches a log surface. `override` is
|
|
4859
|
+
// the caller's own `options.charset`, not input, and is left alone.
|
|
4860
|
+
`options.charset="${override}" disagrees with MSH-18="${safeCharsetLabel(declared)}"`
|
|
4795
4861
|
)
|
|
4796
4862
|
);
|
|
4797
4863
|
}
|
|
@@ -6806,7 +6872,7 @@ async function* parseStream(source, optionsOrProfile) {
|
|
|
6806
6872
|
}
|
|
6807
6873
|
|
|
6808
6874
|
// src/index.ts
|
|
6809
|
-
var VERSION = "0.0.
|
|
6875
|
+
var VERSION = "0.0.5";
|
|
6810
6876
|
|
|
6811
6877
|
export { ACK_CODES, ACK_CONDITIONS, BUILTIN_DATE_FALLBACKS, DEFAULT_ENCODING_CHARACTERS, ERR_CONDITION_CODES, ERR_CONDITION_CODE_SYSTEM, ERR_SEVERITIES, FATAL_CODES, FINDING_CODES, Field, namespace_exports as HL7, Hl7Message, Hl7ParseError, KNOWN_CODING_SYSTEMS, KNOWN_SEGMENTS, MESSAGE_STRUCTURE_DEFINITIONS, ProfileDefinitionError, SUPPORTED_DATE_TOKENS, Segment, USAGE_CODES, VERSION, WARNING_CODES, ackNoCorrelationId, alternateCodingSystemOf, analyzeMessageStructure, batchCountMismatch, batchMissingTrailer, buildAck, buildAdt, buildMessage, buildOru, canonicalCharset, codingSystem, codingSystemOf, conformance_exports as conformance, decodeText, defineConformanceProfile, defineProfile, detectAckMode, downgradePositiveAck, dtmToDate, duplicateRequiredSegment, encodeCe, encodeComposite, encodeCompositeReps, encodeCwe, encodeCx, encodeHd, encodeNm, encodePl, encodeText, encodeTs, encodeXad, encodeXcn, encodeXpn, encodeXtn, encodingMismatch, extraFields, fieldWhitespaceTrimmed, formatDtm, getDefaultProfile, interpretAck, isPositiveAck, mergeMissingPriorOrSurvivor, missingExpectedGroup, missingRequiredField, mllpFramingStripped, outOfOrderSegment, parseCe, parseCwe, parseCx, parseDtm, parseHL7, parseHd, parseNm, parsePath, parsePl, parseSn, parseStream, parseTs, parseXad, parseXcn, parseXpn, parseXtn, pickMrn, profiles, reescape, renderText, resolveCharset, resolvePath, segmentCase, setDefaultProfile, splitBatch, text_exports as text, timestampFallbackFormat, unescape, unknownCharset, unknownEscapeSequence, unknownSegment, unsupportedCharset, unterminatedEscapeSequence, unterminatedStreamMessage, validateAgainstProfile, versionMismatch };
|
|
6812
6878
|
//# sourceMappingURL=index.mjs.map
|