@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/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,45 @@ per the cosyte version ladder (`0.0.x` until first alpha).
|
|
|
15
15
|
|
|
16
16
|
### Added
|
|
17
17
|
|
|
18
|
+
- **The cosyte brand banner heads `README.md` (`ASSETS-P8`, the consuming half).** A plain markdown
|
|
19
|
+
image on the first line of the file, above the H1, pointing at the absolute HTTPS URL published
|
|
20
|
+
for `hl7` in the `assets` repo's `published-urls.json` contract
|
|
21
|
+
(`https://cosyte.com/social/cosyte-banner-hl7-1200x300.png`, `status: live` on
|
|
22
|
+
`website#59 01f988b`). Re-verified `200 image/png`, 18316 bytes, immediately before the push.
|
|
23
|
+
|
|
24
|
+
**The shape is deliberate, and `hl7` sets it for the other thirteen consuming READMEs.** It is a
|
|
25
|
+
markdown image, **not `<img>` and not `<picture>`**: whether npm's markdown sanitizer preserves a
|
|
26
|
+
`<picture>` element is unverified, which is precisely why the banner artwork is self-grounded
|
|
27
|
+
(opaque ground, correct against a light or dark page) and depends on no such element. A markdown
|
|
28
|
+
image with an absolute HTTPS URL is the construct we are willing to assert renders on both npm and
|
|
29
|
+
GitHub, so it carries no width or height attributes either. **PNG only** is a bounded policy (the
|
|
30
|
+
format we assert renders on every README surface), not a demonstrated impossibility for others.
|
|
31
|
+
The **alt text is content, not decoration**: it names the package and its one-line purpose,
|
|
32
|
+
because it is what a screen reader on the npm page reads out, so "banner", "logo", or the file
|
|
33
|
+
name would all be a wasted line.
|
|
34
|
+
|
|
35
|
+
**SUPERSEDED on 2026-07-31 by `1aee04b`, which replaced this banner with the shared Cosyte lockup
|
|
36
|
+
in a `<picture>` block.** The two paragraphs above are left exactly as they shipped rather than
|
|
37
|
+
corrected, because they were true of the tree and right on the evidence available when they were
|
|
38
|
+
written, and they record a real state the package passed through. This note says what changed
|
|
39
|
+
under them. It shipped verbatim inside the `@cosyte/hl7@0.0.4` tarball, so it is text a consumer
|
|
40
|
+
already has on disk.
|
|
41
|
+
|
|
42
|
+
**The claim the new evidence overturns is the second one: "whether npm's markdown sanitizer
|
|
43
|
+
preserves a `<picture>` element is unverified".** That is measured now. The first claim, that the
|
|
44
|
+
banner is a markdown image and "not `<img>` and not `<picture>`", was a consequence of the second
|
|
45
|
+
rather than an independent choice, so it went when its premise did; it is no longer true of the
|
|
46
|
+
tree either way, since `README.md` now opens with a `<picture>`. What was measured: **GitHub
|
|
47
|
+
honours the `prefers-color-scheme` switch**, verified on `@cosyte/astm` in dark mode, where the
|
|
48
|
+
rendered image's `currentSrc` resolves to the on-dark tile and its parent element is `PICTURE`.
|
|
49
|
+
**On the npm package page the `<img>` is hoisted out of its `<picture>` by the anchor wrapper**,
|
|
50
|
+
so the inner fallback renders rather than the element being stripped or broken; and **npmjs.com
|
|
51
|
+
has no dark mode** (founder-confirmed), so the light cut is the correct image there regardless.
|
|
52
|
+
The GitHub and npm measurements above were taken on `astm` and reported into this repo rather
|
|
53
|
+
than re-taken here; what was re-checked directly for `hl7` was that both tile URLs return
|
|
54
|
+
`200 image/png`. **PNG only** and **alt text is content, not decoration** are both unchanged and
|
|
55
|
+
still hold; only the element choice and its stated premise moved.
|
|
56
|
+
|
|
18
57
|
- **The published JSDoc is swept, and `src/` doc comments are gated
|
|
19
58
|
(`pnpm check:no-internal-refs` third pass; `PUBLIC-SURFACE-HYGIENE`).** Doc comments compile
|
|
20
59
|
into `dist/index.d.ts` and `dist/index.d.cts`, `dist` is the first entry in `files`, and every
|
|
@@ -397,6 +436,29 @@ kind, value)`** sets one at a field or `field[rep]` dot-path. Component values a
|
|
|
397
436
|
|
|
398
437
|
### Changed
|
|
399
438
|
|
|
439
|
+
- **`README.md` now opens with the shared Cosyte lockup in a `<picture>` block, replacing the
|
|
440
|
+
per-package banner (`1aee04b`).** The dark-ground org tile sits behind a
|
|
441
|
+
`prefers-color-scheme: dark` media query with the light-ground tile as the inner `<img>`, so on a
|
|
442
|
+
renderer that honours the switch the mark sits on a ground matching the page it is read on. One
|
|
443
|
+
markdown image line out, four lines in: the `# @cosyte/hl7` H1, the blockquote tagline, the badge
|
|
444
|
+
row and everything below are untouched.
|
|
445
|
+
|
|
446
|
+
**Why the per-package banner goes.** It baked the package name and the one-line tagline into
|
|
447
|
+
pixels, and the two lines directly beneath it repeated both. The shared lockup reads "Cosyte"
|
|
448
|
+
while the H1 reads `@cosyte/hl7`, so the strings differ, the duplication goes away, and the
|
|
449
|
+
heading stays.
|
|
450
|
+
|
|
451
|
+
**The failure mode is safe**: a renderer that strips `<source>` renders the inner `<img>`, so the
|
|
452
|
+
worst case is a light-ground mark on a dark page, never a missing or broken image. The block was
|
|
453
|
+
copied out of `astm/README.md` rather than retyped and the two URLs then diffed byte for byte
|
|
454
|
+
against that file, because a transcription error in one of them is a broken image on a public
|
|
455
|
+
package page. Both were re-verified `200 image/png` (10513 and 10455 bytes) immediately before
|
|
456
|
+
the push: the `live` flag in the `assets` manifest is a declaration made on evidence from another
|
|
457
|
+
repo, not a fact checked here. The alt text describes the mark rather than the package, since it
|
|
458
|
+
is what a screen reader on the npm page reads out and what a reader gets when the image fails.
|
|
459
|
+
This supersedes the `ASSETS-P8` banner entry under **Added** above, which is annotated in place
|
|
460
|
+
rather than rewritten. Documentation only: no runtime, API or packaging change.
|
|
461
|
+
|
|
400
462
|
- **Corrected stale "not yet published to npm / unpublished" language in the docs site
|
|
401
463
|
(README-ORG-SWEEP).** `@cosyte/hl7` is now published on npm at `0.0.1` and public, so the
|
|
402
464
|
`docs-content/installation.md` Status blockquote ("not yet published… the shape it will take at
|
|
@@ -433,6 +495,153 @@ kind, value)`** sets one at a field or `field[rep]` dot-path. Component values a
|
|
|
433
495
|
|
|
434
496
|
### Fixed
|
|
435
497
|
|
|
498
|
+
- **A warning or error message could carry clinical text into a log line (`HL7-WARN-MSG-PHI`).**
|
|
499
|
+
No options were required to reach it: plain `parseHL7(raw)` on lenient defaults.
|
|
500
|
+
|
|
501
|
+
**The mechanism.** `normalize()` rewrites every `\r\n` and `\n` in the input to `\r` before
|
|
502
|
+
segment splitting, unconditionally and with no field awareness. HL7 v2 Ch. 2 fixes the segment
|
|
503
|
+
terminator as `<CR>` and provides `\.br\` (§2.7.6) precisely because a literal line break cannot
|
|
504
|
+
appear inside a field, but real senders emit raw breaks inside narrative fields (`OBX-5`, notes,
|
|
505
|
+
embedded documents), which is exactly the class of sender violation this parser exists to
|
|
506
|
+
tolerate. When one occurs, the remainder of that field becomes a forged segment, and `tokenize()`
|
|
507
|
+
takes the whole line as its segment identifier because a line with no field separator offers no
|
|
508
|
+
other candidate. `UNKNOWN_SEGMENT` then interpolated that "identifier" uncapped, putting synthetic
|
|
509
|
+
name, date of birth and medical record number into `Hl7ParseWarning.message`. Under
|
|
510
|
+
`{ strict: true }` the same text reached the thrown `Hl7ParseError.message`, and therefore
|
|
511
|
+
`.stack`.
|
|
512
|
+
|
|
513
|
+
**The disclosure was inverted, which is what made it consequential.** Guidance said to redact
|
|
514
|
+
`Hl7ParseError.snippet`. On the strict path `snippet` holds the head of the message (usually the
|
|
515
|
+
MSH header) while `message` held the payload, so a consumer following the documented mitigation
|
|
516
|
+
exactly still logged the data. `message` is also the field a logger prints by default and the one
|
|
517
|
+
`stack` embeds.
|
|
518
|
+
|
|
519
|
+
**The fix: bound at the factory, not at the call site.** A token taken from input is echoed only
|
|
520
|
+
when it matches the form the spec defines for it, and becomes `<withheld>` otherwise. A length cap
|
|
521
|
+
was rejected because truncating still emits the first N characters of a patient name. Segment
|
|
522
|
+
identifiers are held to exactly three alphanumerics (HL7 v2 Ch. 2 §2.5), with equivalent forms for
|
|
523
|
+
MSH-9 types and MSH-12 versions. Conforming tokens render byte-identically, so `PID`, `ZZZ` and
|
|
524
|
+
every legitimate Z-segment name are unchanged and well-formed input sees no behaviour change:
|
|
525
|
+
1653 existing tests pass untouched. Placing the check in the factories rather than at the one live
|
|
526
|
+
call site also covers the six factories currently exported but unreached, and consumers who
|
|
527
|
+
construct warnings themselves.
|
|
528
|
+
|
|
529
|
+
**Charset labels are bounded by registry membership, not by spelling, and that distinction was
|
|
530
|
+
earned the hard way.** The first version of this fix used a shape test for them too, and a refuter
|
|
531
|
+
broke it: the shape admitted all five of the PHI-shaped markers this repo's own property suite
|
|
532
|
+
defines, including an 18-character patient name. Table 0211 is a **closed** table and
|
|
533
|
+
`UNKNOWN_CHARSET` fires precisely when a label is absent from it, so at the moment of the warning
|
|
534
|
+
there is no spec-defined spelling left to test against, and `UNICODE UTF-8` and a patient name are
|
|
535
|
+
shape-siblings. No regex can separate them. A label is now echoed only when `resolveCharset`
|
|
536
|
+
recognises it.
|
|
537
|
+
|
|
538
|
+
**What is bounded, stated without overclaiming.** `snippet` is the only field carrying input
|
|
539
|
+
verbatim and unbounded, so it is the one to redact first. A message cannot carry a field's value,
|
|
540
|
+
but it can carry a residue of up to three characters when a malformed line happens to look like a
|
|
541
|
+
segment identifier. No absolute "never carries field content" claim is made anywhere in this
|
|
542
|
+
package, because the code cannot honour one.
|
|
543
|
+
|
|
544
|
+
**Two adjacent surfaces bounded in the same pass.** `UNKNOWN_CHARSET` and `ENCODING_MISMATCH`
|
|
545
|
+
interpolate a value read as MSH-18, which on a message carrying no segment terminators is not
|
|
546
|
+
MSH-18 at all but the 18th `|`-token of the flattened input, commonly a data field. That path is
|
|
547
|
+
Buffer-only, and `parseStream` and `splitBatch` re-encode each split message to a Buffer, which is
|
|
548
|
+
the ordinary MLLP and file ingestion shape, so it is not an exotic corner.
|
|
549
|
+
`MISSING_EXPECTED_GROUP` and `MERGE_MISSING_PRIOR_OR_SURVIVOR` interpolate MSH-9.
|
|
550
|
+
|
|
551
|
+
**The test gap is the real finding.** The PHI property suite could not reach this position: every
|
|
552
|
+
generated marker sat after a `|`, so it was always a field value to the tokenizer, while the only
|
|
553
|
+
position that leaks is a line's **leading** token. A green suite over an unreachable case is
|
|
554
|
+
indistinguishable from a correct one. New generators emit the real trigger (a field-internal `\n`
|
|
555
|
+
and `\r\n`) across the lenient and strict arms, asserting on `.stack` separately from `.message`
|
|
556
|
+
because most error reporters ship the stack off-box, and pinning the escalated code on both
|
|
557
|
+
strict properties so neither can pass vacuously if some other warning escalates first. A second
|
|
558
|
+
set covers the Buffer/MSH-18 path, which every string-input generator in the suite structurally
|
|
559
|
+
misses, and a third asserts the invariant on the parsed **model**. All ten new tests fail on the
|
|
560
|
+
code they were written against and pass on this one.
|
|
561
|
+
|
|
562
|
+
**The same lesson applied to this fix's own first draft, which is why it now bounds the model
|
|
563
|
+
too.** Bounding the warning message protected this package's diagnostics and nothing else.
|
|
564
|
+
`Segment.type` still carried the entire forged line, so a consumer that reads the model and builds
|
|
565
|
+
its own report from that "identifier" wrote clinical narrative into it, on lenient defaults, with
|
|
566
|
+
no options set. **A diagnostic-surface fix does not protect a downstream package that reads your
|
|
567
|
+
model and builds its own diagnostics from it. Bounding at the model protects both.**
|
|
568
|
+
`Segment.type`, `Hl7Message.version` and the unrecognized branch of `MessageStructure` now carry
|
|
569
|
+
bounded identifiers, and the shared primitive moved to `src/parser/tokens.ts` so the parser and the
|
|
570
|
+
model use one bound rather than two.
|
|
571
|
+
|
|
572
|
+
**One bound deliberately not taken, named rather than left implicit.** On the _recognized_ branch
|
|
573
|
+
of `MessageStructure`, `triggerEvent` is echoed verbatim, and a definition matching on message code
|
|
574
|
+
alone (`ACK`) accepts an arbitrary MSH-9.2. Left unbounded on purpose: no real sender putting
|
|
575
|
+
narrative in MSH-9.2 is grounded in a de-identified document, and widening the guard there is a
|
|
576
|
+
separate change, not a silent extension of this one. An earlier comment in that file asserted the
|
|
577
|
+
branch returned registry values and could not carry input, which was false; the claim is corrected
|
|
578
|
+
rather than the guard grown.
|
|
579
|
+
|
|
580
|
+
**What stays unbounded, deliberately, because bounding it would be a mis-parse.** The model
|
|
581
|
+
separates **identifiers** from **values**. `Meta.*` is the documented value view of the MSH header;
|
|
582
|
+
`Segment.fields`, `Segment.raw` and `Hl7Message.rawSegments` are the raw tokenization the
|
|
583
|
+
byte-verbatim round-trip serializes from; `toString()` / `toJSON()` / `prettyPrint()` are the
|
|
584
|
+
message itself. Those are content surfaces and say so. `meta.version` in particular keeps MSH-12
|
|
585
|
+
exactly as it arrived and is what the v2.7 MRG field-scoping reads, so bounding
|
|
586
|
+
`Hl7Message.version` changes no clinical field selection.
|
|
587
|
+
|
|
588
|
+
**Docs corrected in both directions.** `docs-content/troubleshooting.md` asserted that warning
|
|
589
|
+
messages never echo a field's value, an overclaim; it and the equivalent assurances in
|
|
590
|
+
`normalize.ts` and `warnings.ts` now state the check that makes it true. The `Hl7ParseError`
|
|
591
|
+
disclosure now says `snippet` is the field to redact first, rather than claiming a message is
|
|
592
|
+
clean. `README.md` called the snippet an excerpt of "the offending input" when it is always
|
|
593
|
+
taken from the start of the message; the wording is corrected rather than the behaviour, because
|
|
594
|
+
re-pointing the snippet at the deviation's own segment would move more clinical content into the
|
|
595
|
+
single unredacted field.
|
|
596
|
+
|
|
597
|
+
**Not a mis-parse.** Parsed values were always correct; this was a diagnostic-surface leak.
|
|
598
|
+
Consumers on any published version up to and including `0.0.3` should treat
|
|
599
|
+
`Hl7ParseWarning.message` and `Hl7ParseError.message` as potentially carrying field content for
|
|
600
|
+
feeds they do not control until they upgrade.
|
|
601
|
+
|
|
602
|
+
- **Stale counts in `docs-content/`, found while correcting the PHI wording.** The warning-code
|
|
603
|
+
count was given as 19 in three places (it is 20), and two pages quoted a published npm version
|
|
604
|
+
that was two releases stale. Both now point at the registry rather than restating it, which is the
|
|
605
|
+
only way a doc page stops drifting from it.
|
|
606
|
+
|
|
607
|
+
- **Four false claims in the README's feature list, each re-derived from source rather than from
|
|
608
|
+
memory (`README-CLAIM-DRIFT`).** Every number and version below was measured against the thing it
|
|
609
|
+
describes, in the order a consumer would have to trust it:
|
|
610
|
+
1. **Node 18+ to Node 22+**, in the badge (`node-%3E%3D18`) and in the prose. `engines.node` is
|
|
611
|
+
`>=22.0.0` in `package.json` and in the registry (`npm view @cosyte/hl7 engines`), and CI runs
|
|
612
|
+
the 22 + 24 matrix. This was the one with a consumer-visible consequence: `npm i @cosyte/hl7`
|
|
613
|
+
on Node 18 warns on engines while the package page says 18 is supported.
|
|
614
|
+
2. **ES2022 to ES2023.** `@cosyte/tsconfig/base.json` sets `"target": "ES2023"` and
|
|
615
|
+
`"lib": ["ES2023"]`, and this repo's `tsconfig.json` extends it without override.
|
|
616
|
+
3. **18 to 20 stable warning codes.** `WARNING_CODES` in `src/parser/warnings.ts` has 20 keys, and
|
|
617
|
+
`test/warning-codes.snapshot.test.ts` already asserted `toHaveLength(20)`. A green test suite
|
|
618
|
+
and a wrong package page coexisted. **No other surface says 18, but that is not the same as the
|
|
619
|
+
count now being consistent**: further down `README.md`, an inline prose list of 16 code names is
|
|
620
|
+
still introduced as "the full list of Tier-2 codes", and `docs-content/` says 19 in three
|
|
621
|
+
places. Those are pre-existing, outside this feature list, and left for their own change rather
|
|
622
|
+
than folded in here.
|
|
623
|
+
4. **"Every public export has JSDoc + `@example`" to "every public function and class."** Measured
|
|
624
|
+
over the 213 names in the built `dist/index.d.ts` export list: all **79 functions** and all
|
|
625
|
+
**5 classes** carry both, but of 17 value constants one (`ERR_CONDITION_CODE_SYSTEM`) has JSDoc
|
|
626
|
+
with no `@example`, and while all **109 type-only exports carry JSDoc, only 73 carry an
|
|
627
|
+
`@example`**. The three aliased namespace exports (`HL7`, `conformance`, `text`) fail the old
|
|
628
|
+
wording outright: two carry no JSDoc at all and the third has no `@example`. The narrowed
|
|
629
|
+
sentence is exactly true; the old one was not.
|
|
630
|
+
|
|
631
|
+
**The rest of the feature list was checked the same way and is correct**, so it is unchanged:
|
|
632
|
+
4 fatal codes (`FATAL_CODES` has exactly 4 keys), 8 built-in vendor profiles (the frozen `profiles`
|
|
633
|
+
namespace has 8, and the eight vendor names match), zero runtime dependencies (`dependencies` is
|
|
634
|
+
present and empty, and there is no `peerDependencies`), `noUncheckedIndexedAccess` (true in the
|
|
635
|
+
inherited base config), dual ESM + CJS (the `exports` map resolves `import` to `.mjs`/`.d.ts` and
|
|
636
|
+
`require` to `.cjs`/`.d.cts`, and `attw` passes node10, node16-CJS, node16-ESM and bundler), and
|
|
637
|
+
the two linked paths (`examples/profile-starter-kit/`, `docs-content/spec-notes-escapes.md`) both
|
|
638
|
+
resolve.
|
|
639
|
+
|
|
640
|
+
**`CLAUDE.md` already said Node >= 22 and Target ES2023, so the repo contradicted itself and
|
|
641
|
+
nothing caught it.** The two files drifted in opposite directions because only one of them is read
|
|
642
|
+
by a consumer, and it is the one that drifted. No gate compares a README claim to the config it
|
|
643
|
+
describes, which is why these were found by hand.
|
|
644
|
+
|
|
436
645
|
- **`scripts/sync-version.mjs` hardened against two latent defects, and gated in CI
|
|
437
646
|
(SYNC-VERSION-HARDENING).** Follow-up hardening on the VERSION-SYNC script; ported byte-identically
|
|
438
647
|
across `hl7`, `x12`, and `mllp`. (1) The version was spliced into `src/index.ts` via
|
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cosyte.com/tile/cosyte-lockup-tile-on-dark-1200x300.png">
|
|
3
|
+
<img alt="Cosyte: a plus mark set in two overlapping rounded squares, one solid and one outlined, beside the Cosyte wordmark" src="https://cosyte.com/tile/cosyte-lockup-tile-on-light-1200x300.png">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
1
6
|
# @cosyte/hl7
|
|
2
7
|
|
|
3
8
|
> Parse real-world, vendor-quirky HL7 v2 messages and extract the fields you need in one line, without reading the spec.
|
|
@@ -5,7 +10,7 @@
|
|
|
5
10
|
[](https://www.npmjs.com/package/@cosyte/hl7)
|
|
6
11
|
[](https://github.com/cosyte/hl7/actions/workflows/ci.yml)
|
|
7
12
|
[](./LICENSE)
|
|
8
|
-
[](https://nodejs.org)
|
|
9
14
|
|
|
10
15
|
A developer-focused HL7 v2 parser and utility library for Node.js and TypeScript. Optimised for the 10% of HL7 you actually use, with the other 90% still one accessor away when you need it.
|
|
11
16
|
|
|
@@ -37,10 +42,10 @@ That's the whole pitch: no config, no schema upload, no spec lookup. The parser
|
|
|
37
42
|
|
|
38
43
|
- **One-line extraction**: `msg.patient.mrn`, `msg.meta.timestamp`, `msg.observations()`, and friends. No segment or field numbers to memorise.
|
|
39
44
|
- **Three access patterns**: named helpers, dot-paths (`msg.get("PID.5.1")`), or structural traversal (`msg.segments("OBX")[0].field(3)`). Pick the level of ceremony you need.
|
|
40
|
-
- **Real-world tolerance, four-tier**: lenient default parses vendor-quirky messages;
|
|
45
|
+
- **Real-world tolerance, four-tier**: lenient default parses vendor-quirky messages; 20 stable warning codes flag what was tolerated; strict mode escalates every deviation for CI validators; only 4 truly-structural failures are fatal.
|
|
41
46
|
- **First-class profile system**: `defineProfile()` API, 8 built-in vendor profiles (Epic, Cerner, Meditech, athenahealth, generic lab, Visage 7 imaging/PACS, Philips Vue PACS, VA VistA Radiology/NucMed), plus a [publishable starter kit](./examples/profile-starter-kit/) you copy-and-ship.
|
|
42
47
|
- **Round-trip safe, byte-verbatim escapes**: `parse -> modify -> toString()` emits spec-clean HL7 regardless of input quirks (Postel's Law: liberal parser, conservative emitter), and a parsed field's escape sequences (`\H\`, `\X41\`, charset/vendor escapes) re-emit **byte-for-byte**. See [Escapes & round-trip](./docs-content/spec-notes-escapes.md).
|
|
43
|
-
- **Strict TypeScript, zero runtime deps**:
|
|
48
|
+
- **Strict TypeScript, zero runtime deps**: ES2023, `noUncheckedIndexedAccess`, dual ESM + CJS, Node 22+. Every public function and class has JSDoc + `@example` that feeds your editor's IntelliSense.
|
|
44
49
|
- **Warnings carry stable codes + positional context**: react programmatically by `w.code`, with `segmentIndex`/`fieldIndex`/etc. attached.
|
|
45
50
|
|
|
46
51
|
---
|
|
@@ -739,7 +744,7 @@ The library throws exactly three error types, all exported from the package barr
|
|
|
739
744
|
|
|
740
745
|
### `Hl7ParseError`
|
|
741
746
|
|
|
742
|
-
Thrown by `parseHL7` when the input hits one of the 4 Tier-3 fatal codes (see above). Carries positional context plus a short snippet of the
|
|
747
|
+
Thrown by `parseHL7` when the input hits one of the 4 Tier-3 fatal codes (see above). Carries positional context plus a short snippet taken from the start of the input (up to 40 characters, plus an ellipsis when truncated).
|
|
743
748
|
|
|
744
749
|
```ts
|
|
745
750
|
import { parseHL7, Hl7ParseError, FATAL_CODES } from "@cosyte/hl7";
|
|
@@ -760,7 +765,13 @@ if (err instanceof Hl7ParseError && err.code === FATAL_CODES.NO_MSH_SEGMENT) {
|
|
|
760
765
|
}
|
|
761
766
|
```
|
|
762
767
|
|
|
763
|
-
`Hl7ParseError.snippet` may contain PHI when parsing real clinical messages. The library does NOT redact. Redact at your call site if compliance demands it.
|
|
768
|
+
`Hl7ParseError.snippet` may contain PHI when parsing real clinical messages. The library does NOT redact it. Redact it at your call site if compliance demands it.
|
|
769
|
+
|
|
770
|
+
**`snippet` is the only field carrying input verbatim and unfiltered** (capped at 40 characters plus an ellipsis, but not shape-checked in any way), so redact it first.
|
|
771
|
+
|
|
772
|
+
`message` is bounded but not absolutely content-free, which is worth stating because `message` is what a logger prints by default and what `stack` embeds. A token taken from the input is echoed only when it matches the form the spec defines for it: a three-character segment identifier, an MSH-9 type, an MSH-12 version, or a charset label the closed Table 0211 actually contains. Anything else becomes `<withheld>`. So a message cannot carry a field's value, but it can carry a residue of up to three characters when a malformed line happens to look like a segment identifier. The other shapes are narrower in practice than their patterns allow, because the library only ever feeds them registry-matched values; the patterns themselves admit more (a message type up to 26 characters, a version up to 14), which matters only if you construct warnings yourself.
|
|
773
|
+
|
|
774
|
+
Under `{ strict: true }` an escalated Tier-2 warning is thrown as an `Hl7ParseError` carrying that same bounded message, plus a `snippet` of the first 40 characters of the input rather than of the deviation's own segment.
|
|
764
775
|
|
|
765
776
|
### `Hl7ParseWarning`
|
|
766
777
|
|
package/dist/index.cjs
CHANGED
|
@@ -255,7 +255,104 @@ var KNOWN_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
255
255
|
"CSS"
|
|
256
256
|
]);
|
|
257
257
|
|
|
258
|
+
// src/parser/charset.ts
|
|
259
|
+
function normalizeLabel(raw) {
|
|
260
|
+
return raw.trim().replace(/\s+/gu, " ").toUpperCase();
|
|
261
|
+
}
|
|
262
|
+
function decodeEntry(canonical, decoder) {
|
|
263
|
+
return { canonical, treatment: "decode", decoder };
|
|
264
|
+
}
|
|
265
|
+
function verbatimEntry(canonical) {
|
|
266
|
+
return { canonical, treatment: "verbatim", decoder: "" };
|
|
267
|
+
}
|
|
268
|
+
function buildTable() {
|
|
269
|
+
const table = /* @__PURE__ */ new Map();
|
|
270
|
+
const add = (aliases, entry) => {
|
|
271
|
+
for (const alias of aliases) table.set(normalizeLabel(alias), entry);
|
|
272
|
+
};
|
|
273
|
+
add(["ASCII", "US-ASCII", "ISO IR6"], decodeEntry("ASCII", "utf-8"));
|
|
274
|
+
add(["UTF-8", "UTF8", "UNICODE UTF-8", "UNICODE"], decodeEntry("UTF-8", "utf-8"));
|
|
275
|
+
add(["8859/1", "ISO-8859-1", "ISO 8859-1", "ISO8859-1"], decodeEntry("8859/1", "latin1"));
|
|
276
|
+
for (const n of [2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16]) {
|
|
277
|
+
add(
|
|
278
|
+
[`8859/${n}`, `ISO-8859-${n}`, `ISO 8859-${n}`, `ISO8859-${n}`],
|
|
279
|
+
decodeEntry(`8859/${n}`, `iso-8859-${n}`)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
add(["8859/9", "ISO-8859-9", "ISO 8859-9", "ISO8859-9"], verbatimEntry("8859/9"));
|
|
283
|
+
add(["8859/11", "ISO-8859-11", "ISO 8859-11", "ISO8859-11"], verbatimEntry("8859/11"));
|
|
284
|
+
add(["ISO IR14", "JIS X 0201"], verbatimEntry("ISO IR14"));
|
|
285
|
+
add(["ISO IR87", "JIS X 0208"], verbatimEntry("ISO IR87"));
|
|
286
|
+
add(["ISO IR159", "JIS X 0212"], verbatimEntry("ISO IR159"));
|
|
287
|
+
add(["GB 18030-2000", "GB18030", "GB 18030"], verbatimEntry("GB 18030-2000"));
|
|
288
|
+
add(["KS X 1001", "KSX1001", "KS C 5601"], verbatimEntry("KS X 1001"));
|
|
289
|
+
add(["CNS 11643-1992", "CNS 11643"], verbatimEntry("CNS 11643-1992"));
|
|
290
|
+
add(["BIG-5", "BIG5"], verbatimEntry("BIG-5"));
|
|
291
|
+
add(["UNICODE UTF-16", "UTF-16"], verbatimEntry("UNICODE UTF-16"));
|
|
292
|
+
add(["UNICODE UTF-32", "UTF-32"], verbatimEntry("UNICODE UTF-32"));
|
|
293
|
+
return table;
|
|
294
|
+
}
|
|
295
|
+
var CHARSET_TABLE = buildTable();
|
|
296
|
+
var DEFAULT_RESOLUTION = Object.freeze({
|
|
297
|
+
canonical: "ASCII",
|
|
298
|
+
treatment: "decode",
|
|
299
|
+
decoder: "utf-8",
|
|
300
|
+
recognized: true
|
|
301
|
+
});
|
|
302
|
+
function resolveCharset(raw) {
|
|
303
|
+
if (raw === void 0) return DEFAULT_RESOLUTION;
|
|
304
|
+
const key = normalizeLabel(raw);
|
|
305
|
+
if (key === "") return DEFAULT_RESOLUTION;
|
|
306
|
+
const entry = CHARSET_TABLE.get(key);
|
|
307
|
+
if (entry !== void 0) {
|
|
308
|
+
return {
|
|
309
|
+
canonical: entry.canonical,
|
|
310
|
+
treatment: entry.treatment,
|
|
311
|
+
decoder: entry.decoder,
|
|
312
|
+
recognized: true
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return { canonical: key, treatment: "verbatim", decoder: "", recognized: false };
|
|
316
|
+
}
|
|
317
|
+
function canonicalCharset(raw) {
|
|
318
|
+
return resolveCharset(raw).canonical;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/parser/tokens.ts
|
|
322
|
+
var DERIVED_TOKEN_SHAPES = {
|
|
323
|
+
/**
|
|
324
|
+
* HL7 v2 Ch. 2 §2.5 segment identifier: three characters, the first
|
|
325
|
+
* alphabetic.
|
|
326
|
+
*
|
|
327
|
+
* Two deliberate tightenings, each measured rather than assumed. The first
|
|
328
|
+
* version allowed four characters as "vendor tolerance", which was invented
|
|
329
|
+
* rather than grounded: all 91 `KNOWN_SEGMENTS` are exactly three, and
|
|
330
|
+
* `defineProfile` independently validates custom Z-segments to
|
|
331
|
+
* `/^Z[A-Z0-9]{2}$/`, so nothing legitimate is longer. The second requires a
|
|
332
|
+
* leading letter, which every known segment and every legal Z-segment
|
|
333
|
+
* satisfies, and which excludes a **bare numeric** residue. That matters out
|
|
334
|
+
* of proportion to its size: a forged line reading `120` is a glucose, a
|
|
335
|
+
* blood pressure or a dose, which is the most clinical meaning three
|
|
336
|
+
* characters can carry.
|
|
337
|
+
*/
|
|
338
|
+
segmentId: /^[A-Za-z][A-Za-z0-9]{2}$/,
|
|
339
|
+
/** MSH-9 message type / trigger, e.g. `ADT^A01` or `A40`. */
|
|
340
|
+
messageType: /^[A-Za-z0-9]{1,8}(?:\^[A-Za-z0-9]{1,8}){0,2}$/,
|
|
341
|
+
/** MSH-12 version ID, e.g. `2.5` or `2.5.1`. */
|
|
342
|
+
version: /^[0-9]{1,2}(?:\.[0-9]{1,3}){0,3}$/
|
|
343
|
+
};
|
|
344
|
+
function safeDerivedToken(value, kind) {
|
|
345
|
+
return DERIVED_TOKEN_SHAPES[kind].test(value) ? value : WITHHELD;
|
|
346
|
+
}
|
|
347
|
+
var WITHHELD = "<withheld>";
|
|
348
|
+
function boundedIdentifier(value, kind) {
|
|
349
|
+
return value === "" ? "" : safeDerivedToken(value, kind);
|
|
350
|
+
}
|
|
351
|
+
|
|
258
352
|
// src/parser/warnings.ts
|
|
353
|
+
function safeCharsetLabel(value) {
|
|
354
|
+
return resolveCharset(value).recognized ? value : WITHHELD;
|
|
355
|
+
}
|
|
259
356
|
var WARNING_CODES = {
|
|
260
357
|
MLLP_FRAMING_STRIPPED: "MLLP_FRAMING_STRIPPED",
|
|
261
358
|
FIELD_WHITESPACE_TRIMMED: "FIELD_WHITESPACE_TRIMMED",
|
|
@@ -340,28 +437,28 @@ function timestampFallbackFormat(position, matchedFormat) {
|
|
|
340
437
|
function segmentCase(position, observed) {
|
|
341
438
|
return {
|
|
342
439
|
code: WARNING_CODES.SEGMENT_CASE,
|
|
343
|
-
message: `Segment identifier "${observed}" is not uppercase; normalized to "${observed.toUpperCase()}".`,
|
|
440
|
+
message: `Segment identifier "${safeDerivedToken(observed, "segmentId")}" is not uppercase; normalized to "${safeDerivedToken(observed.toUpperCase(), "segmentId")}".`,
|
|
344
441
|
position
|
|
345
442
|
};
|
|
346
443
|
}
|
|
347
444
|
function extraFields(position, segmentName2, extraCount) {
|
|
348
445
|
return {
|
|
349
446
|
code: WARNING_CODES.EXTRA_FIELDS,
|
|
350
|
-
message: `Segment "${segmentName2}" has ${String(extraCount)} more field(s) than the profile declares.`,
|
|
447
|
+
message: `Segment "${safeDerivedToken(segmentName2, "segmentId")}" has ${String(extraCount)} more field(s) than the profile declares.`,
|
|
351
448
|
position
|
|
352
449
|
};
|
|
353
450
|
}
|
|
354
451
|
function unknownSegment(position, segmentName2) {
|
|
355
452
|
return {
|
|
356
453
|
code: WARNING_CODES.UNKNOWN_SEGMENT,
|
|
357
|
-
message: `Unknown segment "${segmentName2}": not in HL7 spec and no profile claim.`,
|
|
454
|
+
message: `Unknown segment "${safeDerivedToken(segmentName2, "segmentId")}": not in HL7 spec and no profile claim.`,
|
|
358
455
|
position
|
|
359
456
|
};
|
|
360
457
|
}
|
|
361
458
|
function duplicateRequiredSegment(position, segmentName2) {
|
|
362
459
|
return {
|
|
363
460
|
code: WARNING_CODES.DUPLICATE_REQUIRED_SEGMENT,
|
|
364
|
-
message: `Required singleton segment "${segmentName2}" appears more than once.`,
|
|
461
|
+
message: `Required singleton segment "${safeDerivedToken(segmentName2, "segmentId")}" appears more than once.`,
|
|
365
462
|
position
|
|
366
463
|
};
|
|
367
464
|
}
|
|
@@ -375,42 +472,42 @@ function encodingMismatch(position, detail) {
|
|
|
375
472
|
function missingRequiredField(position, segmentName2, fieldIndex) {
|
|
376
473
|
return {
|
|
377
474
|
code: WARNING_CODES.MISSING_REQUIRED_FIELD,
|
|
378
|
-
message: `Required field ${segmentName2}-${String(fieldIndex)} is missing or empty.`,
|
|
475
|
+
message: `Required field ${safeDerivedToken(segmentName2, "segmentId")}-${String(fieldIndex)} is missing or empty.`,
|
|
379
476
|
position
|
|
380
477
|
};
|
|
381
478
|
}
|
|
382
479
|
function missingExpectedGroup(position, messageType, groupName, anchorSegments) {
|
|
383
480
|
return {
|
|
384
481
|
code: WARNING_CODES.MISSING_EXPECTED_GROUP,
|
|
385
|
-
message: `Message type "${messageType}" is missing its expected "${groupName}" segment group (no ${anchorSegments.join("/")}); message may be truncated or misrouted.`,
|
|
482
|
+
message: `Message type "${safeDerivedToken(messageType, "messageType")}" is missing its expected "${groupName}" segment group (no ${anchorSegments.join("/")}); message may be truncated or misrouted.`,
|
|
386
483
|
position
|
|
387
484
|
};
|
|
388
485
|
}
|
|
389
486
|
function outOfOrderSegment(position, segmentName2) {
|
|
390
487
|
return {
|
|
391
488
|
code: WARNING_CODES.OUT_OF_ORDER_SEGMENT,
|
|
392
|
-
message: `Segment "${segmentName2}" appears out of the order declared by the active profile.`,
|
|
489
|
+
message: `Segment "${safeDerivedToken(segmentName2, "segmentId")}" appears out of the order declared by the active profile.`,
|
|
393
490
|
position
|
|
394
491
|
};
|
|
395
492
|
}
|
|
396
493
|
function versionMismatch(position, declared, expected) {
|
|
397
494
|
return {
|
|
398
495
|
code: WARNING_CODES.VERSION_MISMATCH,
|
|
399
|
-
message: `HL7 version mismatch: message declares "${declared}", expected "${expected}".`,
|
|
496
|
+
message: `HL7 version mismatch: message declares "${safeDerivedToken(declared, "version")}", expected "${expected}".`,
|
|
400
497
|
position
|
|
401
498
|
};
|
|
402
499
|
}
|
|
403
500
|
function unknownCharset(position, requested) {
|
|
404
501
|
return {
|
|
405
502
|
code: WARNING_CODES.UNKNOWN_CHARSET,
|
|
406
|
-
message: `Unrecognized character set "${requested}"; not decoded, bytes read as latin1.`,
|
|
503
|
+
message: `Unrecognized character set "${safeCharsetLabel(requested)}"; not decoded, bytes read as latin1.`,
|
|
407
504
|
position
|
|
408
505
|
};
|
|
409
506
|
}
|
|
410
507
|
function unsupportedCharset(position, code) {
|
|
411
508
|
return {
|
|
412
509
|
code: WARNING_CODES.UNSUPPORTED_CHARSET,
|
|
413
|
-
message: `Character set "${code}" was not decoded; bytes read as latin1.`,
|
|
510
|
+
message: `Character set "${safeCharsetLabel(code)}" was not decoded; bytes read as latin1.`,
|
|
414
511
|
position
|
|
415
512
|
};
|
|
416
513
|
}
|
|
@@ -425,7 +522,7 @@ function mergeMissingPriorOrSurvivor(position, eventType, missing) {
|
|
|
425
522
|
const detail = missing === "prior" ? "no MRG segment carries a usable prior (non-surviving) identifier" : "no PID segment carries a usable surviving identifier";
|
|
426
523
|
return {
|
|
427
524
|
code: WARNING_CODES.MERGE_MISSING_PRIOR_OR_SURVIVOR,
|
|
428
|
-
message: `Identity event "${eventType}" is missing its ${missing} role: ${detail}. Surfacing what is present; the MRG->PID merge direction is not guessed.`,
|
|
525
|
+
message: `Identity event "${safeDerivedToken(eventType, "messageType")}" is missing its ${missing} role: ${detail}. Surfacing what is present; the MRG->PID merge direction is not guessed.`,
|
|
429
526
|
position
|
|
430
527
|
};
|
|
431
528
|
}
|
|
@@ -471,69 +568,6 @@ function emitIfFramed(result, emit, position) {
|
|
|
471
568
|
}
|
|
472
569
|
}
|
|
473
570
|
|
|
474
|
-
// src/parser/charset.ts
|
|
475
|
-
function normalizeLabel(raw) {
|
|
476
|
-
return raw.trim().replace(/\s+/gu, " ").toUpperCase();
|
|
477
|
-
}
|
|
478
|
-
function decodeEntry(canonical, decoder) {
|
|
479
|
-
return { canonical, treatment: "decode", decoder };
|
|
480
|
-
}
|
|
481
|
-
function verbatimEntry(canonical) {
|
|
482
|
-
return { canonical, treatment: "verbatim", decoder: "" };
|
|
483
|
-
}
|
|
484
|
-
function buildTable() {
|
|
485
|
-
const table = /* @__PURE__ */ new Map();
|
|
486
|
-
const add = (aliases, entry) => {
|
|
487
|
-
for (const alias of aliases) table.set(normalizeLabel(alias), entry);
|
|
488
|
-
};
|
|
489
|
-
add(["ASCII", "US-ASCII", "ISO IR6"], decodeEntry("ASCII", "utf-8"));
|
|
490
|
-
add(["UTF-8", "UTF8", "UNICODE UTF-8", "UNICODE"], decodeEntry("UTF-8", "utf-8"));
|
|
491
|
-
add(["8859/1", "ISO-8859-1", "ISO 8859-1", "ISO8859-1"], decodeEntry("8859/1", "latin1"));
|
|
492
|
-
for (const n of [2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16]) {
|
|
493
|
-
add(
|
|
494
|
-
[`8859/${n}`, `ISO-8859-${n}`, `ISO 8859-${n}`, `ISO8859-${n}`],
|
|
495
|
-
decodeEntry(`8859/${n}`, `iso-8859-${n}`)
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
add(["8859/9", "ISO-8859-9", "ISO 8859-9", "ISO8859-9"], verbatimEntry("8859/9"));
|
|
499
|
-
add(["8859/11", "ISO-8859-11", "ISO 8859-11", "ISO8859-11"], verbatimEntry("8859/11"));
|
|
500
|
-
add(["ISO IR14", "JIS X 0201"], verbatimEntry("ISO IR14"));
|
|
501
|
-
add(["ISO IR87", "JIS X 0208"], verbatimEntry("ISO IR87"));
|
|
502
|
-
add(["ISO IR159", "JIS X 0212"], verbatimEntry("ISO IR159"));
|
|
503
|
-
add(["GB 18030-2000", "GB18030", "GB 18030"], verbatimEntry("GB 18030-2000"));
|
|
504
|
-
add(["KS X 1001", "KSX1001", "KS C 5601"], verbatimEntry("KS X 1001"));
|
|
505
|
-
add(["CNS 11643-1992", "CNS 11643"], verbatimEntry("CNS 11643-1992"));
|
|
506
|
-
add(["BIG-5", "BIG5"], verbatimEntry("BIG-5"));
|
|
507
|
-
add(["UNICODE UTF-16", "UTF-16"], verbatimEntry("UNICODE UTF-16"));
|
|
508
|
-
add(["UNICODE UTF-32", "UTF-32"], verbatimEntry("UNICODE UTF-32"));
|
|
509
|
-
return table;
|
|
510
|
-
}
|
|
511
|
-
var CHARSET_TABLE = buildTable();
|
|
512
|
-
var DEFAULT_RESOLUTION = Object.freeze({
|
|
513
|
-
canonical: "ASCII",
|
|
514
|
-
treatment: "decode",
|
|
515
|
-
decoder: "utf-8",
|
|
516
|
-
recognized: true
|
|
517
|
-
});
|
|
518
|
-
function resolveCharset(raw) {
|
|
519
|
-
if (raw === void 0) return DEFAULT_RESOLUTION;
|
|
520
|
-
const key = normalizeLabel(raw);
|
|
521
|
-
if (key === "") return DEFAULT_RESOLUTION;
|
|
522
|
-
const entry = CHARSET_TABLE.get(key);
|
|
523
|
-
if (entry !== void 0) {
|
|
524
|
-
return {
|
|
525
|
-
canonical: entry.canonical,
|
|
526
|
-
treatment: entry.treatment,
|
|
527
|
-
decoder: entry.decoder,
|
|
528
|
-
recognized: true
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
return { canonical: key, treatment: "verbatim", decoder: "", recognized: false };
|
|
532
|
-
}
|
|
533
|
-
function canonicalCharset(raw) {
|
|
534
|
-
return resolveCharset(raw).canonical;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
571
|
// src/parser/normalize.ts
|
|
538
572
|
var CHARSET_POSITION = { segmentIndex: 0, fieldIndex: 18 };
|
|
539
573
|
function preserveVerbatim(input) {
|
|
@@ -928,8 +962,8 @@ function analyzeMessageStructure(messageCode, triggerEvent, presentSegmentNames)
|
|
|
928
962
|
if (def === void 0) {
|
|
929
963
|
return Object.freeze({
|
|
930
964
|
recognized: false,
|
|
931
|
-
messageCode,
|
|
932
|
-
triggerEvent,
|
|
965
|
+
messageCode: boundedIdentifier(messageCode, "segmentId"),
|
|
966
|
+
triggerEvent: boundedIdentifier(triggerEvent, "segmentId"),
|
|
933
967
|
expectedGroups: Object.freeze([]),
|
|
934
968
|
missingGroups: Object.freeze([])
|
|
935
969
|
});
|
|
@@ -2161,7 +2195,24 @@ var EMPTY_REP = Object.freeze({
|
|
|
2161
2195
|
|
|
2162
2196
|
// src/model/segment.ts
|
|
2163
2197
|
var Segment = class {
|
|
2164
|
-
/**
|
|
2198
|
+
/**
|
|
2199
|
+
* Segment identifier: three characters with a leading letter, e.g. `"PID"`,
|
|
2200
|
+
* `"OBX"`, `"ZPI"`.
|
|
2201
|
+
*
|
|
2202
|
+
* **Bounded, and it is `"<withheld>"` when the raw name is not that shape.**
|
|
2203
|
+
* A line with no field separator has its whole content read as a segment
|
|
2204
|
+
* name, so an unescaped line break inside a narrative field forges a
|
|
2205
|
+
* "segment" whose name is clinical text. This field presents itself as a
|
|
2206
|
+
* structural identifier and consumers interpolate it into labels, loci and
|
|
2207
|
+
* reports, so it never carries that text. `""` still means absent, which is
|
|
2208
|
+
* a different fact from withheld.
|
|
2209
|
+
*
|
|
2210
|
+
* Use {@link Segment.raw}`.name` when you need the verbatim text: it is the
|
|
2211
|
+
* unbounded value, and it is what serialization emits, so the byte-verbatim
|
|
2212
|
+
* round-trip is unaffected. One consequence worth knowing: a segment whose
|
|
2213
|
+
* raw name fails the shape (a 4-character vendor Z-segment, which HL7 v2
|
|
2214
|
+
* Ch. 2 §2.5 does not permit) is not matched by `msg.segments(name)`.
|
|
2215
|
+
*/
|
|
2165
2216
|
type;
|
|
2166
2217
|
/** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
|
|
2167
2218
|
fields;
|
|
@@ -2194,7 +2245,7 @@ var Segment = class {
|
|
|
2194
2245
|
*/
|
|
2195
2246
|
constructor(raw, enc, absoluteIndex, customFields) {
|
|
2196
2247
|
this.raw = raw;
|
|
2197
|
-
this.type = raw.name;
|
|
2248
|
+
this.type = boundedIdentifier(raw.name, "segmentId");
|
|
2198
2249
|
this.fields = raw.fields;
|
|
2199
2250
|
this.enc = enc;
|
|
2200
2251
|
this.absoluteIndex = absoluteIndex;
|
|
@@ -3885,6 +3936,16 @@ var Hl7Message = class {
|
|
|
3885
3936
|
*/
|
|
3886
3937
|
rawSegments;
|
|
3887
3938
|
encodingCharacters;
|
|
3939
|
+
/**
|
|
3940
|
+
* HL7 version the model asserts for this message, from MSH-12.1.1, e.g.
|
|
3941
|
+
* `"2.5"` or `"2.5.1"`.
|
|
3942
|
+
*
|
|
3943
|
+
* **Bounded, and it is `"<withheld>"` when MSH-12 does not hold a version.**
|
|
3944
|
+
* Same reasoning as {@link Segment.type}: it presents as an identifier and
|
|
3945
|
+
* consumers label with it. `meta.version` keeps MSH-12 exactly as it
|
|
3946
|
+
* arrived, and is what version-scoped field selection reads, so this bound
|
|
3947
|
+
* changes no parsing behaviour.
|
|
3948
|
+
*/
|
|
3888
3949
|
version;
|
|
3889
3950
|
warnings;
|
|
3890
3951
|
profile;
|
|
@@ -3955,7 +4016,7 @@ var Hl7Message = class {
|
|
|
3955
4016
|
constructor(init) {
|
|
3956
4017
|
this.rawSegments = init.segments;
|
|
3957
4018
|
this.encodingCharacters = init.encodingCharacters;
|
|
3958
|
-
this.version = init.version;
|
|
4019
|
+
this.version = boundedIdentifier(init.version, "version");
|
|
3959
4020
|
this.warnings = Object.freeze(init.warnings.slice());
|
|
3960
4021
|
this.profile = init.profile;
|
|
3961
4022
|
if (init.customSegments !== void 0) this._customSegments = init.customSegments;
|
|
@@ -4793,7 +4854,12 @@ function resolveBufferCharset(raw, options, emit) {
|
|
|
4793
4854
|
emit(
|
|
4794
4855
|
encodingMismatch(
|
|
4795
4856
|
{ segmentIndex: 0 },
|
|
4796
|
-
`
|
|
4857
|
+
// `declared` is MSH-18 read off the wire. On a message with no
|
|
4858
|
+
// segment terminators it is not MSH-18 at all but the 18th
|
|
4859
|
+
// `|`-token of the flattened message, commonly a data field, so it
|
|
4860
|
+
// is shape-checked before it reaches a log surface. `override` is
|
|
4861
|
+
// the caller's own `options.charset`, not input, and is left alone.
|
|
4862
|
+
`options.charset="${override}" disagrees with MSH-18="${safeCharsetLabel(declared)}"`
|
|
4797
4863
|
)
|
|
4798
4864
|
);
|
|
4799
4865
|
}
|
|
@@ -6808,7 +6874,7 @@ async function* parseStream(source, optionsOrProfile) {
|
|
|
6808
6874
|
}
|
|
6809
6875
|
|
|
6810
6876
|
// src/index.ts
|
|
6811
|
-
var VERSION = "0.0.
|
|
6877
|
+
var VERSION = "0.0.5";
|
|
6812
6878
|
|
|
6813
6879
|
exports.ACK_CODES = ACK_CODES;
|
|
6814
6880
|
exports.ACK_CONDITIONS = ACK_CONDITIONS;
|