@cosyte/mllp 0.0.1 → 0.0.2
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 +269 -108
- package/README.md +23 -18
- package/TRADEMARKS.md +30 -0
- package/dist/ack-from-hl7/index.cjs +54 -14
- package/dist/ack-from-hl7/index.cjs.map +1 -1
- package/dist/ack-from-hl7/index.d.cts +113 -38
- package/dist/ack-from-hl7/index.d.ts +113 -38
- package/dist/ack-from-hl7/index.mjs +53 -14
- package/dist/ack-from-hl7/index.mjs.map +1 -1
- package/dist/{index-DjGQJtYE.d.cts → index-DAZ4BvJE.d.cts} +5 -5
- package/dist/{index-DjGQJtYE.d.ts → index-DAZ4BvJE.d.ts} +5 -5
- package/dist/index.cjs +90 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +239 -239
- package/dist/index.d.ts +239 -239
- package/dist/index.mjs +90 -90
- package/dist/index.mjs.map +1 -1
- package/dist/testing/index.cjs +3 -3
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +8 -8
- package/dist/testing/index.d.ts +8 -8
- package/dist/testing/index.mjs +3 -3
- package/dist/testing/index.mjs.map +1 -1
- package/package.json +15 -7
|
@@ -2,7 +2,7 @@ import { AckCode, AckErrorDetail, Hl7Message, AckMode, BuildAckOptions, Hl7Parse
|
|
|
2
2
|
export { AckCode, AckErrorDetail, AckMode } from '@cosyte/hl7';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* `buildMllpAck
|
|
5
|
+
* `buildMllpAck`, a THIN transport adapter over `@cosyte/hl7`'s `buildAck`.
|
|
6
6
|
*
|
|
7
7
|
* `@cosyte/hl7` owns ACK **content** (MSH/MSA/ERR construction, the fail-safe
|
|
8
8
|
* no-correlation downgrade); this module owns **transport policy**: accepting
|
|
@@ -53,7 +53,7 @@ interface MllpAckWarning {
|
|
|
53
53
|
declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
54
54
|
/**
|
|
55
55
|
* Warning code emitted when the built ACK's MSA-2 bytes are **not** byte-identical
|
|
56
|
-
* to the inbound MSH-10 bytes
|
|
56
|
+
* to the inbound MSH-10 bytes, i.e. the ACK does not echo the message control ID
|
|
57
57
|
* verbatim, as HL7 v2.5.1 §2.9.2.2 requires.
|
|
58
58
|
*
|
|
59
59
|
* This is a **correlation failure**, and the reason it is a loud warning rather
|
|
@@ -61,7 +61,7 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
61
61
|
* it put on the wire. An ACK whose MSA-2 differs by even one byte will not match
|
|
62
62
|
* that key, so the send is never settled → ACK timeout → resend → **duplicate
|
|
63
63
|
* clinical message**. Emitting the ACK anyway (rather than throwing) is the
|
|
64
|
-
* fail-safe choice
|
|
64
|
+
* fail-safe choice, a mismatched ACK still tells the peer *something*, but the
|
|
65
65
|
* mismatch must never pass unremarked.
|
|
66
66
|
*
|
|
67
67
|
* `buildMllpAck` verifies this on every build, against the very same byte-level
|
|
@@ -73,7 +73,7 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
73
73
|
* `string`/`Hl7Message` inbound has **already been decoded** before this module sees it:
|
|
74
74
|
* the only thing left to compare against is that same text, encoded with the same codec,
|
|
75
75
|
* so the codec cancels on both sides and a codec-induced mismatch is **structurally
|
|
76
|
-
* invisible
|
|
76
|
+
* invisible**, it warns about nothing. See {@link BuildMllpAckOptions.encoding}. Pass a
|
|
77
77
|
* `Buffer` if you want this guarantee to mean anything.
|
|
78
78
|
*
|
|
79
79
|
* Two known ways to provoke it, both documented in the package limitations:
|
|
@@ -84,7 +84,7 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
84
84
|
* 2. An inbound that declares **non-default encoding characters** (MSH-1/MSH-2).
|
|
85
85
|
* `@cosyte/hl7`'s `buildMessage` always emits the HL7 default `|^~\&`, so an
|
|
86
86
|
* inbound MSH-10 of `ID#X` under a `#` component separator is re-delimited to
|
|
87
|
-
* `ID^X` in MSA-2
|
|
87
|
+
* `ID^X` in MSA-2, different bytes, unmatchable key. Use `buildRawAck` (root
|
|
88
88
|
* export), which echoes the inbound's own MSH-1/MSH-2, if you must serve such
|
|
89
89
|
* a peer.
|
|
90
90
|
*
|
|
@@ -93,11 +93,63 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
93
93
|
* import { MLLP_ACK_CONTROL_ID_NOT_VERBATIM, buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
94
94
|
* const { warnings } = buildAckAA(inbound, { encoding: "ascii" });
|
|
95
95
|
* if (warnings.some((w) => w.code === MLLP_ACK_CONTROL_ID_NOT_VERBATIM)) {
|
|
96
|
-
* // this ACK will NOT correlate at the sender
|
|
96
|
+
* // this ACK will NOT correlate at the sender, investigate before shipping it
|
|
97
97
|
* }
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
|
100
100
|
declare const MLLP_ACK_CONTROL_ID_NOT_VERBATIM = "MLLP_ACK_CONTROL_ID_NOT_VERBATIM";
|
|
101
|
+
/**
|
|
102
|
+
* Warning code emitted when the inbound was handed in as **text** (a `string` or an
|
|
103
|
+
* already-parsed `Hl7Message`) rather than as raw `Buffer` bytes, and the ACK's MSA-2
|
|
104
|
+
* carries a **non-ASCII** control ID, a combination whose byte-verbatim echo
|
|
105
|
+
* (HL7 v2.5.1 §2.9.2.2) `buildMllpAck` **cannot verify**, and may silently be breaking.
|
|
106
|
+
*
|
|
107
|
+
* ## Why a distinct code from {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}
|
|
108
|
+
*
|
|
109
|
+
* `MLLP_ACK_CONTROL_ID_NOT_VERBATIM` is a *proof of failure*: on a `Buffer` inbound the
|
|
110
|
+
* wire bytes are in hand, the check compares MSA-2 against them, and it fires only when
|
|
111
|
+
* they provably differ. This code is the opposite, a *confession that the proof cannot
|
|
112
|
+
* be run*. A `string`/`Hl7Message` inbound was decoded from its wire bytes **before** this
|
|
113
|
+
* module ever saw it, so the only thing left to compare the ACK against is that same text
|
|
114
|
+
* re-encoded with the same codec: the codec cancels on both sides and the verbatim check
|
|
115
|
+
* becomes a tautology that always passes (see {@link verifyVerbatimEcho}). It would report
|
|
116
|
+
* clean even for `buildAckAA(payload.toString("latin1"))` on a high-bit control ID, where
|
|
117
|
+
* a `latin1`-decoded `0x8B` is re-encoded as the two `utf8` bytes `0xC2 0x8B`, a *different*
|
|
118
|
+
* control ID the sender cannot correlate (timeout → resend → **duplicate clinical message**).
|
|
119
|
+
*
|
|
120
|
+
* The guard cannot be grown to catch that; by the time text arrives the bytes are gone.
|
|
121
|
+
* The API can, and does: rather than let the text path pass a codec-sensitive control ID
|
|
122
|
+
* off as verified, `buildMllpAck` refuses to stay silent about it. Whenever the ACK's MSA-2
|
|
123
|
+
* control ID holds a code unit outside `0x00`–`0x7F` on a text inbound, the range where the
|
|
124
|
+
* codec choice is load-bearing, it emits this warning. An all-ASCII control ID round-trips
|
|
125
|
+
* identically under every codec, so the common case stays quiet; a non-ASCII one is flagged as
|
|
126
|
+
* *unverifiable*, not as *known-broken*, because from a decoded string the two are genuinely
|
|
127
|
+
* indistinguishable.
|
|
128
|
+
*
|
|
129
|
+
* The check reads the **pre-encoding code units** of MSA-2, not the emitted bytes, so a lossy
|
|
130
|
+
* `{ encoding: "ascii" }` override, which truncates a code unit to its low 8 bits (`str -> byte &
|
|
131
|
+
* 0xFF`) and so masks a code unit above `0xFF` *into* the ASCII byte range on the way out (`U+0153`
|
|
132
|
+
* -> `0x53`), cannot slip a corrupted control ID past it silently (MLLP-ACK-ASCII-OVERRIDE-BLEED).
|
|
133
|
+
* The strongly-discouraged text-plus-override path is covered for the same reason the default `utf8`
|
|
134
|
+
* text path is; the `Buffer` overload remains the answer.
|
|
135
|
+
*
|
|
136
|
+
* **The fix the warning points at is the `Buffer`-first API rule.** Pass the raw payload,
|
|
137
|
+
* the bytes the server handed you, and you get the real byte-level check (and, if it breaks,
|
|
138
|
+
* the definite {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}) instead of this "cannot tell".
|
|
139
|
+
*
|
|
140
|
+
* Like its sibling, this warning carries byte **lengths** only, never the field bytes
|
|
141
|
+
* (MSH-10 is inbound payload content and a warning goes to a log; see {@link verifyVerbatimEcho}).
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { MLLP_ACK_CONTROL_ID_UNVERIFIABLE, buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
146
|
+
* const { warnings } = buildAckAA(payload.toString("latin1")); // text inbound, high-bit id
|
|
147
|
+
* if (warnings.some((w) => w.code === MLLP_ACK_CONTROL_ID_UNVERIFIABLE)) {
|
|
148
|
+
* // pass the raw Buffer instead, the echo cannot be verified from decoded text
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
declare const MLLP_ACK_CONTROL_ID_UNVERIFIABLE = "MLLP_ACK_CONTROL_ID_UNVERIFIABLE";
|
|
101
153
|
/**
|
|
102
154
|
* Options for {@link buildMllpAck} and its six convenience wrappers.
|
|
103
155
|
*
|
|
@@ -113,7 +165,7 @@ interface BuildMllpAckOptions {
|
|
|
113
165
|
/** Optional error detail; one entry emits one ERR segment. */
|
|
114
166
|
readonly error?: AckErrorDetail | readonly AckErrorDetail[];
|
|
115
167
|
/**
|
|
116
|
-
* The codec used to move the ACK between bytes and text
|
|
168
|
+
* The codec used to move the ACK between bytes and text, **both** directions,
|
|
117
169
|
* as a single symmetric choice. It decodes a raw-`Buffer` `inbound` on the way in
|
|
118
170
|
* and encodes the serialized ACK on the way out, so the ACK is emitted in the same
|
|
119
171
|
* codec the inbound was read in.
|
|
@@ -122,13 +174,13 @@ interface BuildMllpAckOptions {
|
|
|
122
174
|
* **verbatim** (HL7 v2.5.1 §2.9.2.2). Decoding in one codec and encoding in another
|
|
123
175
|
* corrupts every non-ASCII byte it round-trips: a `latin1`-decoded `0x8B` re-encoded
|
|
124
176
|
* as `utf8` goes out as the two bytes `0xC2 0x8B`, which is a *different control ID*
|
|
125
|
-
*
|
|
177
|
+
* and the sender, which keyed its in-flight store on `0x8B`, cannot match the ACK.
|
|
126
178
|
*
|
|
127
179
|
* **Defaults, by inbound type:**
|
|
128
180
|
*
|
|
129
181
|
* - `Buffer` → **`"latin1"`**. We own the decode, and `latin1` is the only codec
|
|
130
182
|
* that round-trips arbitrary bytes losslessly, so MSA-2 is byte-verbatim for any
|
|
131
|
-
* charset
|
|
183
|
+
* charset, including an MSH-18 of `8859/1` with high-bit bytes in the control ID.
|
|
132
184
|
* - `string` / `Hl7Message` → **`"utf8"`**. The caller already decoded; we re-encode
|
|
133
185
|
* with the JS-native codec.
|
|
134
186
|
*
|
|
@@ -136,33 +188,52 @@ interface BuildMllpAckOptions {
|
|
|
136
188
|
* raw-`Buffer` inbound gives up the verbatim guarantee for non-ASCII bytes (`"ascii"`
|
|
137
189
|
* masks the high bit; `"utf8"` folds invalid sequences onto `U+FFFD`). On a `Buffer`,
|
|
138
190
|
* `buildMllpAck` **checks** the result and emits {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}
|
|
139
|
-
* if the echo did not survive, so a bad override there is loud rather than silent
|
|
191
|
+
* if the echo did not survive, so a bad override there is loud rather than silent, but
|
|
140
192
|
* it is still a broken ACK. Set this only when the receiving system genuinely demands a
|
|
141
193
|
* specific codec.
|
|
142
194
|
*
|
|
143
|
-
* **On a `string` / `Hl7Message` inbound the check cannot see a codec problem
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* `buildAckAA(payload.toString("latin1"))` on a high-bit control ID
|
|
148
|
-
* an `MSH-18` of `8859/1`) re-encodes it as the two `utf8` bytes `0xC2 0x8B`,
|
|
149
|
-
* **different** control ID
|
|
150
|
-
*
|
|
195
|
+
* **On a `string` / `Hl7Message` inbound the verbatim check cannot see a codec problem,
|
|
196
|
+
* so a *different* warning does the honest thing instead.** The wire bytes were decoded to
|
|
197
|
+
* text *before* this function saw them, so there is nothing left to compare the ACK against
|
|
198
|
+
* except that same text: the codec cancels on both sides and {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}
|
|
199
|
+
* can never fire. Concretely, `buildAckAA(payload.toString("latin1"))` on a high-bit control ID
|
|
200
|
+
* (`0x8B`, legal under an `MSH-18` of `8859/1`) re-encodes it as the two `utf8` bytes `0xC2 0x8B`,
|
|
201
|
+
* emitting a **different** control ID the sender cannot correlate. The verbatim check is blind to
|
|
202
|
+
* that, but the byte-safety of a text inbound cannot be *proven*, so `buildMllpAck` refuses to
|
|
203
|
+
* pass it off as verified: whenever the emitted MSA-2 holds a non-ASCII byte on a text inbound it
|
|
204
|
+
* emits {@link MLLP_ACK_CONTROL_ID_UNVERIFIABLE}, an explicit "cannot verify, pass a `Buffer`".
|
|
205
|
+
* The guard is not grown to *catch* the mismatch (the bytes are gone by then); the API stops
|
|
206
|
+
* being silent about the fact that it can't.
|
|
151
207
|
*
|
|
152
208
|
* **`Buffer` is the byte-safe path.** Pass the raw payload. It is what the server hands
|
|
153
209
|
* you, it is what the `Buffer`-first API rule exists for, and it is the only input for
|
|
154
|
-
* which the verbatim guarantee
|
|
210
|
+
* which the verbatim guarantee, or a proof that it broke, actually means anything.
|
|
211
|
+
*
|
|
212
|
+
* **Only a *text* codec is accepted, on every input shape.** This codec serializes the ACK
|
|
213
|
+
* back to bytes, so it must be one that writes characters as a byte stream the peer can read as
|
|
214
|
+
* HL7: `"utf8"`, `"ascii"`, `"latin1"`, or `"binary"`. A **non-text** codec,
|
|
215
|
+
* `"base64"`/`"base64url"`/`"hex"` (which reinterpret the ACK *string* as encoded data) or
|
|
216
|
+
* `"utf16le"`/`"ucs2"` (which NUL-pad every byte), emits a wholesale-garbage frame the receiver
|
|
217
|
+
* cannot parse, so `buildMllpAck` **throws a `TypeError`** for it here rather than hand back an
|
|
218
|
+
* unusable ACK. This applies to a `Buffer` inbound too (MLLP-ACK-NONTEXT-CODEC-BUFFER): a
|
|
219
|
+
* non-text codec there garbles the *inbound* decode into the unparseable fallback (empty MSA-2,
|
|
220
|
+
* so the {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM} check never runs) and then serializes the
|
|
221
|
+
* fallback ACK to garbage bytes that intermittently trip the strict frame encoder, it is never
|
|
222
|
+
* the "loud AE" it was once documented to be. The legitimate byte-level escape hatch is
|
|
223
|
+
* unchanged: a lossy **charset** override on a `Buffer` (`"ascii"` masking a high bit) is still
|
|
224
|
+
* accepted and still caught loudly by {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}; only the
|
|
225
|
+
* categorically-non-serializing codecs are refused.
|
|
155
226
|
*/
|
|
156
227
|
readonly encoding?: BufferEncoding;
|
|
157
228
|
/**
|
|
158
229
|
* Passthrough to `encodeFrame`'s `allowDelimiterBytesInPayload`. Defaults
|
|
159
|
-
* to `false` (strict
|
|
230
|
+
* to `false` (strict, throws if the serialized ACK somehow contains a
|
|
160
231
|
* VT/FS byte, which a spec-clean `@cosyte/hl7` emit never does).
|
|
161
232
|
*/
|
|
162
233
|
readonly allowDelimiterBytesInPayload?: boolean;
|
|
163
234
|
}
|
|
164
235
|
/**
|
|
165
|
-
* Result of {@link buildMllpAck}
|
|
236
|
+
* Result of {@link buildMllpAck}, a ready-to-write MLLP frame plus the
|
|
166
237
|
* metadata needed to log or assert on what was actually emitted. Frozen
|
|
167
238
|
* (including the `warnings` array) so subscribers cannot mutate shared state.
|
|
168
239
|
*
|
|
@@ -193,24 +264,28 @@ interface MllpAck {
|
|
|
193
264
|
readonly warnings: readonly MllpAckWarning[];
|
|
194
265
|
}
|
|
195
266
|
/**
|
|
196
|
-
* Build a framed MLLP ACK from an inbound HL7 v2 message
|
|
267
|
+
* Build a framed MLLP ACK from an inbound HL7 v2 message, the core
|
|
197
268
|
* `ack-from-hl7` adapter. `inbound` may be an already-parsed `Hl7Message`, or
|
|
198
269
|
* raw bytes/text to parse first.
|
|
199
270
|
*
|
|
200
271
|
* Behavior:
|
|
201
|
-
* - **Fatally unparseable inbound** (`Hl7ParseError`)
|
|
272
|
+
* - **Fatally unparseable inbound** (`Hl7ParseError`), never emits a
|
|
202
273
|
* positive code: `AA`→`AE`, `CA`→`CE`; other codes pass through. MSA-2 is
|
|
203
274
|
* empty, `correlationId` and `mode` are `undefined`, and `warnings`
|
|
204
275
|
* contains one {@link MLLP_ACK_INBOUND_UNPARSEABLE} entry naming the fatal
|
|
205
|
-
* code
|
|
206
|
-
* - **Normal path
|
|
276
|
+
* code, never any inbound payload bytes.
|
|
277
|
+
* - **Normal path**, detects the ack mode from inbound MSH-15/16, delegates
|
|
207
278
|
* to `@cosyte/hl7`'s `buildAck` (which applies its own no-correlation
|
|
208
279
|
* fail-safe), and forwards `buildAck`'s own warnings (mapped to
|
|
209
|
-
* `{ code, message }`). Inbound parse warnings are NOT forwarded
|
|
280
|
+
* `{ code, message }`). Inbound parse warnings are NOT forwarded, they are
|
|
210
281
|
* the caller's concern if they parsed the inbound themselves.
|
|
211
282
|
*
|
|
212
283
|
* @throws {MllpPeerMissingError} when `@cosyte/hl7` is not installed.
|
|
213
|
-
* @throws {TypeError} when `options.code` is not a known HL7 Table 0008 code
|
|
284
|
+
* @throws {TypeError} when `options.code` is not a known HL7 Table 0008 code, or when
|
|
285
|
+
* `options.encoding` is a **non-text** codec (`base64`/`base64url`/`hex`/`utf16le`/`ucs2`)
|
|
286
|
+
* on **any** inbound, it would serialize a garbage frame the receiver cannot parse
|
|
287
|
+
* (MLLP-ACK-NONTEXT-CODEC-FRAME / MLLP-ACK-NONTEXT-CODEC-BUFFER). See
|
|
288
|
+
* {@link BuildMllpAckOptions.encoding}.
|
|
214
289
|
*
|
|
215
290
|
* @example
|
|
216
291
|
* ```typescript
|
|
@@ -287,14 +362,14 @@ declare function buildAckCR(inbound: Hl7Message | Buffer | string, options?: Omi
|
|
|
287
362
|
* who import both from `@cosyte/hl7` and `@cosyte/mllp/ack-from-hl7`.
|
|
288
363
|
*
|
|
289
364
|
* `inbound` may be an already-parsed `Hl7Message`, or raw bytes/text to parse
|
|
290
|
-
* first. A fatal parse failure rethrows the `Hl7ParseError` as-is
|
|
365
|
+
* first. A fatal parse failure rethrows the `Hl7ParseError` as-is, unlike
|
|
291
366
|
* {@link buildMllpAck}, there is no ACK to build here, so there is no
|
|
292
367
|
* fallback to construct.
|
|
293
368
|
*
|
|
294
369
|
* **Module-realm note:** this adapter loads `@cosyte/hl7` lazily via
|
|
295
370
|
* `createRequire` (see `peer.ts`). If your code ALSO does
|
|
296
371
|
* `import { Hl7ParseError } from "@cosyte/hl7"` directly, Node's module
|
|
297
|
-
* resolution can
|
|
372
|
+
* resolution can, depending on your bundler/runtime, hand you a distinct
|
|
298
373
|
* module instance than the one this adapter loaded, in which case
|
|
299
374
|
* `caught instanceof Hl7ParseError` (your import) may be `false` even though
|
|
300
375
|
* `caught.name === "Hl7ParseError"` and `caught.code` is a valid HL7 fatal
|
|
@@ -318,8 +393,8 @@ declare function detectMode(inbound: Hl7Message | Buffer | string): AckMode;
|
|
|
318
393
|
*
|
|
319
394
|
* `@cosyte/mllp` ships **zero runtime dependencies**; `@cosyte/hl7` is an
|
|
320
395
|
* optional peer dep referenced only from this `/ack-from-hl7` subpath. To keep
|
|
321
|
-
* the rest of the package fully dependency-free, the peer is required lazily
|
|
322
|
-
* on first call into `ack-from-hl7
|
|
396
|
+
* the rest of the package fully dependency-free, the peer is required lazily,
|
|
397
|
+
* on first call into `ack-from-hl7`, via `createRequire`, not at module load
|
|
323
398
|
* time. The public API of this subpath stays synchronous throughout.
|
|
324
399
|
*
|
|
325
400
|
* @example
|
|
@@ -334,7 +409,7 @@ declare function detectMode(inbound: Hl7Message | Buffer | string): AckMode;
|
|
|
334
409
|
/**
|
|
335
410
|
* The subset of the `@cosyte/hl7` runtime surface the `ack-from-hl7` adapter
|
|
336
411
|
* consumes. Kept narrow and typed via `import type` (erased at compile time)
|
|
337
|
-
* so the runtime dependency stays fully lazy
|
|
412
|
+
* so the runtime dependency stays fully lazy, only the shape is known
|
|
338
413
|
* statically, the values are resolved via `require()` at first call.
|
|
339
414
|
*
|
|
340
415
|
* @example
|
|
@@ -354,7 +429,7 @@ interface Hl7Peer {
|
|
|
354
429
|
}) => Hl7Message;
|
|
355
430
|
/**
|
|
356
431
|
* The peer's own `Hl7Message` class. Exposed (not just used as a type) so
|
|
357
|
-
* callers can detect a cross-realm instance
|
|
432
|
+
* callers can detect a cross-realm instance, see the dual-package-hazard
|
|
358
433
|
* note on `resolveInbound` in `build.ts`. Typed as `abstract new` (rather
|
|
359
434
|
* than a concrete constructor signature) purely so TypeScript accepts it as
|
|
360
435
|
* an `instanceof` right-hand side without also implying callers can `new`
|
|
@@ -366,14 +441,14 @@ interface Hl7Peer {
|
|
|
366
441
|
readonly ACK_CODES: typeof ACK_CODES;
|
|
367
442
|
/**
|
|
368
443
|
* The peer's fail-safe downgrade primitive (`AA`→`AE`, `CA`→`CE`, everything
|
|
369
|
-
* else unchanged)
|
|
444
|
+
* else unchanged), the single upstream source of truth for the pair; this
|
|
370
445
|
* adapter never carries its own copy of the mapping.
|
|
371
446
|
*/
|
|
372
447
|
readonly downgradePositiveAck: (code: AckCode) => AckCode;
|
|
373
448
|
}
|
|
374
449
|
/**
|
|
375
|
-
* Thrown when `@cosyte/hl7
|
|
376
|
-
* `@cosyte/mllp/ack-from-hl7
|
|
450
|
+
* Thrown when `@cosyte/hl7`, the optional peer dependency required by
|
|
451
|
+
* `@cosyte/mllp/ack-from-hl7`, cannot be resolved at runtime.
|
|
377
452
|
*
|
|
378
453
|
* `@cosyte/hl7` is declared as an optional peer dependency, so a consumer of
|
|
379
454
|
* the root `@cosyte/mllp` entry point is never forced to install it. Only
|
|
@@ -405,7 +480,7 @@ declare class MllpPeerMissingError extends Error {
|
|
|
405
480
|
}
|
|
406
481
|
/**
|
|
407
482
|
* Load the `@cosyte/hl7` peer module, caching the result for subsequent
|
|
408
|
-
* calls. The public adapter functions in `build.ts` call this internally
|
|
483
|
+
* calls. The public adapter functions in `build.ts` call this internally,
|
|
409
484
|
* most consumers never call it directly.
|
|
410
485
|
*
|
|
411
486
|
* @param requireFn - Injectable `require`-like function, used by tests to
|
|
@@ -422,4 +497,4 @@ declare class MllpPeerMissingError extends Error {
|
|
|
422
497
|
*/
|
|
423
498
|
declare function loadHl7Peer(requireFn?: (id: string) => unknown): Hl7Peer;
|
|
424
499
|
|
|
425
|
-
export { type BuildMllpAckOptions, type Hl7Peer, MLLP_ACK_CONTROL_ID_NOT_VERBATIM, MLLP_ACK_INBOUND_UNPARSEABLE, type MllpAck, type MllpAckWarning, MllpPeerMissingError, buildAckAA, buildAckAE, buildAckAR, buildAckCA, buildAckCE, buildAckCR, buildMllpAck, detectMode, loadHl7Peer };
|
|
500
|
+
export { type BuildMllpAckOptions, type Hl7Peer, MLLP_ACK_CONTROL_ID_NOT_VERBATIM, MLLP_ACK_CONTROL_ID_UNVERIFIABLE, MLLP_ACK_INBOUND_UNPARSEABLE, type MllpAck, type MllpAckWarning, MllpPeerMissingError, buildAckAA, buildAckAE, buildAckAR, buildAckCA, buildAckCE, buildAckCR, buildMllpAck, detectMode, loadHl7Peer };
|
|
@@ -2,7 +2,7 @@ import { AckCode, AckErrorDetail, Hl7Message, AckMode, BuildAckOptions, Hl7Parse
|
|
|
2
2
|
export { AckCode, AckErrorDetail, AckMode } from '@cosyte/hl7';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* `buildMllpAck
|
|
5
|
+
* `buildMllpAck`, a THIN transport adapter over `@cosyte/hl7`'s `buildAck`.
|
|
6
6
|
*
|
|
7
7
|
* `@cosyte/hl7` owns ACK **content** (MSH/MSA/ERR construction, the fail-safe
|
|
8
8
|
* no-correlation downgrade); this module owns **transport policy**: accepting
|
|
@@ -53,7 +53,7 @@ interface MllpAckWarning {
|
|
|
53
53
|
declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
54
54
|
/**
|
|
55
55
|
* Warning code emitted when the built ACK's MSA-2 bytes are **not** byte-identical
|
|
56
|
-
* to the inbound MSH-10 bytes
|
|
56
|
+
* to the inbound MSH-10 bytes, i.e. the ACK does not echo the message control ID
|
|
57
57
|
* verbatim, as HL7 v2.5.1 §2.9.2.2 requires.
|
|
58
58
|
*
|
|
59
59
|
* This is a **correlation failure**, and the reason it is a loud warning rather
|
|
@@ -61,7 +61,7 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
61
61
|
* it put on the wire. An ACK whose MSA-2 differs by even one byte will not match
|
|
62
62
|
* that key, so the send is never settled → ACK timeout → resend → **duplicate
|
|
63
63
|
* clinical message**. Emitting the ACK anyway (rather than throwing) is the
|
|
64
|
-
* fail-safe choice
|
|
64
|
+
* fail-safe choice, a mismatched ACK still tells the peer *something*, but the
|
|
65
65
|
* mismatch must never pass unremarked.
|
|
66
66
|
*
|
|
67
67
|
* `buildMllpAck` verifies this on every build, against the very same byte-level
|
|
@@ -73,7 +73,7 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
73
73
|
* `string`/`Hl7Message` inbound has **already been decoded** before this module sees it:
|
|
74
74
|
* the only thing left to compare against is that same text, encoded with the same codec,
|
|
75
75
|
* so the codec cancels on both sides and a codec-induced mismatch is **structurally
|
|
76
|
-
* invisible
|
|
76
|
+
* invisible**, it warns about nothing. See {@link BuildMllpAckOptions.encoding}. Pass a
|
|
77
77
|
* `Buffer` if you want this guarantee to mean anything.
|
|
78
78
|
*
|
|
79
79
|
* Two known ways to provoke it, both documented in the package limitations:
|
|
@@ -84,7 +84,7 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
84
84
|
* 2. An inbound that declares **non-default encoding characters** (MSH-1/MSH-2).
|
|
85
85
|
* `@cosyte/hl7`'s `buildMessage` always emits the HL7 default `|^~\&`, so an
|
|
86
86
|
* inbound MSH-10 of `ID#X` under a `#` component separator is re-delimited to
|
|
87
|
-
* `ID^X` in MSA-2
|
|
87
|
+
* `ID^X` in MSA-2, different bytes, unmatchable key. Use `buildRawAck` (root
|
|
88
88
|
* export), which echoes the inbound's own MSH-1/MSH-2, if you must serve such
|
|
89
89
|
* a peer.
|
|
90
90
|
*
|
|
@@ -93,11 +93,63 @@ declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
|
93
93
|
* import { MLLP_ACK_CONTROL_ID_NOT_VERBATIM, buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
94
94
|
* const { warnings } = buildAckAA(inbound, { encoding: "ascii" });
|
|
95
95
|
* if (warnings.some((w) => w.code === MLLP_ACK_CONTROL_ID_NOT_VERBATIM)) {
|
|
96
|
-
* // this ACK will NOT correlate at the sender
|
|
96
|
+
* // this ACK will NOT correlate at the sender, investigate before shipping it
|
|
97
97
|
* }
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
|
100
100
|
declare const MLLP_ACK_CONTROL_ID_NOT_VERBATIM = "MLLP_ACK_CONTROL_ID_NOT_VERBATIM";
|
|
101
|
+
/**
|
|
102
|
+
* Warning code emitted when the inbound was handed in as **text** (a `string` or an
|
|
103
|
+
* already-parsed `Hl7Message`) rather than as raw `Buffer` bytes, and the ACK's MSA-2
|
|
104
|
+
* carries a **non-ASCII** control ID, a combination whose byte-verbatim echo
|
|
105
|
+
* (HL7 v2.5.1 §2.9.2.2) `buildMllpAck` **cannot verify**, and may silently be breaking.
|
|
106
|
+
*
|
|
107
|
+
* ## Why a distinct code from {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}
|
|
108
|
+
*
|
|
109
|
+
* `MLLP_ACK_CONTROL_ID_NOT_VERBATIM` is a *proof of failure*: on a `Buffer` inbound the
|
|
110
|
+
* wire bytes are in hand, the check compares MSA-2 against them, and it fires only when
|
|
111
|
+
* they provably differ. This code is the opposite, a *confession that the proof cannot
|
|
112
|
+
* be run*. A `string`/`Hl7Message` inbound was decoded from its wire bytes **before** this
|
|
113
|
+
* module ever saw it, so the only thing left to compare the ACK against is that same text
|
|
114
|
+
* re-encoded with the same codec: the codec cancels on both sides and the verbatim check
|
|
115
|
+
* becomes a tautology that always passes (see {@link verifyVerbatimEcho}). It would report
|
|
116
|
+
* clean even for `buildAckAA(payload.toString("latin1"))` on a high-bit control ID, where
|
|
117
|
+
* a `latin1`-decoded `0x8B` is re-encoded as the two `utf8` bytes `0xC2 0x8B`, a *different*
|
|
118
|
+
* control ID the sender cannot correlate (timeout → resend → **duplicate clinical message**).
|
|
119
|
+
*
|
|
120
|
+
* The guard cannot be grown to catch that; by the time text arrives the bytes are gone.
|
|
121
|
+
* The API can, and does: rather than let the text path pass a codec-sensitive control ID
|
|
122
|
+
* off as verified, `buildMllpAck` refuses to stay silent about it. Whenever the ACK's MSA-2
|
|
123
|
+
* control ID holds a code unit outside `0x00`–`0x7F` on a text inbound, the range where the
|
|
124
|
+
* codec choice is load-bearing, it emits this warning. An all-ASCII control ID round-trips
|
|
125
|
+
* identically under every codec, so the common case stays quiet; a non-ASCII one is flagged as
|
|
126
|
+
* *unverifiable*, not as *known-broken*, because from a decoded string the two are genuinely
|
|
127
|
+
* indistinguishable.
|
|
128
|
+
*
|
|
129
|
+
* The check reads the **pre-encoding code units** of MSA-2, not the emitted bytes, so a lossy
|
|
130
|
+
* `{ encoding: "ascii" }` override, which truncates a code unit to its low 8 bits (`str -> byte &
|
|
131
|
+
* 0xFF`) and so masks a code unit above `0xFF` *into* the ASCII byte range on the way out (`U+0153`
|
|
132
|
+
* -> `0x53`), cannot slip a corrupted control ID past it silently (MLLP-ACK-ASCII-OVERRIDE-BLEED).
|
|
133
|
+
* The strongly-discouraged text-plus-override path is covered for the same reason the default `utf8`
|
|
134
|
+
* text path is; the `Buffer` overload remains the answer.
|
|
135
|
+
*
|
|
136
|
+
* **The fix the warning points at is the `Buffer`-first API rule.** Pass the raw payload,
|
|
137
|
+
* the bytes the server handed you, and you get the real byte-level check (and, if it breaks,
|
|
138
|
+
* the definite {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}) instead of this "cannot tell".
|
|
139
|
+
*
|
|
140
|
+
* Like its sibling, this warning carries byte **lengths** only, never the field bytes
|
|
141
|
+
* (MSH-10 is inbound payload content and a warning goes to a log; see {@link verifyVerbatimEcho}).
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { MLLP_ACK_CONTROL_ID_UNVERIFIABLE, buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
146
|
+
* const { warnings } = buildAckAA(payload.toString("latin1")); // text inbound, high-bit id
|
|
147
|
+
* if (warnings.some((w) => w.code === MLLP_ACK_CONTROL_ID_UNVERIFIABLE)) {
|
|
148
|
+
* // pass the raw Buffer instead, the echo cannot be verified from decoded text
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
declare const MLLP_ACK_CONTROL_ID_UNVERIFIABLE = "MLLP_ACK_CONTROL_ID_UNVERIFIABLE";
|
|
101
153
|
/**
|
|
102
154
|
* Options for {@link buildMllpAck} and its six convenience wrappers.
|
|
103
155
|
*
|
|
@@ -113,7 +165,7 @@ interface BuildMllpAckOptions {
|
|
|
113
165
|
/** Optional error detail; one entry emits one ERR segment. */
|
|
114
166
|
readonly error?: AckErrorDetail | readonly AckErrorDetail[];
|
|
115
167
|
/**
|
|
116
|
-
* The codec used to move the ACK between bytes and text
|
|
168
|
+
* The codec used to move the ACK between bytes and text, **both** directions,
|
|
117
169
|
* as a single symmetric choice. It decodes a raw-`Buffer` `inbound` on the way in
|
|
118
170
|
* and encodes the serialized ACK on the way out, so the ACK is emitted in the same
|
|
119
171
|
* codec the inbound was read in.
|
|
@@ -122,13 +174,13 @@ interface BuildMllpAckOptions {
|
|
|
122
174
|
* **verbatim** (HL7 v2.5.1 §2.9.2.2). Decoding in one codec and encoding in another
|
|
123
175
|
* corrupts every non-ASCII byte it round-trips: a `latin1`-decoded `0x8B` re-encoded
|
|
124
176
|
* as `utf8` goes out as the two bytes `0xC2 0x8B`, which is a *different control ID*
|
|
125
|
-
*
|
|
177
|
+
* and the sender, which keyed its in-flight store on `0x8B`, cannot match the ACK.
|
|
126
178
|
*
|
|
127
179
|
* **Defaults, by inbound type:**
|
|
128
180
|
*
|
|
129
181
|
* - `Buffer` → **`"latin1"`**. We own the decode, and `latin1` is the only codec
|
|
130
182
|
* that round-trips arbitrary bytes losslessly, so MSA-2 is byte-verbatim for any
|
|
131
|
-
* charset
|
|
183
|
+
* charset, including an MSH-18 of `8859/1` with high-bit bytes in the control ID.
|
|
132
184
|
* - `string` / `Hl7Message` → **`"utf8"`**. The caller already decoded; we re-encode
|
|
133
185
|
* with the JS-native codec.
|
|
134
186
|
*
|
|
@@ -136,33 +188,52 @@ interface BuildMllpAckOptions {
|
|
|
136
188
|
* raw-`Buffer` inbound gives up the verbatim guarantee for non-ASCII bytes (`"ascii"`
|
|
137
189
|
* masks the high bit; `"utf8"` folds invalid sequences onto `U+FFFD`). On a `Buffer`,
|
|
138
190
|
* `buildMllpAck` **checks** the result and emits {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}
|
|
139
|
-
* if the echo did not survive, so a bad override there is loud rather than silent
|
|
191
|
+
* if the echo did not survive, so a bad override there is loud rather than silent, but
|
|
140
192
|
* it is still a broken ACK. Set this only when the receiving system genuinely demands a
|
|
141
193
|
* specific codec.
|
|
142
194
|
*
|
|
143
|
-
* **On a `string` / `Hl7Message` inbound the check cannot see a codec problem
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* `buildAckAA(payload.toString("latin1"))` on a high-bit control ID
|
|
148
|
-
* an `MSH-18` of `8859/1`) re-encodes it as the two `utf8` bytes `0xC2 0x8B`,
|
|
149
|
-
* **different** control ID
|
|
150
|
-
*
|
|
195
|
+
* **On a `string` / `Hl7Message` inbound the verbatim check cannot see a codec problem,
|
|
196
|
+
* so a *different* warning does the honest thing instead.** The wire bytes were decoded to
|
|
197
|
+
* text *before* this function saw them, so there is nothing left to compare the ACK against
|
|
198
|
+
* except that same text: the codec cancels on both sides and {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}
|
|
199
|
+
* can never fire. Concretely, `buildAckAA(payload.toString("latin1"))` on a high-bit control ID
|
|
200
|
+
* (`0x8B`, legal under an `MSH-18` of `8859/1`) re-encodes it as the two `utf8` bytes `0xC2 0x8B`,
|
|
201
|
+
* emitting a **different** control ID the sender cannot correlate. The verbatim check is blind to
|
|
202
|
+
* that, but the byte-safety of a text inbound cannot be *proven*, so `buildMllpAck` refuses to
|
|
203
|
+
* pass it off as verified: whenever the emitted MSA-2 holds a non-ASCII byte on a text inbound it
|
|
204
|
+
* emits {@link MLLP_ACK_CONTROL_ID_UNVERIFIABLE}, an explicit "cannot verify, pass a `Buffer`".
|
|
205
|
+
* The guard is not grown to *catch* the mismatch (the bytes are gone by then); the API stops
|
|
206
|
+
* being silent about the fact that it can't.
|
|
151
207
|
*
|
|
152
208
|
* **`Buffer` is the byte-safe path.** Pass the raw payload. It is what the server hands
|
|
153
209
|
* you, it is what the `Buffer`-first API rule exists for, and it is the only input for
|
|
154
|
-
* which the verbatim guarantee
|
|
210
|
+
* which the verbatim guarantee, or a proof that it broke, actually means anything.
|
|
211
|
+
*
|
|
212
|
+
* **Only a *text* codec is accepted, on every input shape.** This codec serializes the ACK
|
|
213
|
+
* back to bytes, so it must be one that writes characters as a byte stream the peer can read as
|
|
214
|
+
* HL7: `"utf8"`, `"ascii"`, `"latin1"`, or `"binary"`. A **non-text** codec,
|
|
215
|
+
* `"base64"`/`"base64url"`/`"hex"` (which reinterpret the ACK *string* as encoded data) or
|
|
216
|
+
* `"utf16le"`/`"ucs2"` (which NUL-pad every byte), emits a wholesale-garbage frame the receiver
|
|
217
|
+
* cannot parse, so `buildMllpAck` **throws a `TypeError`** for it here rather than hand back an
|
|
218
|
+
* unusable ACK. This applies to a `Buffer` inbound too (MLLP-ACK-NONTEXT-CODEC-BUFFER): a
|
|
219
|
+
* non-text codec there garbles the *inbound* decode into the unparseable fallback (empty MSA-2,
|
|
220
|
+
* so the {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM} check never runs) and then serializes the
|
|
221
|
+
* fallback ACK to garbage bytes that intermittently trip the strict frame encoder, it is never
|
|
222
|
+
* the "loud AE" it was once documented to be. The legitimate byte-level escape hatch is
|
|
223
|
+
* unchanged: a lossy **charset** override on a `Buffer` (`"ascii"` masking a high bit) is still
|
|
224
|
+
* accepted and still caught loudly by {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}; only the
|
|
225
|
+
* categorically-non-serializing codecs are refused.
|
|
155
226
|
*/
|
|
156
227
|
readonly encoding?: BufferEncoding;
|
|
157
228
|
/**
|
|
158
229
|
* Passthrough to `encodeFrame`'s `allowDelimiterBytesInPayload`. Defaults
|
|
159
|
-
* to `false` (strict
|
|
230
|
+
* to `false` (strict, throws if the serialized ACK somehow contains a
|
|
160
231
|
* VT/FS byte, which a spec-clean `@cosyte/hl7` emit never does).
|
|
161
232
|
*/
|
|
162
233
|
readonly allowDelimiterBytesInPayload?: boolean;
|
|
163
234
|
}
|
|
164
235
|
/**
|
|
165
|
-
* Result of {@link buildMllpAck}
|
|
236
|
+
* Result of {@link buildMllpAck}, a ready-to-write MLLP frame plus the
|
|
166
237
|
* metadata needed to log or assert on what was actually emitted. Frozen
|
|
167
238
|
* (including the `warnings` array) so subscribers cannot mutate shared state.
|
|
168
239
|
*
|
|
@@ -193,24 +264,28 @@ interface MllpAck {
|
|
|
193
264
|
readonly warnings: readonly MllpAckWarning[];
|
|
194
265
|
}
|
|
195
266
|
/**
|
|
196
|
-
* Build a framed MLLP ACK from an inbound HL7 v2 message
|
|
267
|
+
* Build a framed MLLP ACK from an inbound HL7 v2 message, the core
|
|
197
268
|
* `ack-from-hl7` adapter. `inbound` may be an already-parsed `Hl7Message`, or
|
|
198
269
|
* raw bytes/text to parse first.
|
|
199
270
|
*
|
|
200
271
|
* Behavior:
|
|
201
|
-
* - **Fatally unparseable inbound** (`Hl7ParseError`)
|
|
272
|
+
* - **Fatally unparseable inbound** (`Hl7ParseError`), never emits a
|
|
202
273
|
* positive code: `AA`→`AE`, `CA`→`CE`; other codes pass through. MSA-2 is
|
|
203
274
|
* empty, `correlationId` and `mode` are `undefined`, and `warnings`
|
|
204
275
|
* contains one {@link MLLP_ACK_INBOUND_UNPARSEABLE} entry naming the fatal
|
|
205
|
-
* code
|
|
206
|
-
* - **Normal path
|
|
276
|
+
* code, never any inbound payload bytes.
|
|
277
|
+
* - **Normal path**, detects the ack mode from inbound MSH-15/16, delegates
|
|
207
278
|
* to `@cosyte/hl7`'s `buildAck` (which applies its own no-correlation
|
|
208
279
|
* fail-safe), and forwards `buildAck`'s own warnings (mapped to
|
|
209
|
-
* `{ code, message }`). Inbound parse warnings are NOT forwarded
|
|
280
|
+
* `{ code, message }`). Inbound parse warnings are NOT forwarded, they are
|
|
210
281
|
* the caller's concern if they parsed the inbound themselves.
|
|
211
282
|
*
|
|
212
283
|
* @throws {MllpPeerMissingError} when `@cosyte/hl7` is not installed.
|
|
213
|
-
* @throws {TypeError} when `options.code` is not a known HL7 Table 0008 code
|
|
284
|
+
* @throws {TypeError} when `options.code` is not a known HL7 Table 0008 code, or when
|
|
285
|
+
* `options.encoding` is a **non-text** codec (`base64`/`base64url`/`hex`/`utf16le`/`ucs2`)
|
|
286
|
+
* on **any** inbound, it would serialize a garbage frame the receiver cannot parse
|
|
287
|
+
* (MLLP-ACK-NONTEXT-CODEC-FRAME / MLLP-ACK-NONTEXT-CODEC-BUFFER). See
|
|
288
|
+
* {@link BuildMllpAckOptions.encoding}.
|
|
214
289
|
*
|
|
215
290
|
* @example
|
|
216
291
|
* ```typescript
|
|
@@ -287,14 +362,14 @@ declare function buildAckCR(inbound: Hl7Message | Buffer | string, options?: Omi
|
|
|
287
362
|
* who import both from `@cosyte/hl7` and `@cosyte/mllp/ack-from-hl7`.
|
|
288
363
|
*
|
|
289
364
|
* `inbound` may be an already-parsed `Hl7Message`, or raw bytes/text to parse
|
|
290
|
-
* first. A fatal parse failure rethrows the `Hl7ParseError` as-is
|
|
365
|
+
* first. A fatal parse failure rethrows the `Hl7ParseError` as-is, unlike
|
|
291
366
|
* {@link buildMllpAck}, there is no ACK to build here, so there is no
|
|
292
367
|
* fallback to construct.
|
|
293
368
|
*
|
|
294
369
|
* **Module-realm note:** this adapter loads `@cosyte/hl7` lazily via
|
|
295
370
|
* `createRequire` (see `peer.ts`). If your code ALSO does
|
|
296
371
|
* `import { Hl7ParseError } from "@cosyte/hl7"` directly, Node's module
|
|
297
|
-
* resolution can
|
|
372
|
+
* resolution can, depending on your bundler/runtime, hand you a distinct
|
|
298
373
|
* module instance than the one this adapter loaded, in which case
|
|
299
374
|
* `caught instanceof Hl7ParseError` (your import) may be `false` even though
|
|
300
375
|
* `caught.name === "Hl7ParseError"` and `caught.code` is a valid HL7 fatal
|
|
@@ -318,8 +393,8 @@ declare function detectMode(inbound: Hl7Message | Buffer | string): AckMode;
|
|
|
318
393
|
*
|
|
319
394
|
* `@cosyte/mllp` ships **zero runtime dependencies**; `@cosyte/hl7` is an
|
|
320
395
|
* optional peer dep referenced only from this `/ack-from-hl7` subpath. To keep
|
|
321
|
-
* the rest of the package fully dependency-free, the peer is required lazily
|
|
322
|
-
* on first call into `ack-from-hl7
|
|
396
|
+
* the rest of the package fully dependency-free, the peer is required lazily,
|
|
397
|
+
* on first call into `ack-from-hl7`, via `createRequire`, not at module load
|
|
323
398
|
* time. The public API of this subpath stays synchronous throughout.
|
|
324
399
|
*
|
|
325
400
|
* @example
|
|
@@ -334,7 +409,7 @@ declare function detectMode(inbound: Hl7Message | Buffer | string): AckMode;
|
|
|
334
409
|
/**
|
|
335
410
|
* The subset of the `@cosyte/hl7` runtime surface the `ack-from-hl7` adapter
|
|
336
411
|
* consumes. Kept narrow and typed via `import type` (erased at compile time)
|
|
337
|
-
* so the runtime dependency stays fully lazy
|
|
412
|
+
* so the runtime dependency stays fully lazy, only the shape is known
|
|
338
413
|
* statically, the values are resolved via `require()` at first call.
|
|
339
414
|
*
|
|
340
415
|
* @example
|
|
@@ -354,7 +429,7 @@ interface Hl7Peer {
|
|
|
354
429
|
}) => Hl7Message;
|
|
355
430
|
/**
|
|
356
431
|
* The peer's own `Hl7Message` class. Exposed (not just used as a type) so
|
|
357
|
-
* callers can detect a cross-realm instance
|
|
432
|
+
* callers can detect a cross-realm instance, see the dual-package-hazard
|
|
358
433
|
* note on `resolveInbound` in `build.ts`. Typed as `abstract new` (rather
|
|
359
434
|
* than a concrete constructor signature) purely so TypeScript accepts it as
|
|
360
435
|
* an `instanceof` right-hand side without also implying callers can `new`
|
|
@@ -366,14 +441,14 @@ interface Hl7Peer {
|
|
|
366
441
|
readonly ACK_CODES: typeof ACK_CODES;
|
|
367
442
|
/**
|
|
368
443
|
* The peer's fail-safe downgrade primitive (`AA`→`AE`, `CA`→`CE`, everything
|
|
369
|
-
* else unchanged)
|
|
444
|
+
* else unchanged), the single upstream source of truth for the pair; this
|
|
370
445
|
* adapter never carries its own copy of the mapping.
|
|
371
446
|
*/
|
|
372
447
|
readonly downgradePositiveAck: (code: AckCode) => AckCode;
|
|
373
448
|
}
|
|
374
449
|
/**
|
|
375
|
-
* Thrown when `@cosyte/hl7
|
|
376
|
-
* `@cosyte/mllp/ack-from-hl7
|
|
450
|
+
* Thrown when `@cosyte/hl7`, the optional peer dependency required by
|
|
451
|
+
* `@cosyte/mllp/ack-from-hl7`, cannot be resolved at runtime.
|
|
377
452
|
*
|
|
378
453
|
* `@cosyte/hl7` is declared as an optional peer dependency, so a consumer of
|
|
379
454
|
* the root `@cosyte/mllp` entry point is never forced to install it. Only
|
|
@@ -405,7 +480,7 @@ declare class MllpPeerMissingError extends Error {
|
|
|
405
480
|
}
|
|
406
481
|
/**
|
|
407
482
|
* Load the `@cosyte/hl7` peer module, caching the result for subsequent
|
|
408
|
-
* calls. The public adapter functions in `build.ts` call this internally
|
|
483
|
+
* calls. The public adapter functions in `build.ts` call this internally,
|
|
409
484
|
* most consumers never call it directly.
|
|
410
485
|
*
|
|
411
486
|
* @param requireFn - Injectable `require`-like function, used by tests to
|
|
@@ -422,4 +497,4 @@ declare class MllpPeerMissingError extends Error {
|
|
|
422
497
|
*/
|
|
423
498
|
declare function loadHl7Peer(requireFn?: (id: string) => unknown): Hl7Peer;
|
|
424
499
|
|
|
425
|
-
export { type BuildMllpAckOptions, type Hl7Peer, MLLP_ACK_CONTROL_ID_NOT_VERBATIM, MLLP_ACK_INBOUND_UNPARSEABLE, type MllpAck, type MllpAckWarning, MllpPeerMissingError, buildAckAA, buildAckAE, buildAckAR, buildAckCA, buildAckCE, buildAckCR, buildMllpAck, detectMode, loadHl7Peer };
|
|
500
|
+
export { type BuildMllpAckOptions, type Hl7Peer, MLLP_ACK_CONTROL_ID_NOT_VERBATIM, MLLP_ACK_CONTROL_ID_UNVERIFIABLE, MLLP_ACK_INBOUND_UNPARSEABLE, type MllpAck, type MllpAckWarning, MllpPeerMissingError, buildAckAA, buildAckAE, buildAckAR, buildAckCA, buildAckCE, buildAckCR, buildMllpAck, detectMode, loadHl7Peer };
|