@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
|
@@ -16,13 +16,13 @@ var MllpFramingError = class _MllpFramingError extends Error {
|
|
|
16
16
|
/**
|
|
17
17
|
* Up to 64 bytes copied from around the anomaly.
|
|
18
18
|
*
|
|
19
|
-
* This is a **copied** Buffer
|
|
19
|
+
* This is a **copied** Buffer, isolated from the source buffer so it remains
|
|
20
20
|
* valid after the underlying buffer is reused or overwritten.
|
|
21
21
|
*
|
|
22
22
|
* **PHI contract (MLLP-9):** the decoder only ever populates this with the single
|
|
23
23
|
* framing-boundary byte that violated the frame structure (whose hex value the
|
|
24
|
-
* `message` already discloses)
|
|
25
|
-
* whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE
|
|
24
|
+
* `message` already discloses), never a run of payload content bytes. For anomalies
|
|
25
|
+
* whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE`, the accumulated size),
|
|
26
26
|
* the snippet is **empty**: a payload slice on a public error field would leak a
|
|
27
27
|
* field-body slice of clinical content. Callers constructing this directly are
|
|
28
28
|
* responsible for the same discipline.
|
|
@@ -104,9 +104,9 @@ var UNSAFE_FIELD_SEPARATORS = /* @__PURE__ */ new Set([
|
|
|
104
104
|
SEGMENT_SEPARATOR_CR,
|
|
105
105
|
SEGMENT_SEPARATOR_LF,
|
|
106
106
|
11,
|
|
107
|
-
// VT
|
|
107
|
+
// VT, MLLP start block
|
|
108
108
|
28
|
|
109
|
-
// FS
|
|
109
|
+
// FS, MLLP end block
|
|
110
110
|
]);
|
|
111
111
|
function isMshSegmentAt(buf, i) {
|
|
112
112
|
return i + 3 < buf.length && buf[i] === ASCII_M && buf[i + 1] === ASCII_S && buf[i + 2] === ASCII_H && !UNSAFE_FIELD_SEPARATORS.has(buf[i + 3]);
|
|
@@ -205,7 +205,7 @@ var MllpPeerMissingError = class _MllpPeerMissingError extends Error {
|
|
|
205
205
|
*/
|
|
206
206
|
constructor(cause) {
|
|
207
207
|
super(
|
|
208
|
-
`${PEER_PACKAGE_NAME} is required by ${SUBPATH_NAME} but is not installed. ${PEER_PACKAGE_NAME} is an optional peer dependency of @cosyte/mllp
|
|
208
|
+
`${PEER_PACKAGE_NAME} is required by ${SUBPATH_NAME} but is not installed. ${PEER_PACKAGE_NAME} is an optional peer dependency of @cosyte/mllp, install it alongside @cosyte/mllp to use ${SUBPATH_NAME}: \`npm install ${PEER_PACKAGE_NAME}\` (or the equivalent for your package manager).`,
|
|
209
209
|
{ cause }
|
|
210
210
|
);
|
|
211
211
|
if (Error.captureStackTrace) {
|
|
@@ -246,8 +246,16 @@ function loadHl7Peer(requireFn) {
|
|
|
246
246
|
// src/ack-from-hl7/build.ts
|
|
247
247
|
var MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
248
248
|
var MLLP_ACK_CONTROL_ID_NOT_VERBATIM = "MLLP_ACK_CONTROL_ID_NOT_VERBATIM";
|
|
249
|
+
var MLLP_ACK_CONTROL_ID_UNVERIFIABLE = "MLLP_ACK_CONTROL_ID_UNVERIFIABLE";
|
|
249
250
|
var BYTES_INBOUND_ENCODING = "latin1";
|
|
250
251
|
var TEXT_INBOUND_ENCODING = "utf8";
|
|
252
|
+
var SERIALIZABLE_ACK_ENCODINGS = /* @__PURE__ */ new Set([
|
|
253
|
+
"utf8",
|
|
254
|
+
"utf-8",
|
|
255
|
+
"ascii",
|
|
256
|
+
"latin1",
|
|
257
|
+
"binary"
|
|
258
|
+
]);
|
|
251
259
|
function assertKnownAckCode(peer, code) {
|
|
252
260
|
const known = Object.values(peer.ACK_CODES);
|
|
253
261
|
if (typeof code !== "string" || !known.includes(code)) {
|
|
@@ -260,6 +268,12 @@ function resolveEncoding(inbound, options) {
|
|
|
260
268
|
if (options.encoding !== void 0) return options.encoding;
|
|
261
269
|
return Buffer.isBuffer(inbound) ? BYTES_INBOUND_ENCODING : TEXT_INBOUND_ENCODING;
|
|
262
270
|
}
|
|
271
|
+
function assertSerializableAckEncoding(encoding) {
|
|
272
|
+
if (SERIALIZABLE_ACK_ENCODINGS.has(encoding.toLowerCase())) return;
|
|
273
|
+
throw new TypeError(
|
|
274
|
+
`buildMllpAck: encoding ${JSON.stringify(encoding)} is not a serializable ACK codec. Serializing an ACK to a non-text codec (base64/base64url/hex/utf16le/ucs2) produces a wholesale-garbage frame the receiver cannot parse, on a Buffer inbound it also garbles the inbound decode, so it can never correlate. Use a text codec ("utf8", "ascii", or "latin1"); on a Buffer, "latin1" is byte-verbatim and is the default.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
263
277
|
function inboundBytes(inbound, encoding) {
|
|
264
278
|
if (Buffer.isBuffer(inbound)) return inbound;
|
|
265
279
|
if (typeof inbound === "string") return Buffer.from(inbound, encoding);
|
|
@@ -275,7 +289,23 @@ function verifyVerbatimEcho(ackPayload, inboundRaw) {
|
|
|
275
289
|
const echoed = echoedId === null ? "absent" : `${String(echoedId.length)} bytes`;
|
|
276
290
|
return {
|
|
277
291
|
code: MLLP_ACK_CONTROL_ID_NOT_VERBATIM,
|
|
278
|
-
message: `ACK does not echo the inbound MSH-10 verbatim (HL7 v2.5.1 \xA72.9.2.2): inbound MSH-10 is ${inboundLen} bytes, emitted MSA-2 is ${echoed}, and they differ. The sender keys its in-flight store on the inbound bytes, so it will NOT match this ACK (ACK timeout -> resend -> duplicate message). Field values are withheld
|
|
292
|
+
message: `ACK does not echo the inbound MSH-10 verbatim (HL7 v2.5.1 \xA72.9.2.2): inbound MSH-10 is ${inboundLen} bytes, emitted MSA-2 is ${echoed}, and they differ. The sender keys its in-flight store on the inbound bytes, so it will NOT match this ACK (ACK timeout -> resend -> duplicate message). Field values are withheld, MSH-10 is inbound payload content and this warning goes to a log; compare your inbound payload against MllpAck.payload to see the bytes.`
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function verifyTextInboundEcho(controlId, inbound) {
|
|
296
|
+
if (Buffer.isBuffer(inbound)) return null;
|
|
297
|
+
if (controlId === "") return null;
|
|
298
|
+
let hasNonAscii = false;
|
|
299
|
+
for (let i = 0; i < controlId.length; i++) {
|
|
300
|
+
if (controlId.charCodeAt(i) > 127) {
|
|
301
|
+
hasNonAscii = true;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (!hasNonAscii) return null;
|
|
306
|
+
return {
|
|
307
|
+
code: MLLP_ACK_CONTROL_ID_UNVERIFIABLE,
|
|
308
|
+
message: `ACK's byte-verbatim echo of MSH-10 (HL7 v2.5.1 \xA72.9.2.2) cannot be verified: the inbound was decoded text (a string or Hl7Message), not raw bytes, so its wire bytes are gone and the ${String(controlId.length)}-code-unit MSA-2 control ID holds a non-ASCII code unit the decode/encode codec could have altered (a lossy override such as ascii can even mask it into the ASCII byte range). It may not correlate at the sender (ACK timeout -> resend -> duplicate message). Pass the raw payload Buffer to get the byte-level guarantee. Field values are withheld, MSH-10 is inbound payload content and this warning goes to a log.`
|
|
279
309
|
};
|
|
280
310
|
}
|
|
281
311
|
function toMllpAck(params) {
|
|
@@ -285,7 +315,13 @@ function toMllpAck(params) {
|
|
|
285
315
|
allowDelimiterBytesInPayload: options.allowDelimiterBytesInPayload ?? false
|
|
286
316
|
});
|
|
287
317
|
const echoWarning = verifyVerbatimEcho(payload, params.verifyAgainst);
|
|
288
|
-
const
|
|
318
|
+
const echoedControlId = ack.segments("MSA")[0]?.field(2).text ?? "";
|
|
319
|
+
const unverifiableWarning = params.inbound === void 0 ? null : verifyTextInboundEcho(echoedControlId, params.inbound);
|
|
320
|
+
const allWarnings = [
|
|
321
|
+
...warnings,
|
|
322
|
+
...echoWarning === null ? [] : [echoWarning],
|
|
323
|
+
...unverifiableWarning === null ? [] : [unverifiableWarning]
|
|
324
|
+
];
|
|
289
325
|
const result = {
|
|
290
326
|
frame,
|
|
291
327
|
payload,
|
|
@@ -316,11 +352,12 @@ function buildUnparseableFallback(peer, requestedCode, fatalCode, options, encod
|
|
|
316
352
|
warnings,
|
|
317
353
|
options,
|
|
318
354
|
encoding,
|
|
319
|
-
// MSA-2 is INTENTIONALLY empty here
|
|
320
|
-
// control id to echo and we refuse to fabricate one. Running
|
|
321
|
-
// report that deliberate choice as a
|
|
355
|
+
// MSA-2 is INTENTIONALLY empty here, the inbound could not be parsed, so there is no
|
|
356
|
+
// control id to echo and we refuse to fabricate one. Running either echo check would
|
|
357
|
+
// report that deliberate choice as a problem; `MLLP_ACK_INBOUND_UNPARSEABLE` (above)
|
|
322
358
|
// is the accurate warning for this case, and it is already attached.
|
|
323
|
-
verifyAgainst: void 0
|
|
359
|
+
verifyAgainst: void 0,
|
|
360
|
+
inbound: void 0
|
|
324
361
|
});
|
|
325
362
|
}
|
|
326
363
|
function resolveInbound(peer, inbound, encoding) {
|
|
@@ -337,6 +374,7 @@ function buildMllpAck(inbound, options) {
|
|
|
337
374
|
const peer = loadHl7Peer();
|
|
338
375
|
assertKnownAckCode(peer, options.code);
|
|
339
376
|
const encoding = resolveEncoding(inbound, options);
|
|
377
|
+
assertSerializableAckEncoding(encoding);
|
|
340
378
|
let msg;
|
|
341
379
|
try {
|
|
342
380
|
msg = resolveInbound(peer, inbound, encoding);
|
|
@@ -368,7 +406,8 @@ function buildMllpAck(inbound, options) {
|
|
|
368
406
|
warnings,
|
|
369
407
|
options,
|
|
370
408
|
encoding,
|
|
371
|
-
verifyAgainst: inboundBytes(inbound, encoding)
|
|
409
|
+
verifyAgainst: inboundBytes(inbound, encoding),
|
|
410
|
+
inbound
|
|
372
411
|
});
|
|
373
412
|
}
|
|
374
413
|
function buildAckAA(inbound, options) {
|
|
@@ -395,6 +434,6 @@ function detectMode(inbound) {
|
|
|
395
434
|
return peer.detectAckMode(msg);
|
|
396
435
|
}
|
|
397
436
|
|
|
398
|
-
export { MLLP_ACK_CONTROL_ID_NOT_VERBATIM, MLLP_ACK_INBOUND_UNPARSEABLE, MllpPeerMissingError, buildAckAA, buildAckAE, buildAckAR, buildAckCA, buildAckCE, buildAckCR, buildMllpAck, detectMode, loadHl7Peer };
|
|
437
|
+
export { MLLP_ACK_CONTROL_ID_NOT_VERBATIM, MLLP_ACK_CONTROL_ID_UNVERIFIABLE, MLLP_ACK_INBOUND_UNPARSEABLE, MllpPeerMissingError, buildAckAA, buildAckAE, buildAckAR, buildAckCA, buildAckCE, buildAckCR, buildMllpAck, detectMode, loadHl7Peer };
|
|
399
438
|
//# sourceMappingURL=index.mjs.map
|
|
400
439
|
//# sourceMappingURL=index.mjs.map
|