@cosyte/mllp 0.0.1

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/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @cosyte/mllp
2
+
3
+ > Send and receive HL7 v2 over a production-grade MLLP connection in a few lines — with framing, ACK correlation, auto-reconnect, and backpressure handled for you.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@cosyte/mllp.svg)](https://www.npmjs.com/package/@cosyte/mllp)
6
+ [![CI](https://img.shields.io/github/actions/workflow/status/cosyte/mllp/ci.yml?branch=main&label=CI)](https://github.com/cosyte/mllp/actions/workflows/ci.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
+ [![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://nodejs.org)
9
+
10
+ A developer-focused MLLP (Minimal Lower Layer Protocol) client and server for Node.js and TypeScript. Transport-only sibling to [`@cosyte/hl7`](https://github.com/cosyte/hl7) (the parser): `@cosyte/mllp` moves the bytes, `@cosyte/hl7` reads them.
11
+
12
+ **Status: under active development. API not yet stable (`0.0.x`).**
13
+
14
+ ---
15
+
16
+ ## Quickstart
17
+
18
+ ```bash
19
+ # pnpm (recommended) — also works with: npm install @cosyte/mllp | yarn add @cosyte/mllp
20
+ pnpm add @cosyte/mllp
21
+ ```
22
+
23
+ ```ts
24
+ import { createStarterServer } from "@cosyte/mllp";
25
+
26
+ // Echoes every received frame straight back as the ACK.
27
+ const server = await createStarterServer({ port: 2575, onMessage: (buf) => buf });
28
+ ```
29
+
30
+ ```ts
31
+ import { createStarterClient } from "@cosyte/mllp";
32
+
33
+ const client = await createStarterClient({ host: "localhost", port: 2575 });
34
+ const ack = await client.send(Buffer.from("MSH|^~\\&|..."));
35
+ ```
36
+
37
+ The payload API is **Buffer-first** everywhere — HL7 v2 messages are raw bytes with caller-managed charset decoding.
38
+
39
+ ## MLLPS / TLS
40
+
41
+ TLS is built on `node:tls` — no bundled TLS, no extra dependency. Certificate verification is **on
42
+ by default**; the server binds `127.0.0.1` by default and requires an explicit opt-in
43
+ (`allowWildcardBind: true`) to bind all interfaces.
44
+
45
+ ```ts
46
+ // Server — plain TLS
47
+ const server = createServer({ tls: { cert: certPem, key: keyPem } });
48
+ await server.listen(2575, "127.0.0.1");
49
+
50
+ // Server — mutual TLS (ATNA ITI-19)
51
+ const server = createServer({
52
+ tls: { cert: certPem, key: keyPem, ca: clientCaPem, clientAuth: "MUST" },
53
+ });
54
+ ```
55
+
56
+ ```ts
57
+ // Client
58
+ const client = createClient({ host: "mllp.example.com", port: 2575, tls: { ca: caPem } });
59
+
60
+ // Client — mutual TLS
61
+ const client = createClient({
62
+ host: "mllp.example.com",
63
+ port: 2575,
64
+ tls: { ca: caPem, cert: clientCertPem, key: clientKeyPem },
65
+ });
66
+ ```
67
+
68
+ See the **MLLPS / TLS** doc for the `ClientAuth` table, the TLS 1.2 floor (IHE ATNA ITI-19), typed
69
+ failure modes (`tls-verify` vs `tls-handshake`), and bind-safety details.
70
+
71
+ ## Fail-safe ACKs — the commit contract
72
+
73
+ A positive acknowledgement (`AA`) tells the sender *"you may forget this message — I have it."* So a
74
+ receiver must never send one before the message is durably handled, or the message is silently lost.
75
+ `@cosyte/mllp` makes that structural: pair `autoAck: 'AA'` with an `onMessage` handler and the server
76
+ **awaits your handler (the durable-commit step) and only then ACKs**.
77
+
78
+ ```ts
79
+ const server = createServer({
80
+ autoAck: "AA",
81
+ onMessage: async (payload) => {
82
+ await db.commit(payload); // throw here ⇒ AE (resend may succeed), never AA
83
+ },
84
+ });
85
+ ```
86
+
87
+ Handler resolves ⇒ `AA`. Handler throws ⇒ `AE` (or `AR` via `MllpAckError`). **A positive ACK cannot
88
+ precede a successful commit.** `autoAck: 'AA'` *without* a handler is documented as a
89
+ transport-accept — "received and framed", not "processed".
90
+
91
+ ## What's in the box
92
+
93
+ - **Client + server** with strict MLLP framing (`VT + payload + FS + CR`), ACK correlation, auto-reconnect with backoff, and backpressure.
94
+ - **The commit contract** — a positive ACK can never precede a durable commit; an unparseable inbound can never yield a positive ACK.
95
+ - **Explicit 6-state connection machine** (`CONNECTING | CONNECTED | DRAINING | RECONNECTING | DISCONNECTED | CLOSED`) with `stateChange` events — never socket flags.
96
+ - **Lenient decoder, strict encoder** (Postel's Law) with **11 stable warning codes** carrying byte offsets. Tolerance is opt-in per flag; the server ships tolerant defaults.
97
+ - **TLS (MLLPS)** — verification on by default, mutual TLS (`clientAuth: 'NONE' | 'WANT' | 'MUST'`), a TLS 1.2 floor per IHE ATNA ITI-19, and bind-safety guardrails (`127.0.0.1` default, wildcard binds require opt-in). `AbortSignal` on every awaitable and `Symbol.asyncDispose` on every closeable.
98
+ - **PHI-safe diagnostics** — no error, warning, event payload, or stats object ever echoes a *run* of message content; a framing error carries at most the single byte at the structural violation.
99
+ - **In-memory transport** (`@cosyte/mllp/testing`) — a deterministic, socket-free test double for fast, reliable tests.
100
+ - **`@cosyte/hl7` as an optional peer** — the `@cosyte/mllp/ack-from-hl7` subpath builds ACKs from parsed messages when it's installed.
101
+ - **Zero runtime dependencies.** Node stdlib only.
102
+
103
+ ## What it deliberately does not do
104
+
105
+ MLLP + ACK is **at-least-once at best** — your application owns idempotency and de-duplication
106
+ (`MSH-10` + `MSH-7`). This package does not parse HL7 (use `@cosyte/hl7`), does not queue or replay
107
+ unacked messages, does not decide clinical acceptance, does not speak MLLP **Release 2**, and ships
108
+ no PKI. The full list is in the **Known limitations & non-goals** doc — read it before you depend on
109
+ this.
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,417 @@
1
+ 'use strict';
2
+
3
+ var module$1 = require('module');
4
+
5
+ // node_modules/.pnpm/tsup@8.5.1_postcss@8.5.10_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
6
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
7
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
8
+
9
+ // src/framing/constants.ts
10
+ var VT = 11;
11
+ var FS = 28;
12
+ var CR = 13;
13
+
14
+ // src/framing/error.ts
15
+ var MAX_SNIPPET_BYTES = 64;
16
+ var MllpFramingError = class _MllpFramingError extends Error {
17
+ name = "MllpFramingError";
18
+ /** Stable warning code identifying the violation type. */
19
+ code;
20
+ /** Absolute byte offset in the stream where the error was detected. */
21
+ byteOffset;
22
+ /**
23
+ * Up to 64 bytes copied from around the anomaly.
24
+ *
25
+ * This is a **copied** Buffer — isolated from the source buffer so it remains
26
+ * valid after the underlying buffer is reused or overwritten.
27
+ *
28
+ * **PHI contract (MLLP-9):** the decoder only ever populates this with the single
29
+ * framing-boundary byte that violated the frame structure (whose hex value the
30
+ * `message` already discloses) — never a run of payload content bytes. For anomalies
31
+ * whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE` — the accumulated size),
32
+ * the snippet is **empty**: a payload slice on a public error field would leak a
33
+ * field-body slice of clinical content. Callers constructing this directly are
34
+ * responsible for the same discipline.
35
+ */
36
+ snippet;
37
+ /**
38
+ * Construct an MLLP framing error.
39
+ *
40
+ * @param code - The stable warning code classifying the violation.
41
+ * @param byteOffset - Absolute stream byte offset where the violation was detected.
42
+ * @param snippet - Raw bytes copied from around the anomaly (capped to 64 bytes).
43
+ * @param message - Optional human-readable override message.
44
+ */
45
+ constructor(code, byteOffset, snippet, message) {
46
+ super(message ?? `MLLP framing error: ${code} at byte offset ${byteOffset}`);
47
+ this.code = code;
48
+ this.byteOffset = byteOffset;
49
+ this.snippet = Buffer.from(snippet.subarray(0, MAX_SNIPPET_BYTES));
50
+ if (Error.captureStackTrace) {
51
+ Error.captureStackTrace(this, _MllpFramingError);
52
+ }
53
+ }
54
+ };
55
+
56
+ // src/framing/registry.ts
57
+ function createWarning(code, byteOffset, message) {
58
+ return Object.freeze({
59
+ code,
60
+ message,
61
+ byteOffset,
62
+ connectionId: void 0,
63
+ timestamp: /* @__PURE__ */ new Date()
64
+ });
65
+ }
66
+
67
+ // src/framing/encoder.ts
68
+ function encodeFrame(payload, opts) {
69
+ const allow = opts?.allowDelimiterBytesInPayload === true;
70
+ const onWarning = opts?.onWarning;
71
+ for (let i = 0; i < payload.length; i++) {
72
+ const byte = payload[i];
73
+ if (byte === void 0) break;
74
+ if (byte === VT || byte === FS) {
75
+ const code = byte === VT ? "MLLP_PAYLOAD_CONTAINS_VT" : "MLLP_PAYLOAD_CONTAINS_FS";
76
+ if (!allow) {
77
+ throw new MllpFramingError(code, i, Buffer.from([byte]));
78
+ }
79
+ if (onWarning !== void 0) {
80
+ const byteHex = byte === VT ? "0x0B" : "0x1C";
81
+ const warning = createWarning(
82
+ code,
83
+ i,
84
+ `Payload contains ${byte === VT ? "VT" : "FS"} byte (${byteHex}) at offset ${i}; frame will be ambiguous`
85
+ );
86
+ try {
87
+ onWarning(warning);
88
+ } catch {
89
+ }
90
+ }
91
+ }
92
+ }
93
+ const frame = Buffer.allocUnsafe(payload.length + 3);
94
+ frame[0] = VT;
95
+ payload.copy(frame, 1);
96
+ frame[payload.length + 1] = FS;
97
+ frame[payload.length + 2] = CR;
98
+ return frame;
99
+ }
100
+
101
+ // src/internal/control-id.ts
102
+ var ASCII_M = 77;
103
+ var ASCII_S = 83;
104
+ var ASCII_H = 72;
105
+ var ASCII_A = 65;
106
+ var SEGMENT_SEPARATOR_CR = 13;
107
+ var SEGMENT_SEPARATOR_LF = 10;
108
+ var CONTROL_ID_ENCODING = "latin1";
109
+ var UNSAFE_FIELD_SEPARATORS = /* @__PURE__ */ new Set([
110
+ SEGMENT_SEPARATOR_CR,
111
+ SEGMENT_SEPARATOR_LF,
112
+ 11,
113
+ // VT — MLLP start block
114
+ 28
115
+ // FS — MLLP end block
116
+ ]);
117
+ function isMshSegmentAt(buf, i) {
118
+ 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]);
119
+ }
120
+ function segmentEnd(buf, i) {
121
+ let end = i;
122
+ while (end < buf.length && buf[end] !== SEGMENT_SEPARATOR_CR && buf[end] !== SEGMENT_SEPARATOR_LF) {
123
+ end++;
124
+ }
125
+ return end;
126
+ }
127
+ function findMshSegment(buf) {
128
+ let segStart = 0;
129
+ while (segStart < buf.length) {
130
+ const end = segmentEnd(buf, segStart);
131
+ if (isMshSegmentAt(buf, segStart)) return { start: segStart, end };
132
+ let next = end;
133
+ while (next < buf.length && (buf[next] === SEGMENT_SEPARATOR_CR || buf[next] === SEGMENT_SEPARATOR_LF)) {
134
+ next++;
135
+ }
136
+ if (next === segStart) return null;
137
+ segStart = next;
138
+ }
139
+ return null;
140
+ }
141
+ function readMshSegment(buf) {
142
+ const at = findMshSegment(buf);
143
+ if (at === null) return null;
144
+ const fieldSep = String.fromCharCode(buf[at.start + 3]);
145
+ const fields = buf.subarray(at.start, at.end).toString(CONTROL_ID_ENCODING).split(fieldSep);
146
+ return { fieldSep, fields };
147
+ }
148
+ function stripLeadingSegmentTerminators(buf) {
149
+ let i = 0;
150
+ while (i < buf.length && (buf[i] === SEGMENT_SEPARATOR_CR || buf[i] === SEGMENT_SEPARATOR_LF)) {
151
+ i++;
152
+ }
153
+ return i === 0 ? buf : buf.subarray(i);
154
+ }
155
+ function extractMshControlId(buf) {
156
+ const msh = readMshSegment(buf);
157
+ if (msh === null) return null;
158
+ const id = msh.fields[9];
159
+ return id === void 0 || id === "" ? null : id;
160
+ }
161
+ function extractMsaControlId(buf) {
162
+ const msh = readMshSegment(buf);
163
+ if (msh === null) return null;
164
+ const fieldSep = msh.fieldSep.charCodeAt(0);
165
+ const end = buf.length;
166
+ let segStart = 0;
167
+ while (segStart < end) {
168
+ let segEnd = segStart;
169
+ while (segEnd < end && buf[segEnd] !== SEGMENT_SEPARATOR_CR && buf[segEnd] !== SEGMENT_SEPARATOR_LF) {
170
+ segEnd++;
171
+ }
172
+ if (segEnd - segStart >= 4 && buf[segStart] === ASCII_M && buf[segStart + 1] === ASCII_S && buf[segStart + 2] === ASCII_A && buf[segStart + 3] === fieldSep) {
173
+ let fieldIndex = 0;
174
+ let fieldStart = 0;
175
+ for (let i = segStart + 3; i <= segEnd; i++) {
176
+ const b = i < segEnd ? buf[i] : fieldSep;
177
+ if (b === fieldSep || b === SEGMENT_SEPARATOR_CR || b === SEGMENT_SEPARATOR_LF) {
178
+ fieldIndex++;
179
+ if (fieldIndex === 2) {
180
+ fieldStart = i + 1;
181
+ } else if (fieldIndex === 3) {
182
+ if (fieldStart >= i) return null;
183
+ return buf.subarray(fieldStart, i).toString(CONTROL_ID_ENCODING);
184
+ }
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+ while (segEnd < end && (buf[segEnd] === SEGMENT_SEPARATOR_CR || buf[segEnd] === SEGMENT_SEPARATOR_LF)) {
190
+ segEnd++;
191
+ }
192
+ if (segEnd === segStart) return null;
193
+ segStart = segEnd;
194
+ }
195
+ return null;
196
+ }
197
+ var SUBPATH_NAME = "@cosyte/mllp/ack-from-hl7";
198
+ var PEER_PACKAGE_NAME = "@cosyte/hl7";
199
+ var MODULE_NOT_FOUND_CODES = /* @__PURE__ */ new Set([
200
+ "ERR_MODULE_NOT_FOUND",
201
+ "MODULE_NOT_FOUND"
202
+ ]);
203
+ var MllpPeerMissingError = class _MllpPeerMissingError extends Error {
204
+ name = "MllpPeerMissingError";
205
+ /** Stable machine-readable code identifying this error class. */
206
+ code = "MLLP_PEER_MISSING";
207
+ /**
208
+ * Construct an `MllpPeerMissingError`.
209
+ *
210
+ * @param cause - The original module-resolution error, preserved for debugging.
211
+ */
212
+ constructor(cause) {
213
+ super(
214
+ `${PEER_PACKAGE_NAME} is required by ${SUBPATH_NAME} but is not installed. ${PEER_PACKAGE_NAME} is an optional peer dependency of @cosyte/mllp \u2014 install it alongside @cosyte/mllp to use ${SUBPATH_NAME}: \`npm install ${PEER_PACKAGE_NAME}\` (or the equivalent for your package manager).`,
215
+ { cause }
216
+ );
217
+ if (Error.captureStackTrace) {
218
+ Error.captureStackTrace(this, _MllpPeerMissingError);
219
+ }
220
+ }
221
+ };
222
+ var cachedPeer;
223
+ var PEER_UNRESOLVABLE_RE = /Cannot find (?:module|package) '@cosyte\/hl7'/;
224
+ function isPeerModuleNotFound(err) {
225
+ if (typeof err !== "object" || err === null) return false;
226
+ const code = err.code;
227
+ if (typeof code !== "string" || !MODULE_NOT_FOUND_CODES.has(code)) return false;
228
+ const message = err.message;
229
+ return typeof message === "string" && PEER_UNRESOLVABLE_RE.test(message);
230
+ }
231
+ function loadHl7Peer(requireFn) {
232
+ if (cachedPeer !== void 0 && requireFn === void 0) {
233
+ return cachedPeer;
234
+ }
235
+ const doRequire = requireFn ?? module$1.createRequire(importMetaUrl);
236
+ let mod;
237
+ try {
238
+ mod = doRequire(PEER_PACKAGE_NAME);
239
+ } catch (err) {
240
+ if (isPeerModuleNotFound(err)) {
241
+ throw new MllpPeerMissingError(err);
242
+ }
243
+ throw err;
244
+ }
245
+ const peer = mod;
246
+ if (requireFn === void 0) {
247
+ cachedPeer = peer;
248
+ }
249
+ return peer;
250
+ }
251
+
252
+ // src/ack-from-hl7/build.ts
253
+ var MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
254
+ var MLLP_ACK_CONTROL_ID_NOT_VERBATIM = "MLLP_ACK_CONTROL_ID_NOT_VERBATIM";
255
+ var BYTES_INBOUND_ENCODING = "latin1";
256
+ var TEXT_INBOUND_ENCODING = "utf8";
257
+ function assertKnownAckCode(peer, code) {
258
+ const known = Object.values(peer.ACK_CODES);
259
+ if (typeof code !== "string" || !known.includes(code)) {
260
+ throw new TypeError(
261
+ `buildMllpAck: \`code\` must be a known HL7 Table 0008 acknowledgment code (AA/AE/AR/CA/CE/CR). Received: ${JSON.stringify(code)}.`
262
+ );
263
+ }
264
+ }
265
+ function resolveEncoding(inbound, options) {
266
+ if (options.encoding !== void 0) return options.encoding;
267
+ return Buffer.isBuffer(inbound) ? BYTES_INBOUND_ENCODING : TEXT_INBOUND_ENCODING;
268
+ }
269
+ function inboundBytes(inbound, encoding) {
270
+ if (Buffer.isBuffer(inbound)) return inbound;
271
+ if (typeof inbound === "string") return Buffer.from(inbound, encoding);
272
+ return void 0;
273
+ }
274
+ function verifyVerbatimEcho(ackPayload, inboundRaw) {
275
+ if (inboundRaw === void 0) return null;
276
+ const inboundId = extractMshControlId(inboundRaw);
277
+ if (inboundId === null) return null;
278
+ const echoedId = extractMsaControlId(ackPayload);
279
+ if (echoedId === inboundId) return null;
280
+ const inboundLen = String(inboundId.length);
281
+ const echoed = echoedId === null ? "absent" : `${String(echoedId.length)} bytes`;
282
+ return {
283
+ code: MLLP_ACK_CONTROL_ID_NOT_VERBATIM,
284
+ 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 \u2014 MSH-10 is inbound payload content and this warning goes to a log; compare your inbound payload against MllpAck.payload to see the bytes.`
285
+ };
286
+ }
287
+ function toMllpAck(params) {
288
+ const { ack, requestedCode, code, correlationId, mode, warnings, options, encoding } = params;
289
+ const payload = Buffer.from(ack.toString(), encoding);
290
+ const frame = encodeFrame(payload, {
291
+ allowDelimiterBytesInPayload: options.allowDelimiterBytesInPayload ?? false
292
+ });
293
+ const echoWarning = verifyVerbatimEcho(payload, params.verifyAgainst);
294
+ const allWarnings = echoWarning === null ? warnings : [...warnings, echoWarning];
295
+ const result = {
296
+ frame,
297
+ payload,
298
+ ack,
299
+ requestedCode,
300
+ code,
301
+ correlationId,
302
+ mode,
303
+ warnings: Object.freeze([...allWarnings])
304
+ };
305
+ return Object.freeze(result);
306
+ }
307
+ function buildUnparseableFallback(peer, requestedCode, fatalCode, options, encoding) {
308
+ const code = peer.downgradePositiveAck(requestedCode);
309
+ const ack = peer.buildMessage({ type: "ACK" }).addSegment("MSA", [code, ""]);
310
+ const warnings = [
311
+ {
312
+ code: MLLP_ACK_INBOUND_UNPARSEABLE,
313
+ message: `Inbound message unparseable (${fatalCode}); emitted ${code} with no correlation id.`
314
+ }
315
+ ];
316
+ return toMllpAck({
317
+ ack,
318
+ requestedCode,
319
+ code,
320
+ correlationId: void 0,
321
+ mode: void 0,
322
+ warnings,
323
+ options,
324
+ encoding,
325
+ // MSA-2 is INTENTIONALLY empty here — the inbound could not be parsed, so there is no
326
+ // control id to echo and we refuse to fabricate one. Running the verbatim check would
327
+ // report that deliberate choice as a violation; `MLLP_ACK_INBOUND_UNPARSEABLE` (above)
328
+ // is the accurate warning for this case, and it is already attached.
329
+ verifyAgainst: void 0
330
+ });
331
+ }
332
+ function resolveInbound(peer, inbound, encoding) {
333
+ if (Buffer.isBuffer(inbound)) {
334
+ return peer.parseHL7(stripLeadingSegmentTerminators(inbound).toString(encoding));
335
+ }
336
+ if (typeof inbound === "string") {
337
+ return peer.parseHL7(inbound.replace(/^[\r\n]+/, ""));
338
+ }
339
+ const isPeerNative = inbound instanceof peer.Hl7Message;
340
+ return isPeerNative ? inbound : peer.parseHL7(inbound.toString());
341
+ }
342
+ function buildMllpAck(inbound, options) {
343
+ const peer = loadHl7Peer();
344
+ assertKnownAckCode(peer, options.code);
345
+ const encoding = resolveEncoding(inbound, options);
346
+ let msg;
347
+ try {
348
+ msg = resolveInbound(peer, inbound, encoding);
349
+ } catch (err) {
350
+ if (err instanceof peer.Hl7ParseError) {
351
+ return buildUnparseableFallback(peer, options.code, err.code, options, encoding);
352
+ }
353
+ throw err;
354
+ }
355
+ const mode = peer.detectAckMode(msg);
356
+ const ack = peer.buildAck(
357
+ msg,
358
+ options.error === void 0 ? { code: options.code, mode } : { code: options.code, error: options.error, mode }
359
+ );
360
+ const emittedCode = ack.get("MSA.1");
361
+ assertKnownAckCode(peer, emittedCode);
362
+ const msa2 = ack.segments("MSA")[0]?.field(2).text ?? "";
363
+ const correlationId = msa2 !== "" ? msa2 : void 0;
364
+ const warnings = ack.warnings.map((w) => ({
365
+ code: w.code,
366
+ message: w.message
367
+ }));
368
+ return toMllpAck({
369
+ ack,
370
+ requestedCode: options.code,
371
+ code: emittedCode,
372
+ correlationId,
373
+ mode,
374
+ warnings,
375
+ options,
376
+ encoding,
377
+ verifyAgainst: inboundBytes(inbound, encoding)
378
+ });
379
+ }
380
+ function buildAckAA(inbound, options) {
381
+ return buildMllpAck(inbound, { ...options, code: "AA" });
382
+ }
383
+ function buildAckAE(inbound, options) {
384
+ return buildMllpAck(inbound, { ...options, code: "AE" });
385
+ }
386
+ function buildAckAR(inbound, options) {
387
+ return buildMllpAck(inbound, { ...options, code: "AR" });
388
+ }
389
+ function buildAckCA(inbound, options) {
390
+ return buildMllpAck(inbound, { ...options, code: "CA" });
391
+ }
392
+ function buildAckCE(inbound, options) {
393
+ return buildMllpAck(inbound, { ...options, code: "CE" });
394
+ }
395
+ function buildAckCR(inbound, options) {
396
+ return buildMllpAck(inbound, { ...options, code: "CR" });
397
+ }
398
+ function detectMode(inbound) {
399
+ const peer = loadHl7Peer();
400
+ const msg = resolveInbound(peer, inbound, BYTES_INBOUND_ENCODING);
401
+ return peer.detectAckMode(msg);
402
+ }
403
+
404
+ exports.MLLP_ACK_CONTROL_ID_NOT_VERBATIM = MLLP_ACK_CONTROL_ID_NOT_VERBATIM;
405
+ exports.MLLP_ACK_INBOUND_UNPARSEABLE = MLLP_ACK_INBOUND_UNPARSEABLE;
406
+ exports.MllpPeerMissingError = MllpPeerMissingError;
407
+ exports.buildAckAA = buildAckAA;
408
+ exports.buildAckAE = buildAckAE;
409
+ exports.buildAckAR = buildAckAR;
410
+ exports.buildAckCA = buildAckCA;
411
+ exports.buildAckCE = buildAckCE;
412
+ exports.buildAckCR = buildAckCR;
413
+ exports.buildMllpAck = buildMllpAck;
414
+ exports.detectMode = detectMode;
415
+ exports.loadHl7Peer = loadHl7Peer;
416
+ //# sourceMappingURL=index.cjs.map
417
+ //# sourceMappingURL=index.cjs.map