@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/CHANGELOG.md +529 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/ack-from-hl7/index.cjs +417 -0
- package/dist/ack-from-hl7/index.cjs.map +1 -0
- package/dist/ack-from-hl7/index.d.cts +425 -0
- package/dist/ack-from-hl7/index.d.ts +425 -0
- package/dist/ack-from-hl7/index.mjs +400 -0
- package/dist/ack-from-hl7/index.mjs.map +1 -0
- package/dist/index-DjGQJtYE.d.cts +83 -0
- package/dist/index-DjGQJtYE.d.ts +83 -0
- package/dist/index.cjs +3455 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3112 -0
- package/dist/index.d.ts +3112 -0
- package/dist/index.mjs +3428 -0
- package/dist/index.mjs.map +1 -0
- package/dist/testing/index.cjs +243 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +174 -0
- package/dist/testing/index.d.ts +174 -0
- package/dist/testing/index.mjs +241 -0
- package/dist/testing/index.mjs.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.10_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","../../src/framing/constants.ts","../../src/framing/error.ts","../../src/framing/registry.ts","../../src/framing/encoder.ts","../../src/internal/control-id.ts","../../src/ack-from-hl7/peer.ts","../../src/ack-from-hl7/build.ts"],"names":["createRequire"],"mappings":"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","file":"index.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * MLLP framing byte constants.\n *\n * All byte values match the MLLP v1 wire protocol as specified in HL7 appendix C.\n *\n * @example\n * ```typescript\n * import { VT, FS, CR } from '@cosyte/mllp';\n * // VT = 0x0B, FS = 0x1C, CR = 0x0D\n * ```\n *\n * @packageDocumentation\n */\n\n/** Vertical Tab (0x0B) — marks the start of an MLLP frame. */\nexport const VT = 0x0b;\n\n/** File Separator (0x1C) — marks the end of MLLP payload bytes. */\nexport const FS = 0x1c;\n\n/** Carriage Return (0x0D) — terminates the FS byte; together FS+CR ends the frame. */\nexport const CR = 0x0d;\n\n/** Line Feed (0x0A) — used by the MLLP_LF_AFTER_FS tolerance (FRAME-08). */\nexport const LF = 0x0a;\n\n/**\n * Default maximum accumulated payload size in bytes (16 MiB).\n *\n * Exceeding this limit throws `MllpFramingError('MLLP_FRAME_TOO_LARGE')`.\n * Satisfies FRAME-11 DoS prevention requirement.\n */\nexport const DEFAULT_MAX_FRAME_SIZE = 16 * 1024 * 1024;\n","/**\n * Typed framing error class for unrecoverable MLLP wire-format violations.\n *\n * @example\n * ```typescript\n * import { MllpFramingError } from '@cosyte/mllp';\n * try {\n * encodeFrame(payloadWithVt);\n * } catch (err) {\n * if (err instanceof MllpFramingError) {\n * logger.error({ code: err.code, offset: err.byteOffset });\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { WarningCode } from \"./registry.js\";\n\n/** Maximum number of bytes captured in the `snippet` field. */\nconst MAX_SNIPPET_BYTES = 64;\n\n/**\n * Thrown for unrecoverable MLLP wire-format violations.\n *\n * - `code` — stable `WarningCode` identifying the violation\n * - `byteOffset` — absolute stream position where the violation was detected\n * - `snippet` — the single framing-boundary byte that broke the structure, or empty when the\n * anomaly is not a specific byte. **Never a run of payload content** (see the `snippet` PHI\n * contract below); the constructor caps whatever it is given to 64 bytes as a backstop.\n *\n * @example\n * ```typescript\n * // Pass only the offending framing-boundary byte — never a slice of payload content.\n * throw new MllpFramingError('MLLP_MISSING_LEADING_VT', byteOffset, Buffer.from([byte]));\n * ```\n */\nexport class MllpFramingError extends Error {\n override readonly name = \"MllpFramingError\" as const;\n\n /** Stable warning code identifying the violation type. */\n readonly code: WarningCode;\n\n /** Absolute byte offset in the stream where the error was detected. */\n readonly byteOffset: number;\n\n /**\n * Up to 64 bytes copied from around the anomaly.\n *\n * This is a **copied** Buffer — isolated from the source buffer so it remains\n * valid after the underlying buffer is reused or overwritten.\n *\n * **PHI contract (MLLP-9):** the decoder only ever populates this with the single\n * framing-boundary byte that violated the frame structure (whose hex value the\n * `message` already discloses) — never a run of payload content bytes. For anomalies\n * whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE` — the accumulated size),\n * the snippet is **empty**: a payload slice on a public error field would leak a\n * field-body slice of clinical content. Callers constructing this directly are\n * responsible for the same discipline.\n */\n readonly snippet: Buffer;\n\n /**\n * Construct an MLLP framing error.\n *\n * @param code - The stable warning code classifying the violation.\n * @param byteOffset - Absolute stream byte offset where the violation was detected.\n * @param snippet - Raw bytes copied from around the anomaly (capped to 64 bytes).\n * @param message - Optional human-readable override message.\n */\n constructor(\n code: WarningCode,\n byteOffset: number,\n /** Raw bytes around the anomaly. Capped to MAX_SNIPPET_BYTES (64). */\n snippet: Buffer,\n message?: string,\n ) {\n super(message ?? `MLLP framing error: ${code} at byte offset ${byteOffset}`);\n this.code = code;\n this.byteOffset = byteOffset;\n // Copy and cap — error must outlive source buffer; never store a view.\n this.snippet = Buffer.from(snippet.subarray(0, MAX_SNIPPET_BYTES));\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MllpFramingError);\n }\n }\n}\n","/**\n * MLLP warning code registry and `MllpWarning` factory.\n *\n * `WarningCode` is a **stable public API**. Renaming or removing a code is a breaking change.\n * New codes may only be added in minor versions.\n *\n * @example\n * ```typescript\n * import { createWarning, type MllpWarning, type WarningCode } from '@cosyte/mllp';\n * const w: MllpWarning = createWarning('MLLP_EMPTY_PAYLOAD', 128, 'Empty payload between VT and FS');\n * ```\n *\n * @packageDocumentation\n */\n\n/**\n * Union of all stable MLLP warning codes.\n *\n * These codes are a **public API** — they appear in `onWarning` handlers, log pipelines,\n * monitoring dashboards, and error messages. Renaming is a breaking change.\n *\n * @example\n * ```typescript\n * const code: WarningCode = 'MLLP_FS_WITHOUT_CR';\n * ```\n */\nexport type WarningCode =\n | \"MLLP_MISSING_LEADING_VT\"\n | \"MLLP_FS_WITHOUT_CR\"\n | \"MLLP_LF_AFTER_FS\"\n | \"MLLP_LEADING_WHITESPACE\"\n | \"MLLP_TRAILING_BYTES\"\n | \"MLLP_PAYLOAD_CONTAINS_VT\"\n | \"MLLP_PAYLOAD_CONTAINS_FS\"\n | \"MLLP_EMPTY_PAYLOAD\"\n | \"MLLP_FRAME_TOO_LARGE\"\n | \"MLLP_ACK_UNMATCHED_CONTROL_ID\"\n | \"MLLP_ACK_AFTER_TIMEOUT\";\n\n/**\n * A frozen warning object emitted when the decoder tolerates a framing deviation.\n *\n * `connectionId` is `undefined` when emitted by a standalone `FrameReader` (Phase 2).\n * Phase 3 `Connection` enriches it to the real UUIDv4 before forwarding upstream.\n *\n * @example\n * ```typescript\n * const reader = new FrameReader({\n * onFrame: (p) => process(p),\n * onWarning: (w: MllpWarning) => logger.warn(w),\n * allowFsOnly: true,\n * });\n * ```\n */\nexport interface MllpWarning {\n readonly code: WarningCode;\n /** Stable human-readable description. Never contains payload bytes or secrets. */\n readonly message: string;\n /** Absolute stream byte offset where the anomaly was detected. */\n readonly byteOffset: number;\n /**\n * Connection identifier. `undefined` at the framing layer;\n * enriched by Phase 3 `Connection` before emitting upstream.\n */\n readonly connectionId: string | undefined;\n /** Wall-clock time at point of emission. */\n readonly timestamp: Date;\n}\n\n/**\n * Callback type for receiving MLLP framing warnings.\n *\n * @example\n * ```typescript\n * const onWarning: OnWarning = (w) => logger.warn({ code: w.code, offset: w.byteOffset });\n * ```\n */\nexport type OnWarning = (warning: MllpWarning) => void;\n\n/**\n * Create a frozen `MllpWarning` object. The returned object is `Object.freeze()`'d\n * so subscribers cannot mutate shared warning state.\n *\n * `connectionId` is always `undefined` here — Phase 3 enriches via\n * `{ ...w, connectionId: this.connectionId }` then re-freezes (D-08).\n *\n * @example\n * ```typescript\n * const w = createWarning('MLLP_EMPTY_PAYLOAD', 64, 'Empty payload between VT and FS');\n * // w.connectionId === undefined\n * ```\n */\nexport function createWarning(code: WarningCode, byteOffset: number, message: string): MllpWarning {\n return Object.freeze<MllpWarning>({\n code,\n message,\n byteOffset,\n connectionId: undefined,\n timestamp: new Date(),\n });\n}\n","/**\n * MLLP frame encoder — canonical `VT + payload + FS + CR` wrapping.\n *\n * The encoder is a **strict emitter** (Postel's Law: conservative on write).\n * There is no option to loosen the output format — it always emits canonical framing.\n * Tolerance for delimiter bytes in the payload is available via `allowDelimiterBytesInPayload`,\n * which passes bytes through verbatim and emits an `MllpWarning` for each offending byte.\n *\n * @example\n * ```typescript\n * import { encodeFrame } from '@cosyte/mllp';\n * const frame = encodeFrame(Buffer.from('MSH|^~\\\\&|...'));\n * // frame[0] === 0x0B (VT), frame[frame.length - 2] === 0x1C (FS), frame[frame.length - 1] === 0x0D (CR)\n * ```\n *\n * @packageDocumentation\n */\n\nimport { VT, FS, CR } from \"./constants.js\";\nimport { MllpFramingError } from \"./error.js\";\nimport { createWarning } from \"./registry.js\";\nimport type { MllpWarning } from \"./registry.js\";\n\n/**\n * Options for {@link encodeFrame}.\n *\n * @example\n * ```typescript\n * const frame = encodeFrame(payload, {\n * allowDelimiterBytesInPayload: true,\n * onWarning: (w) => logger.warn(w),\n * });\n * ```\n */\nexport interface EncoderOptions {\n /**\n * When `true`, VT (0x0B) or FS (0x1C) bytes within the payload are preserved\n * verbatim in the output frame instead of throwing `MllpFramingError`.\n * An `MllpWarning` is emitted per offending byte if `onWarning` is provided.\n *\n * Default: `false` (strict — throws on delimiter bytes).\n */\n allowDelimiterBytesInPayload?: boolean;\n\n /**\n * Called for each offending delimiter byte when `allowDelimiterBytesInPayload` is `true`.\n * Invocation is wrapped in try/catch — a throwing handler does not interrupt encoding (WARN-06).\n *\n * @example\n * ```typescript\n * const frame = encodeFrame(payload, {\n * allowDelimiterBytesInPayload: true,\n * onWarning: (w) => logger.warn({ code: w.code, offset: w.byteOffset }),\n * });\n * ```\n */\n onWarning?: (w: MllpWarning) => void;\n}\n\n/**\n * Encode `payload` in canonical MLLP framing: `VT (0x0B) + payload + FS (0x1C) + CR (0x0D)`.\n *\n * Strict by default: throws `MllpFramingError` if the payload contains VT or FS bytes.\n * Set `{ allowDelimiterBytesInPayload: true }` to pass those bytes through verbatim\n * and receive an `MllpWarning` per offending byte via `onWarning` instead.\n *\n * The returned Buffer has length `payload.length + 3`. Bytes at positions 1 through\n * `payload.length` are an exact copy of the payload (independent of the input buffer).\n *\n * @throws {MllpFramingError} with code `MLLP_PAYLOAD_CONTAINS_VT` when payload contains\n * byte `0x0B` and `allowDelimiterBytesInPayload` is `false` (default).\n * @throws {MllpFramingError} with code `MLLP_PAYLOAD_CONTAINS_FS` when payload contains\n * byte `0x1C` and `allowDelimiterBytesInPayload` is `false` (default).\n *\n * @example\n * ```typescript\n * import { encodeFrame } from '@cosyte/mllp';\n *\n * // Strict (default) — throws on delimiter bytes in payload\n * const frame = encodeFrame(Buffer.from('MSH|^~\\\\&|SEND|FAC|RECV|FAC|...'));\n * socket.write(frame);\n *\n * // Tolerant — passes delimiter bytes through with a warning\n * const frame2 = encodeFrame(dirtyPayload, {\n * allowDelimiterBytesInPayload: true,\n * onWarning: (w) => logger.warn(w),\n * });\n * ```\n */\nexport function encodeFrame(payload: Buffer, opts?: EncoderOptions): Buffer {\n const allow = opts?.allowDelimiterBytesInPayload === true;\n const onWarning = opts?.onWarning;\n\n // Scan for delimiter bytes — O(n) but required to ensure correct framing (FRAME-03).\n for (let i = 0; i < payload.length; i++) {\n const byte = payload[i];\n if (byte === undefined) break; // noUncheckedIndexedAccess guard\n\n if (byte === VT || byte === FS) {\n const code = byte === VT ? \"MLLP_PAYLOAD_CONTAINS_VT\" : \"MLLP_PAYLOAD_CONTAINS_FS\";\n\n if (!allow) {\n // PHI: the snippet must NOT carry the surrounding payload bytes — that is a\n // field-body slice of clinical content on a public error field (MLLP-9 PHI\n // audit, mirroring the decoder's MLLP_FRAME_TOO_LARGE fix). The offending byte\n // is itself a framing delimiter (VT/FS, a control byte, never PHI); the `code`\n // already names which and the offset is on the error, so the snippet is just\n // that one boundary byte.\n throw new MllpFramingError(code, i, Buffer.from([byte]));\n }\n\n // Tolerant path: emit warning and continue — bytes pass through verbatim.\n if (onWarning !== undefined) {\n const byteHex = byte === VT ? \"0x0B\" : \"0x1C\";\n const warning = createWarning(\n code,\n i,\n `Payload contains ${byte === VT ? \"VT\" : \"FS\"} byte (${byteHex}) at offset ${i}; frame will be ambiguous`,\n );\n try {\n onWarning(warning);\n } catch {\n // WARN-06: a throwing handler must not interrupt encoding.\n }\n }\n }\n }\n\n // Allocate output: VT + payload + FS + CR — canonical, immutable shape (FRAME-03).\n // allocUnsafe is safe here because all bytes are set before the buffer is returned.\n const frame = Buffer.allocUnsafe(payload.length + 3);\n frame[0] = VT;\n payload.copy(frame, 1);\n frame[payload.length + 1] = FS;\n frame[payload.length + 2] = CR;\n return frame;\n}\n","/**\n * Byte-level MSH-10 / MSA-2 control-ID scanners — the **single** implementation\n * of \"read the control ID off the wire\", shared by everything in this package\n * that has to agree on what a control ID *is*.\n *\n * Three call sites depend on these bytes agreeing, and a disagreement between\n * any two of them is a lost clinical message:\n *\n * * `src/client/correlator.ts` keys its in-flight store by the MSH-10 it\n * extracts from the **outbound** frame, and looks an inbound ACK up by the\n * MSA-2 it extracts from the **ACK** frame.\n * * `src/server/ack.ts` (`buildRawAck`) echoes the inbound MSH-10 into MSA-2.\n * * `src/ack-from-hl7/build.ts` verifies, against these same scanners, that\n * the ACK it hands back really does echo MSH-10 verbatim.\n *\n * They live here — not in `client/` — because none of that is client policy.\n * It is the package's byte-level reading of HL7 v2.5.1 §2.5.4 (MSH-1 *defines*\n * the field separator) and §2.9.2.2 (MSA-2 echoes MSH-10 verbatim).\n *\n * **INTERNAL** — not part of the public API.\n *\n * @packageDocumentation\n */\n\nconst ASCII_M = 0x4d;\nconst ASCII_S = 0x53;\nconst ASCII_H = 0x48;\nconst ASCII_A = 0x41;\nconst ASCII_B = 0x42;\nconst ASCII_F = 0x46;\nconst ASCII_T = 0x54;\nconst SEGMENT_SEPARATOR_CR = 0x0d;\nconst SEGMENT_SEPARATOR_LF = 0x0a;\n\n/**\n * Decode used for MSH-10 / MSA-2 control-ID extraction: `latin1`, NOT `ascii`.\n *\n * Node's `ascii` codec masks the high bit (`byte & 0x7f`), so it is a **lossy**\n * decode of the raw HL7 v2 bytes. Two consequences, both of which land squarely\n * on ACK correlation:\n *\n * 1. **Key collision.** The correlator keys its live store, its graveyard, and\n * its ACK lookups by the extracted control-ID *string*. Under `ascii`, the\n * distinct control IDs `A\\x8B` and `A\\x0B` both decode to `A\\x0B`, so two\n * concurrently in-flight sends collapse onto one key: the second `enqueue()`\n * overwrites the first in the `Map`, and the first send's promise can never\n * be settled by its own ACK. `latin1` is a 1:1 byte↔code-unit mapping, so\n * every distinct byte string stays a distinct key.\n * 2. **Corrupted observability.** The extracted ID is what we hand to\n * `MLLP_ACK_UNMATCHED_CONTROL_ID` / `MLLP_ACK_AFTER_TIMEOUT` warnings and to\n * `MllpTimeoutError.messageControlId`. A masked ID is a control ID that never\n * existed on the wire — it misdirects exactly the operator who is trying to\n * trace a lost message.\n *\n * Reachable whenever MSH-18 declares a non-ASCII charset (e.g. `8859/1`), where\n * high-bit bytes in a control ID are legal.\n *\n * **`latin1` is the only byte-faithful decode available.** It is not merely the\n * best of several — it is the one that exists. Node's `Buffer` `latin1` codec is\n * true ISO-8859-1: a 1:1 map between the 256 byte values and U+0000–U+00FF, so\n * `Buffer.from(buf.toString(\"latin1\"), \"latin1\")` is the identity for *every*\n * byte string. Every alternative loses bytes in exactly the range that matters:\n *\n * * `ascii` masks the high bit (above).\n * * `utf8` folds every invalid sequence onto `U+FFFD`, so all high-bit bytes in\n * a non-UTF-8 payload collapse onto one key — the same collision as `ascii`.\n * * `TextDecoder(\"iso-8859-1\")` — which is what a charset-driven decode such as\n * `@cosyte/hl7`'s `parseHL7(buffer, { charset })` uses — is **not** ISO-8859-1\n * at all. The WHATWG Encoding Standard aliases the label `iso-8859-1` to\n * **windows-1252**, which maps 0x80–0x9F to typography (`0x8B` → `U+2039`,\n * `0x9C` → `U+0153`) rather than to `U+008B`/`U+009C`. Re-encoding that back\n * to bytes does not round-trip. That is precisely the byte range a high-bit\n * control ID lives in, which is why this package decodes control-ID bytes\n * itself rather than delegating to a charset-aware parser.\n *\n * The key is byte-faithful, not text: under a multi-byte charset (`UNICODE UTF-8`)\n * a control ID reads back as its `latin1` bytes. Correlation stays correct — the\n * map is injective for any charset, which is the property the key needs, and the\n * one a charset-driven decode would lose.\n *\n * @internal\n */\nexport const CONTROL_ID_ENCODING = \"latin1\" as const;\n\n/**\n * MSH-1 values we refuse to read a message with.\n *\n * MSH-1 *is* the field separator (§2.5.4), but a segment terminator (`CR`/`LF`) or\n * an MLLP framing byte (`VT`/`FS`) cannot be one: the first would end the segment it\n * is supposed to delimit, and the second cannot be written back into an ACK without\n * making the ACK unframeable (strict `encodeFrame` throws `MLLP_PAYLOAD_CONTAINS_VT`/`_FS`).\n *\n * **How a payload with such a byte reaches us.** The plainest route is that `buildRawAck` is\n * a **public export**, so a caller can hand any `Buffer` straight to it, decoder or no\n * decoder; these scanners also run on **outbound** payloads the caller supplies. But it can\n * come off the wire too: a `VT` mid-payload makes `FrameReader` discard what it accumulated\n * and start over (`MLLP_TRAILING_BYTES`), so a *delivered* payload never contains a `VT` — but\n * a delivered payload CAN contain an `FS`. Under the `allowMissingLeadingVt` tolerance a non-VT,\n * non-whitespace first byte is taken as payload byte 0, and `FS` (0x1C) is neither, so\n * `FS \"MSH…\" FS CR` delivers a payload whose byte 0 is `0x1C`. Either way the guard is\n * warranted, and neither route depends on the false premise that the decoder screens these out.\n * @internal\n */\nconst UNSAFE_FIELD_SEPARATORS: ReadonlySet<number> = new Set([\n SEGMENT_SEPARATOR_CR,\n SEGMENT_SEPARATOR_LF,\n 0x0b, // VT — MLLP start block\n 0x1c, // FS — MLLP end block\n]);\n\n/** True iff the `CR`/`LF`-delimited segment starting at `i` is an `MSH`. @internal */\nfunction isMshSegmentAt(buf: Buffer, i: number): boolean {\n return (\n i + 3 < buf.length &&\n buf[i] === ASCII_M &&\n buf[i + 1] === ASCII_S &&\n buf[i + 2] === ASCII_H &&\n !UNSAFE_FIELD_SEPARATORS.has(buf[i + 3] as number)\n );\n}\n\n/** Index of the byte after the `CR`/`LF`-delimited segment starting at `i`. @internal */\nfunction segmentEnd(buf: Buffer, i: number): number {\n let end = i;\n while (\n end < buf.length &&\n buf[end] !== SEGMENT_SEPARATOR_CR &&\n buf[end] !== SEGMENT_SEPARATOR_LF\n ) {\n end++;\n }\n return end;\n}\n\n/**\n * Locate the message's `MSH` segment: the **first** `CR`/`LF`-delimited segment whose\n * first three bytes are `MSH` and whose 4th byte is a usable field separator. Returns\n * its `[start, end)` bounds, or `null` if the payload has no such segment.\n *\n * ## Why we search rather than demand `MSH` at byte 0\n *\n * §2.5.1 does make `MSH` the first segment of a *message*, and it is tempting to read\n * that as \"byte 0 or it is not HL7\" — that is what an earlier version of this module\n * did, to force its three consumers into agreement. It was a **tolerance regression**,\n * and it caused the exact harm this module exists to prevent. Two shapes reach us with\n * a perfectly good MSH-10 that is not at byte 0:\n *\n * * **A leading `CR`.** The MLLP decoder passes it straight through into the payload\n * (it special-cases only `VT`/`FS`), and real senders emit it.\n * * **A batch header.** `FHS`/`BHS` precede the `MSH` (§2.10.3).\n *\n * Under the byte-0 rule both read as \"no MSH\", so `buildRawAck` emitted a **positive\n * `AA` with an empty MSA-2 and no warning**: the sender — which keyed on the MSH-10 it\n * sent — cannot correlate it, times out, resends, and the receiver commits a\n * **duplicate clinical message**. Silently discarding a field that is *present* is the\n * worst thing a lenient reader can do, and this package's decoder is emphatically\n * lenient (Postel's Law; see CLAUDE.md).\n *\n * The consumers still agree — they simply agree at the **tolerant** fixed point rather\n * than the lossy one. Agreement was never the hard part; agreeing on the *right* answer\n * is.\n * @internal\n */\nfunction findMshSegment(buf: Buffer): { start: number; end: number } | null {\n let segStart = 0;\n while (segStart < buf.length) {\n const end = segmentEnd(buf, segStart);\n if (isMshSegmentAt(buf, segStart)) return { start: segStart, end };\n // Advance past this segment's terminator bytes (handles `CRLF` as one break).\n let next = end;\n while (\n next < buf.length &&\n (buf[next] === SEGMENT_SEPARATOR_CR || buf[next] === SEGMENT_SEPARATOR_LF)\n ) {\n next++;\n }\n if (next === segStart) return null; // no progress — malformed\n segStart = next;\n }\n return null;\n}\n\n/**\n * True iff the `CR`/`LF`-delimited segment starting at `i` is an HL7 **batch/file\n * envelope** header — `FHS`, `BHS`, `BTS`, or `FTS` (§2.10.3). Matched on the\n * three uppercase ASCII segment-ID bytes only; the 4th byte (the separator) is not\n * inspected, because the mere *presence* of a batch segment is what disqualifies a\n * single-message ACK, regardless of that segment's own delimiter. @internal\n */\nfunction isBatchEnvelopeSegmentAt(buf: Buffer, i: number): boolean {\n if (i + 2 >= buf.length) return false;\n const a = buf[i] as number;\n const b = buf[i + 1] as number;\n const c = buf[i + 2] as number;\n // FHS / FTS\n if (a === ASCII_F && ((b === ASCII_H && c === ASCII_S) || (b === ASCII_T && c === ASCII_S))) {\n return true;\n }\n // BHS / BTS\n if (a === ASCII_B && ((b === ASCII_H && c === ASCII_S) || (b === ASCII_T && c === ASCII_S))) {\n return true;\n }\n return false;\n}\n\n/**\n * True iff the payload is **not a single bare HL7 message** — it carries a batch/file\n * envelope (`FHS`/`BHS`/`BTS`/`FTS`, §2.10.3) or a **second `MSH` segment** (two or more\n * messages concatenated in one frame, a documented real-world quirk).\n *\n * ## Why this is a fail-safe refusal signal, not tolerance\n *\n * A single MSA-2 can echo exactly **one** MSH-10. A batch is a *sequence* of messages with\n * a `BTS-1` count; a concatenated frame is N messages back to back. Either way, a positive\n * `AA` that names the first control ID silently leaves messages 2..N unacknowledged — the\n * sender reads \"batch accepted\", so they are lost outright or time out and resend as\n * **duplicate clinical messages**. This predicate lets the ACK builders **downgrade to a\n * loud non-positive answer** for such a payload rather than fabricate a positive\n * disposition for messages nobody looked at.\n *\n * It does **not** parse the batch, re-base on the first `MSH`, or otherwise widen any\n * reader — it only *detects* the multi-message shape so callers can refuse it. Batch ACK\n * is its own feature (`MLLP-BATCH`); this is the refusal that keeps the door shut until it\n * is designed. A normal single message (`MSH` followed by `PID`/`PV1`/`OBR`/…) contains no\n * batch segment and exactly one `MSH`, so this returns `false` for it.\n *\n * Pure byte-level, never throws.\n *\n * @internal\n */\nexport function containsBatchOrExtraMessage(buf: Buffer): boolean {\n let mshCount = 0;\n let segStart = 0;\n while (segStart < buf.length) {\n const end = segmentEnd(buf, segStart);\n if (isMshSegmentAt(buf, segStart)) {\n mshCount++;\n if (mshCount > 1) return true;\n } else if (isBatchEnvelopeSegmentAt(buf, segStart)) {\n return true;\n }\n // Advance past this segment's terminator bytes (handles `CRLF` as one break).\n let next = end;\n while (\n next < buf.length &&\n (buf[next] === SEGMENT_SEPARATOR_CR || buf[next] === SEGMENT_SEPARATOR_LF)\n ) {\n next++;\n }\n if (next === segStart) return false; // no progress — malformed\n segStart = next;\n }\n return false;\n}\n\n/** The MSH segment, decoded and split into its fields. @internal */\nexport interface MshSegment {\n /** MSH-1 — the field separator this message declares (§2.5.4). One `latin1` char. */\n readonly fieldSep: string;\n /**\n * The MSH segment split on {@link fieldSep}. `[0]` is the literal `\"MSH\"`, `[1]` is\n * MSH-2 (the encoding characters), and thereafter the index **is** the field number:\n * `[9]` is MSH-10, `[10]` is MSH-11, `[11]` is MSH-12.\n */\n readonly fields: readonly string[];\n}\n\n/**\n * Read the MSH segment of an HL7 v2 payload — **the** scan, which every consumer in\n * this package goes through.\n *\n * Pure byte-level, never throws, returns `null` for anything it cannot read (Postel's\n * Law decoder side; CLAUDE.md guardrail). It:\n *\n * * **locates** the MSH — the first `CR`/`LF`-delimited segment that starts with\n * `MSH` — rather than demanding it at byte 0, so a leading `CR` or an `FHS`/`BHS`\n * batch header cannot hide a control ID that is plainly there ({@link findMshSegment});\n * * takes the field separator from MSH-1 rather than assuming `|` (§2.5.4);\n * * **bounds the field scan at that segment's terminator**;\n * * decodes only that segment, as `latin1` (see {@link CONTROL_ID_ENCODING}), so a\n * high-bit byte survives and a 16 MB payload is not decoded to read its header.\n *\n * ## The segment terminator is load-bearing, not a detail\n *\n * An earlier version of this scan counted field separators without ever stopping at\n * the segment terminator. On a **truncated MSH** — `MSH|^~\\&|EPIC|HOSP|MIRTH|LAB\\r`,\n * which has only 6 fields — the count therefore ran *past the `CR`* and kept counting\n * separators inside the next segment. The \"MSH-10\" it returned was `PID-3`: the\n * patient's **MRN**. That value became the client's correlation key, and was carried\n * into `MllpTimeoutError.messageControlId` and the `MLLP_ACK_UNMATCHED_CONTROL_ID` /\n * `MLLP_ACK_AFTER_TIMEOUT` warnings — a patient identifier in a log line, and a\n * mis-read one at that. A field that does not exist must read as absent, never as the\n * next segment's contents.\n *\n * The two rules are independent, and both are needed. Bounding the scan is what kills\n * the PID-3 read. **Locating** the MSH (rather than demanding it at byte 0) is what\n * stops the bound from turning into a tolerance regression that silently drops a\n * control ID which is present — see {@link findMshSegment}.\n *\n * @example\n * ```typescript\n * const msh = readMshSegment(payloadBuffer);\n * // msh?.fields[9] === 'MSG00001'; msh?.fieldSep === '|'\n * ```\n *\n * @internal\n */\nexport function readMshSegment(buf: Buffer): MshSegment | null {\n const at = findMshSegment(buf);\n if (at === null) return null;\n\n // The separator is MSH-1 — the 4th byte OF THE MSH SEGMENT, wherever that segment\n // begins. `findMshSegment` has already rejected an unusable one.\n const fieldSep = String.fromCharCode(buf[at.start + 3] as number);\n // `at.end` is the segment's terminator: the field split cannot reach past it.\n const fields = buf.subarray(at.start, at.end).toString(CONTROL_ID_ENCODING).split(fieldSep);\n return { fieldSep, fields };\n}\n\n/**\n * Strip **leading segment terminators only** — `CR`/`LF` bytes before the first segment.\n *\n * This is the whole of what the parser-backed ACK builder needs re-based, and it is\n * deliberately the *minimum*. `@cosyte/hl7`'s `parseHL7` requires `MSH` to be the first\n * segment and throws `NO_MSH_SEGMENT` otherwise, so a leading `CR` — which the MLLP\n * decoder passes straight through into the payload — would otherwise send `buildMllpAck`\n * down its unparseable fallback for a message whose MSH-10 is plainly readable. A leading\n * `CR`/`LF` is pure segment-terminator noise: it carries **no data**, so dropping it\n * cannot hide anything.\n *\n * ## What this deliberately does NOT do: skip an `FHS`/`BHS` batch header\n *\n * An earlier version of this function re-based on the *located `MSH`*, which also skipped\n * a batch envelope. That was a serious mistake. A batch (§2.10.3) is\n * `[FHS] { [BHS] { MSH … } [BTS] } [FTS]` — a **sequence of messages**. Re-basing on the\n * first `MSH` handed `parseHL7` message 1 and silently discarded every later `MSH`, the\n * `BTS` count, and the `FTS`. `buildMllpAck` then returned a confident positive **`AA`\n * correlated to message 1, with zero warnings**, for a batch whose messages 2..N it had\n * never looked at. The sender reads that as \"the batch is accepted\" — so those messages\n * are lost outright, or time out and resend as **duplicate clinical messages**.\n *\n * Batch ACK is a real feature and is tracked separately. It is not something to arrive at\n * by accident, via a byte-offset helper, on the way to fixing something else. Until it is\n * built deliberately, an `FHS`/`BHS` envelope must fall through to `parseHL7`'s\n * `NO_MSH_SEGMENT` and out into the **warned, non-positive `AE` fallback** — a loud\n * refusal to ACK what we did not read. That is the fail-safe answer and it is what the\n * package did before this item touched it.\n *\n * @internal\n */\nexport function stripLeadingSegmentTerminators(buf: Buffer): Buffer {\n let i = 0;\n while (i < buf.length && (buf[i] === SEGMENT_SEPARATOR_CR || buf[i] === SEGMENT_SEPARATOR_LF)) {\n i++;\n }\n return i === 0 ? buf : buf.subarray(i);\n}\n\n/**\n * Extract MSH-10 (Message Control ID) from an HL7 v2 payload — the correlation key.\n *\n * A thin read off {@link readMshSegment}: `null` when the payload has no readable MSH,\n * when the MSH segment is too short to reach MSH-10, or when MSH-10 is present but\n * empty. It is **never** a value taken from another segment.\n *\n * @example\n * ```typescript\n * const id = extractMshControlId(payloadBuffer);\n * // id === 'MSG00001' | null\n * ```\n *\n * @internal\n */\nexport function extractMshControlId(buf: Buffer): string | null {\n const msh = readMshSegment(buf);\n if (msh === null) return null;\n const id = msh.fields[9];\n return id === undefined || id === \"\" ? null : id;\n}\n\n/**\n * Extract MSA-2 (acknowledged Message Control ID) from an HL7 v2 ACK payload.\n *\n * Pure byte-level scan — never throws, returns `null` for malformed input. The field\n * separator comes from the ACK's own MSH-1, read through {@link readMshSegment} — the\n * same tolerant locate every other read in this module uses, so an ACK whose `MSH` is\n * not at byte 0 is still read rather than silently discarded. The `MSA` segment is\n * then located by scanning segment boundaries (`\\r` / `\\n`).\n *\n * Decoded as `latin1` (see {@link CONTROL_ID_ENCODING}), matching both\n * {@link extractMshControlId} and `buildRawAck`'s verbatim MSH-10 → MSA-2 echo —\n * so an ACK for a high-bit control ID looks up the key the send actually\n * enqueued.\n *\n * @example\n * ```typescript\n * const acked = extractMsaControlId(ackPayloadBuffer);\n * // acked === 'MSG00001' | null\n * ```\n *\n * @internal\n */\nexport function extractMsaControlId(buf: Buffer): string | null {\n // MSH-1 establishes the separator for the whole message, including its MSA.\n const msh = readMshSegment(buf);\n if (msh === null) return null;\n const fieldSep = msh.fieldSep.charCodeAt(0);\n const end = buf.length;\n let segStart = 0;\n while (segStart < end) {\n let segEnd = segStart;\n while (\n segEnd < end &&\n buf[segEnd] !== SEGMENT_SEPARATOR_CR &&\n buf[segEnd] !== SEGMENT_SEPARATOR_LF\n ) {\n segEnd++;\n }\n if (\n segEnd - segStart >= 4 &&\n buf[segStart] === ASCII_M &&\n buf[segStart + 1] === ASCII_S &&\n buf[segStart + 2] === ASCII_A &&\n buf[segStart + 3] === fieldSep\n ) {\n // MSA fields: [0]'MSA' [1]MSA-1 [2]MSA-2 ...\n // Iterate from the first separator (segStart+3) up to and including\n // segEnd. Treat `segEnd` as a synthetic separator so the final field\n // closes cleanly even without a trailing CR.\n let fieldIndex = 0;\n let fieldStart = 0;\n for (let i = segStart + 3; i <= segEnd; i++) {\n const b = i < segEnd ? (buf[i] as number) : fieldSep;\n if (b === fieldSep || b === SEGMENT_SEPARATOR_CR || b === SEGMENT_SEPARATOR_LF) {\n fieldIndex++;\n if (fieldIndex === 2) {\n fieldStart = i + 1;\n } else if (fieldIndex === 3) {\n if (fieldStart >= i) return null; // empty MSA-2\n return buf.subarray(fieldStart, i).toString(CONTROL_ID_ENCODING);\n }\n }\n }\n return null;\n }\n // Skip segment terminator bytes to advance to the next segment.\n while (\n segEnd < end &&\n (buf[segEnd] === SEGMENT_SEPARATOR_CR || buf[segEnd] === SEGMENT_SEPARATOR_LF)\n ) {\n segEnd++;\n }\n if (segEnd === segStart) return null; // no progress — malformed\n segStart = segEnd;\n }\n return null;\n}\n","/**\n * Lazy loader for the optional `@cosyte/hl7` peer dependency.\n *\n * `@cosyte/mllp` ships **zero runtime dependencies**; `@cosyte/hl7` is an\n * optional peer dep referenced only from this `/ack-from-hl7` subpath. To keep\n * the rest of the package fully dependency-free, the peer is required lazily —\n * on first call into `ack-from-hl7` — via `createRequire`, not at module load\n * time. The public API of this subpath stays synchronous throughout.\n *\n * @example\n * ```typescript\n * import { loadHl7Peer } from '@cosyte/mllp/ack-from-hl7';\n * const hl7 = loadHl7Peer(); // throws MllpPeerMissingError if not installed\n * ```\n *\n * @packageDocumentation\n */\n\nimport { createRequire } from \"node:module\";\n\nimport type {\n ACK_CODES,\n AckCode,\n BuildAckOptions,\n FATAL_CODES,\n Hl7Message as Hl7MessageType,\n Hl7ParseError as Hl7ParseErrorType,\n} from \"@cosyte/hl7\";\n\n/**\n * The subset of the `@cosyte/hl7` runtime surface the `ack-from-hl7` adapter\n * consumes. Kept narrow and typed via `import type` (erased at compile time)\n * so the runtime dependency stays fully lazy — only the shape is known\n * statically, the values are resolved via `require()` at first call.\n *\n * @example\n * ```typescript\n * import type { Hl7Peer } from '@cosyte/mllp/ack-from-hl7';\n * function useAck(peer: Hl7Peer) {\n * return peer.buildAck;\n * }\n * ```\n */\nexport interface Hl7Peer {\n readonly parseHL7: (raw: string | Buffer) => Hl7MessageType;\n readonly buildAck: (inbound: Hl7MessageType, options: BuildAckOptions) => Hl7MessageType;\n readonly detectAckMode: (inbound: Hl7MessageType) => \"original\" | \"enhanced\";\n readonly buildMessage: (init: { readonly type: string }) => Hl7MessageType;\n /**\n * The peer's own `Hl7Message` class. Exposed (not just used as a type) so\n * callers can detect a cross-realm instance — see the dual-package-hazard\n * note on `resolveInbound` in `build.ts`. Typed as `abstract new` (rather\n * than a concrete constructor signature) purely so TypeScript accepts it as\n * an `instanceof` right-hand side without also implying callers can `new`\n * it directly with arbitrary args.\n */\n readonly Hl7Message: abstract new (...args: never[]) => Hl7MessageType;\n readonly Hl7ParseError: typeof Hl7ParseErrorType;\n readonly FATAL_CODES: typeof FATAL_CODES;\n readonly ACK_CODES: typeof ACK_CODES;\n /**\n * The peer's fail-safe downgrade primitive (`AA`→`AE`, `CA`→`CE`, everything\n * else unchanged) — the single upstream source of truth for the pair; this\n * adapter never carries its own copy of the mapping.\n */\n readonly downgradePositiveAck: (code: AckCode) => AckCode;\n}\n\n/** The subpath's own name, used in {@link MllpPeerMissingError}'s message. */\nconst SUBPATH_NAME = \"@cosyte/mllp/ack-from-hl7\";\n\n/** The peer package name, as seen by Node module resolution. */\nconst PEER_PACKAGE_NAME = \"@cosyte/hl7\";\n\n/** Node error codes that indicate a module could not be found at all. */\nconst MODULE_NOT_FOUND_CODES: ReadonlySet<string> = new Set([\n \"ERR_MODULE_NOT_FOUND\",\n \"MODULE_NOT_FOUND\",\n]);\n\n/**\n * Thrown when `@cosyte/hl7` — the optional peer dependency required by\n * `@cosyte/mllp/ack-from-hl7` — cannot be resolved at runtime.\n *\n * `@cosyte/hl7` is declared as an optional peer dependency, so a consumer of\n * the root `@cosyte/mllp` entry point is never forced to install it. Only\n * code that imports from the `/ack-from-hl7` subpath needs it, and only once\n * that code actually runs (the loader is lazy).\n *\n * @example\n * ```typescript\n * import { buildAckAA, MllpPeerMissingError } from '@cosyte/mllp/ack-from-hl7';\n * try {\n * buildAckAA(inboundBuffer);\n * } catch (err) {\n * if (err instanceof MllpPeerMissingError) {\n * console.error(err.message); // explains how to install @cosyte/hl7\n * }\n * }\n * ```\n */\nexport class MllpPeerMissingError extends Error {\n override readonly name = \"MllpPeerMissingError\" as const;\n\n /** Stable machine-readable code identifying this error class. */\n readonly code = \"MLLP_PEER_MISSING\" as const;\n\n /**\n * Construct an `MllpPeerMissingError`.\n *\n * @param cause - The original module-resolution error, preserved for debugging.\n */\n constructor(cause: unknown) {\n super(\n `${PEER_PACKAGE_NAME} is required by ${SUBPATH_NAME} but is not installed. ` +\n `${PEER_PACKAGE_NAME} is an optional peer dependency of @cosyte/mllp — install it ` +\n `alongside @cosyte/mllp to use ${SUBPATH_NAME}: \\`npm install ${PEER_PACKAGE_NAME}\\` ` +\n `(or the equivalent for your package manager).`,\n { cause },\n );\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MllpPeerMissingError);\n }\n }\n}\n\n/** Cached peer module, populated on first successful {@link loadHl7Peer} call. */\nlet cachedPeer: Hl7Peer | undefined;\n\n/**\n * Matches the *quoted unresolvable module id* in Node's resolution-failure\n * messages — `Cannot find module '@cosyte/hl7'` (CJS) / `Cannot find package\n * '@cosyte/hl7'` (ESM). Anchoring on the quoted id (rather than a bare\n * substring test) keeps a module-not-found thrown from *inside* `@cosyte/hl7`\n * — whose message names a different module but whose require-stack still\n * contains the peer's path — propagating unchanged: that is a real bug in the\n * installed peer, not a missing peer.\n * @internal\n */\nconst PEER_UNRESOLVABLE_RE = /Cannot find (?:module|package) '@cosyte\\/hl7'/;\n\n/**\n * True iff `err` is a Node \"module could not be resolved\" error specifically\n * for the `@cosyte/hl7` peer package itself.\n * @internal\n */\nfunction isPeerModuleNotFound(err: unknown): boolean {\n if (typeof err !== \"object\" || err === null) return false;\n const code = (err as { code?: unknown }).code;\n if (typeof code !== \"string\" || !MODULE_NOT_FOUND_CODES.has(code)) return false;\n const message = (err as { message?: unknown }).message;\n return typeof message === \"string\" && PEER_UNRESOLVABLE_RE.test(message);\n}\n\n/**\n * Load the `@cosyte/hl7` peer module, caching the result for subsequent\n * calls. The public adapter functions in `build.ts` call this internally —\n * most consumers never call it directly.\n *\n * @param requireFn - Injectable `require`-like function, used by tests to\n * simulate a missing peer without actually uninstalling it. Defaults to a\n * real `createRequire(import.meta.url)`.\n * @internal\n *\n * @example\n * ```typescript\n * import { loadHl7Peer } from '@cosyte/mllp/ack-from-hl7';\n * const hl7 = loadHl7Peer();\n * const ack = hl7.buildAck(hl7.parseHL7(raw), { code: \"AA\" });\n * ```\n */\nexport function loadHl7Peer(requireFn?: (id: string) => unknown): Hl7Peer {\n if (cachedPeer !== undefined && requireFn === undefined) {\n return cachedPeer;\n }\n\n const doRequire = requireFn ?? createRequire(import.meta.url);\n\n let mod: unknown;\n try {\n mod = doRequire(PEER_PACKAGE_NAME);\n } catch (err) {\n if (isPeerModuleNotFound(err)) {\n throw new MllpPeerMissingError(err);\n }\n throw err;\n }\n\n const peer = mod as Hl7Peer;\n if (requireFn === undefined) {\n cachedPeer = peer;\n }\n return peer;\n}\n","/**\n * `buildMllpAck` — a THIN transport adapter over `@cosyte/hl7`'s `buildAck`.\n *\n * `@cosyte/hl7` owns ACK **content** (MSH/MSA/ERR construction, the fail-safe\n * no-correlation downgrade); this module owns **transport policy**: accepting\n * raw inbound bytes (or an already-parsed message), tolerating an unparseable\n * inbound without ever fabricating a positive disposition, and framing the\n * result into a ready-to-write MLLP `Buffer` via `encodeFrame`.\n *\n * @example\n * ```typescript\n * import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';\n * const { frame } = buildAckAA(inboundBuffer);\n * socket.write(frame);\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { AckCode, AckErrorDetail, AckMode, Hl7Message } from \"@cosyte/hl7\";\n\nimport { encodeFrame } from \"../framing/encoder.js\";\nimport {\n extractMsaControlId,\n extractMshControlId,\n stripLeadingSegmentTerminators,\n} from \"../internal/control-id.js\";\n\nimport { loadHl7Peer } from \"./peer.js\";\nimport type { Hl7Peer } from \"./peer.js\";\n\n/**\n * A single ACK-generation warning, surfaced by {@link buildMllpAck}.\n *\n * `code` is either an upstream `@cosyte/hl7` warning code passed through\n * verbatim (e.g. `\"ACK_NO_CORRELATION_ID\"`) or the one `mllp`-owned code,\n * {@link MLLP_ACK_INBOUND_UNPARSEABLE}.\n *\n * @example\n * ```typescript\n * import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';\n * const { warnings } = buildAckAA(inbound);\n * for (const w of warnings) console.warn(w.code, w.message);\n * ```\n */\nexport interface MllpAckWarning {\n readonly code: string;\n readonly message: string;\n}\n\n/**\n * Warning code emitted when the inbound message could not be parsed at all\n * (a fatal `Hl7ParseError`) and {@link buildMllpAck} fell back to a minimal,\n * uncorrelated ACK rather than fabricating a positive disposition.\n *\n * @example\n * ```typescript\n * import { MLLP_ACK_INBOUND_UNPARSEABLE, buildAckAA } from '@cosyte/mllp/ack-from-hl7';\n * const { warnings } = buildAckAA(\"not hl7 at all\");\n * warnings.some((w) => w.code === MLLP_ACK_INBOUND_UNPARSEABLE); // true\n * ```\n */\nexport const MLLP_ACK_INBOUND_UNPARSEABLE = \"MLLP_ACK_INBOUND_UNPARSEABLE\";\n\n/**\n * Warning code emitted when the built ACK's MSA-2 bytes are **not** byte-identical\n * to the inbound MSH-10 bytes — i.e. the ACK does not echo the message control ID\n * verbatim, as HL7 v2.5.1 §2.9.2.2 requires.\n *\n * This is a **correlation failure**, and the reason it is a loud warning rather\n * than a silent success: the sender keys its in-flight store on the MSH-10 bytes\n * it put on the wire. An ACK whose MSA-2 differs by even one byte will not match\n * that key, so the send is never settled → ACK timeout → resend → **duplicate\n * clinical message**. Emitting the ACK anyway (rather than throwing) is the\n * fail-safe choice — a mismatched ACK still tells the peer *something* — but the\n * mismatch must never pass unremarked.\n *\n * `buildMllpAck` verifies this on every build, against the very same byte-level\n * scanners the `@cosyte/mllp` client uses to correlate (`src/internal/control-id.ts`).\n *\n * **The verification is meaningful for a `Buffer` inbound, and only for a `Buffer`.**\n * A `Buffer` *is* the wire bytes, so comparing the ACK to it is a real byte-level check\n * and a clean result means a `@cosyte/mllp` sender will match this ACK. A\n * `string`/`Hl7Message` inbound has **already been decoded** before this module sees it:\n * the only thing left to compare against is that same text, encoded with the same codec,\n * so the codec cancels on both sides and a codec-induced mismatch is **structurally\n * invisible** — it warns about nothing. See {@link BuildMllpAckOptions.encoding}. Pass a\n * `Buffer` if you want this guarantee to mean anything.\n *\n * Two known ways to provoke it, both documented in the package limitations:\n *\n * 1. Overriding {@link BuildMllpAckOptions.encoding} to a codec that cannot\n * round-trip the inbound bytes (`\"utf8\"`/`\"ascii\"` against a high-bit,\n * 8859/1-charset control ID). The default never does this.\n * 2. An inbound that declares **non-default encoding characters** (MSH-1/MSH-2).\n * `@cosyte/hl7`'s `buildMessage` always emits the HL7 default `|^~\\&`, so an\n * inbound MSH-10 of `ID#X` under a `#` component separator is re-delimited to\n * `ID^X` in MSA-2 — different bytes, unmatchable key. Use `buildRawAck` (root\n * export), which echoes the inbound's own MSH-1/MSH-2, if you must serve such\n * a peer.\n *\n * @example\n * ```typescript\n * import { MLLP_ACK_CONTROL_ID_NOT_VERBATIM, buildAckAA } from '@cosyte/mllp/ack-from-hl7';\n * const { warnings } = buildAckAA(inbound, { encoding: \"ascii\" });\n * if (warnings.some((w) => w.code === MLLP_ACK_CONTROL_ID_NOT_VERBATIM)) {\n * // this ACK will NOT correlate at the sender — investigate before shipping it\n * }\n * ```\n */\nexport const MLLP_ACK_CONTROL_ID_NOT_VERBATIM = \"MLLP_ACK_CONTROL_ID_NOT_VERBATIM\";\n\n/**\n * Byte-faithful codec for raw-`Buffer` inbound — the default (HL7 v2.5.1 §2.9.2.2).\n *\n * `latin1` is Node's true ISO-8859-1: a 1:1 map between the 256 byte values and\n * U+0000–U+00FF, so `Buffer.from(buf.toString(\"latin1\"), \"latin1\")` is the identity\n * for **every** byte string. It is the only codec for which that holds, which makes\n * it the only one under which MSA-2 can echo an arbitrary inbound MSH-10 verbatim.\n * See `src/internal/control-id.ts` for why `ascii`, `utf8`, and a charset-driven\n * `TextDecoder` each lose bytes in exactly the range a high-bit control ID lives in.\n * @internal\n */\nconst BYTES_INBOUND_ENCODING: BufferEncoding = \"latin1\";\n\n/**\n * Codec for `string` / `Hl7Message` inbound — JS-native `utf8`.\n *\n * When the caller hands us text, **they** already chose the decode; we cannot know\n * which codec produced those code units, so we re-encode with the JS-native one and\n * let them override. (For raw bytes we own the decode, so we can guarantee the\n * round-trip — see {@link BYTES_INBOUND_ENCODING}.)\n * @internal\n */\nconst TEXT_INBOUND_ENCODING: BufferEncoding = \"utf8\";\n\n/**\n * Options for {@link buildMllpAck} and its six convenience wrappers.\n *\n * @example\n * ```typescript\n * import type { BuildMllpAckOptions } from '@cosyte/mllp/ack-from-hl7';\n * const opts: BuildMllpAckOptions = { code: \"AE\", error: { conditionCode: \"101\" } };\n * ```\n */\nexport interface BuildMllpAckOptions {\n /** Required acknowledgment disposition to emit in MSA-1 (HL7 Table 0008). */\n readonly code: AckCode;\n /** Optional error detail; one entry emits one ERR segment. */\n readonly error?: AckErrorDetail | readonly AckErrorDetail[];\n /**\n * The codec used to move the ACK between bytes and text — **both** directions,\n * as a single symmetric choice. It decodes a raw-`Buffer` `inbound` on the way in\n * and encodes the serialized ACK on the way out, so the ACK is emitted in the same\n * codec the inbound was read in.\n *\n * That symmetry is the whole point, and it is what makes MSA-2 echo MSH-10\n * **verbatim** (HL7 v2.5.1 §2.9.2.2). Decoding in one codec and encoding in another\n * corrupts every non-ASCII byte it round-trips: a `latin1`-decoded `0x8B` re-encoded\n * as `utf8` goes out as the two bytes `0xC2 0x8B`, which is a *different control ID*\n * — and the sender, which keyed its in-flight store on `0x8B`, cannot match the ACK.\n *\n * **Defaults, by inbound type:**\n *\n * - `Buffer` → **`\"latin1\"`**. We own the decode, and `latin1` is the only codec\n * that round-trips arbitrary bytes losslessly, so MSA-2 is byte-verbatim for any\n * charset — including an MSH-18 of `8859/1` with high-bit bytes in the control ID.\n * - `string` / `Hl7Message` → **`\"utf8\"`**. The caller already decoded; we re-encode\n * with the JS-native codec.\n *\n * **Override with care, and only on a `Buffer`.** Any value other than `\"latin1\"` on a\n * raw-`Buffer` inbound gives up the verbatim guarantee for non-ASCII bytes (`\"ascii\"`\n * masks the high bit; `\"utf8\"` folds invalid sequences onto `U+FFFD`). On a `Buffer`,\n * `buildMllpAck` **checks** the result and emits {@link MLLP_ACK_CONTROL_ID_NOT_VERBATIM}\n * if the echo did not survive, so a bad override there is loud rather than silent — but\n * it is still a broken ACK. Set this only when the receiving system genuinely demands a\n * specific codec.\n *\n * **On a `string` / `Hl7Message` inbound the check cannot see a codec problem at all.**\n * The wire bytes were decoded to text *before* this function saw them, so there is\n * nothing left to compare the ACK against except that same text — the codec cancels on\n * both sides and the comparison is a tautology. Concretely,\n * `buildAckAA(payload.toString(\"latin1\"))` on a high-bit control ID (`0x8B`, legal under\n * an `MSH-18` of `8859/1`) re-encodes it as the two `utf8` bytes `0xC2 0x8B`, emits a\n * **different** control ID, and warns about **nothing** — the sender cannot correlate it.\n * This is not a hole the guard can be grown to cover; the bytes are gone by then.\n *\n * **`Buffer` is the byte-safe path.** Pass the raw payload. It is what the server hands\n * you, it is what the `Buffer`-first API rule exists for, and it is the only input for\n * which the verbatim guarantee — or a warning that it broke — actually means anything.\n */\n readonly encoding?: BufferEncoding;\n /**\n * Passthrough to `encodeFrame`'s `allowDelimiterBytesInPayload`. Defaults\n * to `false` (strict — throws if the serialized ACK somehow contains a\n * VT/FS byte, which a spec-clean `@cosyte/hl7` emit never does).\n */\n readonly allowDelimiterBytesInPayload?: boolean;\n}\n\n/**\n * Result of {@link buildMllpAck} — a ready-to-write MLLP frame plus the\n * metadata needed to log or assert on what was actually emitted. Frozen\n * (including the `warnings` array) so subscribers cannot mutate shared state.\n *\n * @example\n * ```typescript\n * import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';\n * const ack = buildAckAA(inbound);\n * socket.write(ack.frame);\n * console.log(ack.code, ack.correlationId);\n * ```\n */\nexport interface MllpAck {\n /** `VT + payload + FS + CR`, ready to write to a socket. */\n readonly frame: Buffer;\n /** The serialized ACK, unframed. */\n readonly payload: Buffer;\n /** The built ACK `Hl7Message`. */\n readonly ack: Hl7Message;\n /** The disposition the caller asked for. */\n readonly requestedCode: AckCode;\n /** The disposition MSA-1 actually carries (post fail-safe downgrade). */\n readonly code: AckCode;\n /** Inbound MSH-10, echoed in MSA-2. `undefined` when unfindable. */\n readonly correlationId: string | undefined;\n /** Detected from inbound MSH-15/16. `undefined` when inbound unparseable. */\n readonly mode: AckMode | undefined;\n /** Warnings collected while building the ACK. */\n readonly warnings: readonly MllpAckWarning[];\n}\n\n/**\n * Validate that `code` is one of the six known HL7 Table 0008 acknowledgment\n * codes, using the peer's own `ACK_CODES` registry as the source of truth.\n * Mirrors `@cosyte/hl7`'s own `buildAck` runtime guard so JS callers (who\n * bypass the TypeScript union) get the same `TypeError` behavior.\n * @internal\n */\nfunction assertKnownAckCode(peer: Hl7Peer, code: unknown): asserts code is AckCode {\n const known: readonly string[] = Object.values(peer.ACK_CODES);\n if (typeof code !== \"string\" || !known.includes(code)) {\n throw new TypeError(\n `buildMllpAck: \\`code\\` must be a known HL7 Table 0008 acknowledgment code ` +\n `(AA/AE/AR/CA/CE/CR). Received: ${JSON.stringify(code)}.`,\n );\n }\n}\n\n/**\n * Resolve the single codec used for BOTH the inbound decode and the outbound\n * encode. An explicit `options.encoding` always wins; otherwise raw bytes get\n * the byte-faithful `latin1` and caller-supplied text gets JS-native `utf8`.\n * @internal\n */\nfunction resolveEncoding(\n inbound: Hl7Message | Buffer | string,\n options: BuildMllpAckOptions,\n): BufferEncoding {\n if (options.encoding !== undefined) return options.encoding;\n return Buffer.isBuffer(inbound) ? BYTES_INBOUND_ENCODING : TEXT_INBOUND_ENCODING;\n}\n\n/**\n * The inbound as bytes, for the verbatim-echo check — or `undefined` when the\n * inbound has no bytes to name.\n *\n * A `Buffer` *is* its bytes. A `string`'s bytes are that string under the\n * resolved codec. A pre-parsed `Hl7Message` has none: the caller already consumed\n * whatever bytes it came from, so there is nothing to compare MSA-2 against and\n * the check is skipped rather than invented.\n * @internal\n */\nfunction inboundBytes(\n inbound: Hl7Message | Buffer | string,\n encoding: BufferEncoding,\n): Buffer | undefined {\n if (Buffer.isBuffer(inbound)) return inbound;\n if (typeof inbound === \"string\") return Buffer.from(inbound, encoding);\n return undefined;\n}\n\n/**\n * Verify HL7 v2.5.1 §2.9.2.2 on the bytes we are about to put on the wire: the\n * ACK's MSA-2 must be byte-identical to the inbound's MSH-10.\n *\n * Deliberately checked against `src/internal/control-id.ts` — the very scanners\n * the `@cosyte/mllp` client uses to key its in-flight store and to look an ACK\n * back up. This is therefore not only a spec assertion but an end-to-end\n * correlation assertion: if it passes, a `@cosyte/mllp` sender will match this\n * ACK to its send.\n *\n * ## Its reach is bounded by what the caller gave us, and that bound is REAL\n *\n * `inboundBytes` re-encodes a `string`/`Hl7Message` inbound with the *same* codec the\n * ACK is encoded with, so on that path the codec **cancels on both sides** and this\n * check is a **tautology**: it cannot, even in principle, catch a codec-induced\n * non-verbatim echo. `buildAckAA(wire.toString(\"latin1\"))` on a `0x8B` control ID emits\n * `0xC2 0x8B` and this function returns `null` — no warning, unmatchable ACK.\n *\n * That is not a bug to fix here, and do **not** try to grow the guard to cover it: by\n * the time a `string` reaches us the wire bytes are already gone, and there is nothing\n * left to compare against. It is a real limitation of the `string` overload, documented\n * as such on {@link BuildMllpAckOptions.encoding} and in the package limitations. The\n * check is honest and complete for a `Buffer` inbound — the `Buffer`-first path the API\n * rule points at — and it claims nothing beyond that.\n *\n * Returns `null` when the check passes, or cannot be made at all — the inbound has no\n * readable MSH (it does not lead with `MSH`, e.g. it is still MLLP-framed), or it\n * carries no MSH-10 (the peer's own no-correlation fail-safe already warns about that).\n * We never warn on a comparison we could not actually perform.\n *\n * ## The message names NO field content — deliberately\n *\n * An earlier version hex-encoded both control IDs into the warning text, reasoning that\n * a control ID is routing metadata rather than clinical content and that an operator\n * tracing a lost message needs the bytes. That reasoning was wrong twice:\n *\n * 1. **It is not this function's call to make.** MSH-10 is inbound payload content,\n * this module's contract is that its warnings carry *never any inbound payload\n * bytes*, and a warning goes to a log — which in this domain is a place PHI must\n * not reach. A field is not safe to log merely because it is *usually* an opaque id.\n * 2. **It was demonstrably PHI.** Paired with a scanner that ran past the segment\n * terminator (since fixed — see `readMshSegment`), a truncated MSH made \"MSH-10\"\n * resolve to PID-3, and this warning rendered the patient's **MRN** in hex. The\n * scanner bug is fixed; withholding the bytes stays, as defence in depth: the next\n * such bug must not have a paved road into a log line.\n *\n * The warning says *that* the echo broke and what to compare. Nothing is lost — the\n * caller already holds both byte strings (the inbound is their own `payload`; the ACK is\n * the `MllpAck.payload` this call returns).\n * @internal\n */\nfunction verifyVerbatimEcho(\n ackPayload: Buffer,\n inboundRaw: Buffer | undefined,\n): MllpAckWarning | null {\n if (inboundRaw === undefined) return null;\n const inboundId = extractMshControlId(inboundRaw);\n if (inboundId === null) return null;\n const echoedId = extractMsaControlId(ackPayload);\n if (echoedId === inboundId) return null;\n\n // Byte LENGTHS only — never the bytes themselves. The ids are `latin1`, which is 1:1\n // with bytes, so `.length` is a byte count. A length is a shape, not content.\n const inboundLen = String(inboundId.length);\n const echoed = echoedId === null ? \"absent\" : `${String(echoedId.length)} bytes`;\n return {\n code: MLLP_ACK_CONTROL_ID_NOT_VERBATIM,\n message:\n `ACK does not echo the inbound MSH-10 verbatim (HL7 v2.5.1 §2.9.2.2): ` +\n `inbound MSH-10 is ${inboundLen} bytes, emitted MSA-2 is ${echoed}, and they differ. ` +\n `The sender keys its in-flight store on the inbound bytes, so it will NOT match this ACK ` +\n `(ACK timeout -> resend -> duplicate message). ` +\n `Field values are withheld — MSH-10 is inbound payload content and this warning goes to a log; ` +\n `compare your inbound payload against MllpAck.payload to see the bytes.`,\n };\n}\n\n/**\n * Serialize a built ACK message into a framed `MllpAck` result, encoding it with\n * the same codec the inbound was decoded with, and verifying the MSA-2 echo.\n * @internal\n */\nfunction toMllpAck(params: {\n readonly ack: Hl7Message;\n readonly requestedCode: AckCode;\n readonly code: AckCode;\n readonly correlationId: string | undefined;\n readonly mode: AckMode | undefined;\n readonly warnings: readonly MllpAckWarning[];\n readonly options: BuildMllpAckOptions;\n readonly encoding: BufferEncoding;\n /** Inbound bytes to verify the MSA-2 echo against; `undefined` skips the check. */\n readonly verifyAgainst: Buffer | undefined;\n}): MllpAck {\n const { ack, requestedCode, code, correlationId, mode, warnings, options, encoding } = params;\n const payload = Buffer.from(ack.toString(), encoding);\n const frame = encodeFrame(payload, {\n allowDelimiterBytesInPayload: options.allowDelimiterBytesInPayload ?? false,\n });\n\n const echoWarning = verifyVerbatimEcho(payload, params.verifyAgainst);\n const allWarnings = echoWarning === null ? warnings : [...warnings, echoWarning];\n\n const result: MllpAck = {\n frame,\n payload,\n ack,\n requestedCode,\n code,\n correlationId,\n mode,\n warnings: Object.freeze([...allWarnings]),\n };\n return Object.freeze(result);\n}\n\n/**\n * Build the minimal fallback ACK for an inbound message that failed to parse\n * at all (a fatal `Hl7ParseError`). Never emits a positive disposition — a\n * requested `AA`/`CA` is downgraded to `AE`/`CE`; every other code passes\n * through unchanged. MSA-2 is left empty (no fabricated correlation) and no\n * ERR segment is added — table-level detail stays the caller's concern via\n * `options.error` on the normal path; this fallback only guarantees a\n * spec-shaped, framed, non-positive ACK when the inbound could not be read\n * at all.\n * @internal\n */\nfunction buildUnparseableFallback(\n peer: Hl7Peer,\n requestedCode: AckCode,\n fatalCode: string,\n options: BuildMllpAckOptions,\n encoding: BufferEncoding,\n): MllpAck {\n // The downgrade pair lives UPSTREAM (single source of truth) — this adapter\n // never carries its own AA→AE / CA→CE copy.\n const code = peer.downgradePositiveAck(requestedCode);\n const ack = peer.buildMessage({ type: \"ACK\" }).addSegment(\"MSA\", [code, \"\"]);\n\n const warnings: readonly MllpAckWarning[] = [\n {\n code: MLLP_ACK_INBOUND_UNPARSEABLE,\n message: `Inbound message unparseable (${fatalCode}); emitted ${code} with no correlation id.`,\n },\n ];\n\n return toMllpAck({\n ack,\n requestedCode,\n code,\n correlationId: undefined,\n mode: undefined,\n warnings,\n options,\n encoding,\n // MSA-2 is INTENTIONALLY empty here — the inbound could not be parsed, so there is no\n // control id to echo and we refuse to fabricate one. Running the verbatim check would\n // report that deliberate choice as a violation; `MLLP_ACK_INBOUND_UNPARSEABLE` (above)\n // is the accurate warning for this case, and it is already attached.\n verifyAgainst: undefined,\n });\n}\n\n/**\n * Parse `inbound` if it is a `Buffer`/`string`; pass an already-parsed\n * `Hl7Message` through unchanged — UNLESS it was constructed by a different\n * loaded copy of `@cosyte/hl7` than the one this adapter lazily loaded (the\n * classic Node \"dual package hazard\": an ESM `import` and this loader's\n * `createRequire` can each resolve a distinct module instance with distinct\n * class identities, so the peer's own `inbound instanceof Hl7Message` guard\n * inside `buildAck` would otherwise reject a perfectly valid message). In\n * that case, re-derive an in-realm `Hl7Message` by round-tripping through\n * `.toString()` — cheap, and the wire format is the one true interchange\n * boundary between two copies of the same library.\n *\n * ## Why a `Buffer` is decoded here rather than handed to `parseHL7`\n *\n * `parseHL7(buffer)` does its own charset resolution (MSH-18 discovery, or an\n * `options.charset` override) and decodes through a WHATWG `TextDecoder`. That is\n * the right behaviour for reading *clinical text* — and the wrong one for an ACK\n * builder, which needs the control ID to survive as **bytes**:\n *\n * * `TextDecoder` has no byte-faithful codec. Its `iso-8859-1` label is aliased\n * by the WHATWG Encoding Standard to **windows-1252**, which maps `0x8B` to\n * `U+2039` and `0x9C` to `U+0153` — re-encoding those does not give back the\n * bytes that arrived. `utf8` (the default when MSH-18 is absent) folds every\n * invalid sequence onto `U+FFFD`, collapsing all high-bit bytes onto one value.\n * * Node's `Buffer` `latin1` codec, by contrast, IS a 1:1 byte↔code-unit map, so\n * `Buffer.from(buf.toString(\"latin1\"), \"latin1\")` is the identity for every\n * byte string.\n *\n * So we decode the bytes ourselves with the resolved `encoding` and hand `parseHL7`\n * a `string`, which it takes verbatim. Charset is the *caller's* concern (MSH-18,\n * and the `Buffer`-first API rule) — this adapter's concern is that the control ID\n * it echoes is the one that arrived.\n * @internal\n */\nfunction resolveInbound(\n peer: Hl7Peer,\n inbound: Hl7Message | Buffer | string,\n encoding: BufferEncoding,\n): Hl7Message {\n // Strip LEADING SEGMENT TERMINATORS ONLY, and do it for bytes and text alike so the two\n // give the same answer for the same message. `parseHL7` requires MSH to be the FIRST\n // segment and throws `NO_MSH_SEGMENT` otherwise; a leading `CR` (which the MLLP decoder\n // passes straight through) carries no data, so dropping it cannot hide anything.\n //\n // It deliberately stops there. It does NOT skip an `FHS`/`BHS` batch envelope — doing so\n // would hand `parseHL7` only the batch's FIRST message and yield a positive `AA` for\n // messages 2..N that were never read. A batch falls through to `NO_MSH_SEGMENT` and out\n // into the warned, non-positive `AE` fallback: a loud refusal to ACK what we did not\n // read. See `stripLeadingSegmentTerminators`.\n if (Buffer.isBuffer(inbound)) {\n return peer.parseHL7(stripLeadingSegmentTerminators(inbound).toString(encoding));\n }\n if (typeof inbound === \"string\") {\n return peer.parseHL7(inbound.replace(/^[\\r\\n]+/, \"\"));\n }\n const isPeerNative: boolean = inbound instanceof peer.Hl7Message;\n return isPeerNative ? inbound : peer.parseHL7(inbound.toString());\n}\n\n/**\n * Build a framed MLLP ACK from an inbound HL7 v2 message — the core\n * `ack-from-hl7` adapter. `inbound` may be an already-parsed `Hl7Message`, or\n * raw bytes/text to parse first.\n *\n * Behavior:\n * - **Fatally unparseable inbound** (`Hl7ParseError`) — never emits a\n * positive code: `AA`→`AE`, `CA`→`CE`; other codes pass through. MSA-2 is\n * empty, `correlationId` and `mode` are `undefined`, and `warnings`\n * contains one {@link MLLP_ACK_INBOUND_UNPARSEABLE} entry naming the fatal\n * code — never any inbound payload bytes.\n * - **Normal path** — detects the ack mode from inbound MSH-15/16, delegates\n * to `@cosyte/hl7`'s `buildAck` (which applies its own no-correlation\n * fail-safe), and forwards `buildAck`'s own warnings (mapped to\n * `{ code, message }`). Inbound parse warnings are NOT forwarded — they are\n * the caller's concern if they parsed the inbound themselves.\n *\n * @throws {MllpPeerMissingError} when `@cosyte/hl7` is not installed.\n * @throws {TypeError} when `options.code` is not a known HL7 Table 0008 code.\n *\n * @example\n * ```typescript\n * import { buildMllpAck } from '@cosyte/mllp/ack-from-hl7';\n * const ack = buildMllpAck(inboundBuffer, { code: \"AA\" });\n * socket.write(ack.frame);\n * ```\n */\nexport function buildMllpAck(\n inbound: Hl7Message | Buffer | string,\n options: BuildMllpAckOptions,\n): MllpAck {\n const peer = loadHl7Peer();\n assertKnownAckCode(peer, options.code);\n\n // ONE codec for the whole build — the inbound decode and the outbound encode are\n // the same choice, because an asymmetric pair is exactly what breaks the verbatim\n // MSA-2 echo (§2.9.2.2) and with it the sender's ACK correlation.\n const encoding = resolveEncoding(inbound, options);\n\n let msg: Hl7Message;\n try {\n msg = resolveInbound(peer, inbound, encoding);\n } catch (err) {\n if (err instanceof peer.Hl7ParseError) {\n return buildUnparseableFallback(peer, options.code, err.code, options, encoding);\n }\n throw err;\n }\n\n const mode = peer.detectAckMode(msg);\n const ack = peer.buildAck(\n msg,\n options.error === undefined\n ? { code: options.code, mode }\n : { code: options.code, error: options.error, mode },\n );\n\n const emittedCode = ack.get(\"MSA.1\");\n assertKnownAckCode(peer, emittedCode);\n\n // The correlation id is what MSA-2 ACTUALLY carries on the wire — the\n // verbatim field text (`Field.text`), never the component-1-only scalar.\n // A vendor-quirk inbound MSH-10 like `ID^X` surfaces here byte-for-byte,\n // matching what a raw-bytes-correlating sender (this package's own client\n // correlator) will compare against.\n const msa2 = ack.segments(\"MSA\")[0]?.field(2).text ?? \"\";\n const correlationId = msa2 !== \"\" ? msa2 : undefined;\n\n const warnings: readonly MllpAckWarning[] = ack.warnings.map((w) => ({\n code: w.code,\n message: w.message,\n }));\n\n return toMllpAck({\n ack,\n requestedCode: options.code,\n code: emittedCode,\n correlationId,\n mode,\n warnings,\n options,\n encoding,\n verifyAgainst: inboundBytes(inbound, encoding),\n });\n}\n\n/**\n * Build an `AA` (Application Accept) MLLP ACK.\n *\n * @example\n * ```typescript\n * import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';\n * socket.write(buildAckAA(inboundBuffer).frame);\n * ```\n */\nexport function buildAckAA(\n inbound: Hl7Message | Buffer | string,\n options?: Omit<BuildMllpAckOptions, \"code\">,\n): MllpAck {\n return buildMllpAck(inbound, { ...options, code: \"AA\" });\n}\n\n/**\n * Build an `AE` (Application Error) MLLP ACK.\n *\n * @example\n * ```typescript\n * import { buildAckAE } from '@cosyte/mllp/ack-from-hl7';\n * socket.write(buildAckAE(inboundBuffer, { error: { conditionCode: \"101\" } }).frame);\n * ```\n */\nexport function buildAckAE(\n inbound: Hl7Message | Buffer | string,\n options?: Omit<BuildMllpAckOptions, \"code\">,\n): MllpAck {\n return buildMllpAck(inbound, { ...options, code: \"AE\" });\n}\n\n/**\n * Build an `AR` (Application Reject) MLLP ACK.\n *\n * @example\n * ```typescript\n * import { buildAckAR } from '@cosyte/mllp/ack-from-hl7';\n * socket.write(buildAckAR(inboundBuffer, { error: { conditionCode: \"200\" } }).frame);\n * ```\n */\nexport function buildAckAR(\n inbound: Hl7Message | Buffer | string,\n options?: Omit<BuildMllpAckOptions, \"code\">,\n): MllpAck {\n return buildMllpAck(inbound, { ...options, code: \"AR\" });\n}\n\n/**\n * Build a `CA` (Commit Accept) MLLP ACK (enhanced mode).\n *\n * @example\n * ```typescript\n * import { buildAckCA } from '@cosyte/mllp/ack-from-hl7';\n * socket.write(buildAckCA(inboundBuffer).frame);\n * ```\n */\nexport function buildAckCA(\n inbound: Hl7Message | Buffer | string,\n options?: Omit<BuildMllpAckOptions, \"code\">,\n): MllpAck {\n return buildMllpAck(inbound, { ...options, code: \"CA\" });\n}\n\n/**\n * Build a `CE` (Commit Error) MLLP ACK (enhanced mode).\n *\n * @example\n * ```typescript\n * import { buildAckCE } from '@cosyte/mllp/ack-from-hl7';\n * socket.write(buildAckCE(inboundBuffer, { error: { conditionCode: \"101\" } }).frame);\n * ```\n */\nexport function buildAckCE(\n inbound: Hl7Message | Buffer | string,\n options?: Omit<BuildMllpAckOptions, \"code\">,\n): MllpAck {\n return buildMllpAck(inbound, { ...options, code: \"CE\" });\n}\n\n/**\n * Build a `CR` (Commit Reject) MLLP ACK (enhanced mode).\n *\n * @example\n * ```typescript\n * import { buildAckCR } from '@cosyte/mllp/ack-from-hl7';\n * socket.write(buildAckCR(inboundBuffer, { error: { conditionCode: \"200\" } }).frame);\n * ```\n */\nexport function buildAckCR(\n inbound: Hl7Message | Buffer | string,\n options?: Omit<BuildMllpAckOptions, \"code\">,\n): MllpAck {\n return buildMllpAck(inbound, { ...options, code: \"CR\" });\n}\n\n/**\n * Detect the HL7 acknowledgment mode (`\"original\"` vs `\"enhanced\"`) of an\n * inbound message, from MSH-15/16. Named `detectMode` (not `detectAckMode`)\n * to avoid colliding with the peer's own `detectAckMode` export for callers\n * who import both from `@cosyte/hl7` and `@cosyte/mllp/ack-from-hl7`.\n *\n * `inbound` may be an already-parsed `Hl7Message`, or raw bytes/text to parse\n * first. A fatal parse failure rethrows the `Hl7ParseError` as-is — unlike\n * {@link buildMllpAck}, there is no ACK to build here, so there is no\n * fallback to construct.\n *\n * **Module-realm note:** this adapter loads `@cosyte/hl7` lazily via\n * `createRequire` (see `peer.ts`). If your code ALSO does\n * `import { Hl7ParseError } from \"@cosyte/hl7\"` directly, Node's module\n * resolution can — depending on your bundler/runtime — hand you a distinct\n * module instance than the one this adapter loaded, in which case\n * `caught instanceof Hl7ParseError` (your import) may be `false` even though\n * `caught.name === \"Hl7ParseError\"` and `caught.code` is a valid HL7 fatal\n * code (`NO_MSH_SEGMENT` / `MSH_TOO_SHORT` / `INVALID_ENCODING_CHARACTERS` /\n * `EMPTY_INPUT`). Prefer narrowing on `.code`/`.name`, or obtain the exact\n * class via this subpath's own `loadHl7Peer().Hl7ParseError`.\n *\n * @throws {MllpPeerMissingError} when `@cosyte/hl7` is not installed.\n * @throws {Hl7ParseError} when `inbound` is raw bytes/text that fails to parse.\n *\n * @example\n * ```typescript\n * import { detectMode } from '@cosyte/mllp/ack-from-hl7';\n * detectMode(inboundBuffer); // \"original\" | \"enhanced\"\n * ```\n */\nexport function detectMode(inbound: Hl7Message | Buffer | string): AckMode {\n const peer = loadHl7Peer();\n // Same byte-faithful decode as `buildMllpAck` (see `resolveInbound`), so a\n // high-bit byte anywhere in the message cannot make the two disagree about\n // which message they are looking at. MSH-15/16 are ASCII either way, so this\n // is about consistency, not about the mode itself.\n const msg = resolveInbound(peer, inbound, BYTES_INBOUND_ENCODING);\n return peer.detectAckMode(msg);\n}\n"]}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { AckCode, AckErrorDetail, Hl7Message, AckMode, BuildAckOptions, Hl7ParseError, FATAL_CODES, ACK_CODES } from '@cosyte/hl7';
|
|
2
|
+
export { AckCode, AckErrorDetail, AckMode } from '@cosyte/hl7';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `buildMllpAck` — a THIN transport adapter over `@cosyte/hl7`'s `buildAck`.
|
|
6
|
+
*
|
|
7
|
+
* `@cosyte/hl7` owns ACK **content** (MSH/MSA/ERR construction, the fail-safe
|
|
8
|
+
* no-correlation downgrade); this module owns **transport policy**: accepting
|
|
9
|
+
* raw inbound bytes (or an already-parsed message), tolerating an unparseable
|
|
10
|
+
* inbound without ever fabricating a positive disposition, and framing the
|
|
11
|
+
* result into a ready-to-write MLLP `Buffer` via `encodeFrame`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
16
|
+
* const { frame } = buildAckAA(inboundBuffer);
|
|
17
|
+
* socket.write(frame);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A single ACK-generation warning, surfaced by {@link buildMllpAck}.
|
|
25
|
+
*
|
|
26
|
+
* `code` is either an upstream `@cosyte/hl7` warning code passed through
|
|
27
|
+
* verbatim (e.g. `"ACK_NO_CORRELATION_ID"`) or the one `mllp`-owned code,
|
|
28
|
+
* {@link MLLP_ACK_INBOUND_UNPARSEABLE}.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
33
|
+
* const { warnings } = buildAckAA(inbound);
|
|
34
|
+
* for (const w of warnings) console.warn(w.code, w.message);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
interface MllpAckWarning {
|
|
38
|
+
readonly code: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Warning code emitted when the inbound message could not be parsed at all
|
|
43
|
+
* (a fatal `Hl7ParseError`) and {@link buildMllpAck} fell back to a minimal,
|
|
44
|
+
* uncorrelated ACK rather than fabricating a positive disposition.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import { MLLP_ACK_INBOUND_UNPARSEABLE, buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
49
|
+
* const { warnings } = buildAckAA("not hl7 at all");
|
|
50
|
+
* warnings.some((w) => w.code === MLLP_ACK_INBOUND_UNPARSEABLE); // true
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare const MLLP_ACK_INBOUND_UNPARSEABLE = "MLLP_ACK_INBOUND_UNPARSEABLE";
|
|
54
|
+
/**
|
|
55
|
+
* Warning code emitted when the built ACK's MSA-2 bytes are **not** byte-identical
|
|
56
|
+
* to the inbound MSH-10 bytes — i.e. the ACK does not echo the message control ID
|
|
57
|
+
* verbatim, as HL7 v2.5.1 §2.9.2.2 requires.
|
|
58
|
+
*
|
|
59
|
+
* This is a **correlation failure**, and the reason it is a loud warning rather
|
|
60
|
+
* than a silent success: the sender keys its in-flight store on the MSH-10 bytes
|
|
61
|
+
* it put on the wire. An ACK whose MSA-2 differs by even one byte will not match
|
|
62
|
+
* that key, so the send is never settled → ACK timeout → resend → **duplicate
|
|
63
|
+
* clinical message**. Emitting the ACK anyway (rather than throwing) is the
|
|
64
|
+
* fail-safe choice — a mismatched ACK still tells the peer *something* — but the
|
|
65
|
+
* mismatch must never pass unremarked.
|
|
66
|
+
*
|
|
67
|
+
* `buildMllpAck` verifies this on every build, against the very same byte-level
|
|
68
|
+
* scanners the `@cosyte/mllp` client uses to correlate (`src/internal/control-id.ts`).
|
|
69
|
+
*
|
|
70
|
+
* **The verification is meaningful for a `Buffer` inbound, and only for a `Buffer`.**
|
|
71
|
+
* A `Buffer` *is* the wire bytes, so comparing the ACK to it is a real byte-level check
|
|
72
|
+
* and a clean result means a `@cosyte/mllp` sender will match this ACK. A
|
|
73
|
+
* `string`/`Hl7Message` inbound has **already been decoded** before this module sees it:
|
|
74
|
+
* the only thing left to compare against is that same text, encoded with the same codec,
|
|
75
|
+
* so the codec cancels on both sides and a codec-induced mismatch is **structurally
|
|
76
|
+
* invisible** — it warns about nothing. See {@link BuildMllpAckOptions.encoding}. Pass a
|
|
77
|
+
* `Buffer` if you want this guarantee to mean anything.
|
|
78
|
+
*
|
|
79
|
+
* Two known ways to provoke it, both documented in the package limitations:
|
|
80
|
+
*
|
|
81
|
+
* 1. Overriding {@link BuildMllpAckOptions.encoding} to a codec that cannot
|
|
82
|
+
* round-trip the inbound bytes (`"utf8"`/`"ascii"` against a high-bit,
|
|
83
|
+
* 8859/1-charset control ID). The default never does this.
|
|
84
|
+
* 2. An inbound that declares **non-default encoding characters** (MSH-1/MSH-2).
|
|
85
|
+
* `@cosyte/hl7`'s `buildMessage` always emits the HL7 default `|^~\&`, so an
|
|
86
|
+
* inbound MSH-10 of `ID#X` under a `#` component separator is re-delimited to
|
|
87
|
+
* `ID^X` in MSA-2 — different bytes, unmatchable key. Use `buildRawAck` (root
|
|
88
|
+
* export), which echoes the inbound's own MSH-1/MSH-2, if you must serve such
|
|
89
|
+
* a peer.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* import { MLLP_ACK_CONTROL_ID_NOT_VERBATIM, buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
94
|
+
* const { warnings } = buildAckAA(inbound, { encoding: "ascii" });
|
|
95
|
+
* if (warnings.some((w) => w.code === MLLP_ACK_CONTROL_ID_NOT_VERBATIM)) {
|
|
96
|
+
* // this ACK will NOT correlate at the sender — investigate before shipping it
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare const MLLP_ACK_CONTROL_ID_NOT_VERBATIM = "MLLP_ACK_CONTROL_ID_NOT_VERBATIM";
|
|
101
|
+
/**
|
|
102
|
+
* Options for {@link buildMllpAck} and its six convenience wrappers.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* import type { BuildMllpAckOptions } from '@cosyte/mllp/ack-from-hl7';
|
|
107
|
+
* const opts: BuildMllpAckOptions = { code: "AE", error: { conditionCode: "101" } };
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
interface BuildMllpAckOptions {
|
|
111
|
+
/** Required acknowledgment disposition to emit in MSA-1 (HL7 Table 0008). */
|
|
112
|
+
readonly code: AckCode;
|
|
113
|
+
/** Optional error detail; one entry emits one ERR segment. */
|
|
114
|
+
readonly error?: AckErrorDetail | readonly AckErrorDetail[];
|
|
115
|
+
/**
|
|
116
|
+
* The codec used to move the ACK between bytes and text — **both** directions,
|
|
117
|
+
* as a single symmetric choice. It decodes a raw-`Buffer` `inbound` on the way in
|
|
118
|
+
* and encodes the serialized ACK on the way out, so the ACK is emitted in the same
|
|
119
|
+
* codec the inbound was read in.
|
|
120
|
+
*
|
|
121
|
+
* That symmetry is the whole point, and it is what makes MSA-2 echo MSH-10
|
|
122
|
+
* **verbatim** (HL7 v2.5.1 §2.9.2.2). Decoding in one codec and encoding in another
|
|
123
|
+
* corrupts every non-ASCII byte it round-trips: a `latin1`-decoded `0x8B` re-encoded
|
|
124
|
+
* as `utf8` goes out as the two bytes `0xC2 0x8B`, which is a *different control ID*
|
|
125
|
+
* — and the sender, which keyed its in-flight store on `0x8B`, cannot match the ACK.
|
|
126
|
+
*
|
|
127
|
+
* **Defaults, by inbound type:**
|
|
128
|
+
*
|
|
129
|
+
* - `Buffer` → **`"latin1"`**. We own the decode, and `latin1` is the only codec
|
|
130
|
+
* that round-trips arbitrary bytes losslessly, so MSA-2 is byte-verbatim for any
|
|
131
|
+
* charset — including an MSH-18 of `8859/1` with high-bit bytes in the control ID.
|
|
132
|
+
* - `string` / `Hl7Message` → **`"utf8"`**. The caller already decoded; we re-encode
|
|
133
|
+
* with the JS-native codec.
|
|
134
|
+
*
|
|
135
|
+
* **Override with care, and only on a `Buffer`.** Any value other than `"latin1"` on a
|
|
136
|
+
* raw-`Buffer` inbound gives up the verbatim guarantee for non-ASCII bytes (`"ascii"`
|
|
137
|
+
* masks the high bit; `"utf8"` folds invalid sequences onto `U+FFFD`). On a `Buffer`,
|
|
138
|
+
* `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 — but
|
|
140
|
+
* it is still a broken ACK. Set this only when the receiving system genuinely demands a
|
|
141
|
+
* specific codec.
|
|
142
|
+
*
|
|
143
|
+
* **On a `string` / `Hl7Message` inbound the check cannot see a codec problem at all.**
|
|
144
|
+
* The wire bytes were decoded to text *before* this function saw them, so there is
|
|
145
|
+
* nothing left to compare the ACK against except that same text — the codec cancels on
|
|
146
|
+
* both sides and the comparison is a tautology. Concretely,
|
|
147
|
+
* `buildAckAA(payload.toString("latin1"))` on a high-bit control ID (`0x8B`, legal under
|
|
148
|
+
* an `MSH-18` of `8859/1`) re-encodes it as the two `utf8` bytes `0xC2 0x8B`, emits a
|
|
149
|
+
* **different** control ID, and warns about **nothing** — the sender cannot correlate it.
|
|
150
|
+
* This is not a hole the guard can be grown to cover; the bytes are gone by then.
|
|
151
|
+
*
|
|
152
|
+
* **`Buffer` is the byte-safe path.** Pass the raw payload. It is what the server hands
|
|
153
|
+
* you, it is what the `Buffer`-first API rule exists for, and it is the only input for
|
|
154
|
+
* which the verbatim guarantee — or a warning that it broke — actually means anything.
|
|
155
|
+
*/
|
|
156
|
+
readonly encoding?: BufferEncoding;
|
|
157
|
+
/**
|
|
158
|
+
* Passthrough to `encodeFrame`'s `allowDelimiterBytesInPayload`. Defaults
|
|
159
|
+
* to `false` (strict — throws if the serialized ACK somehow contains a
|
|
160
|
+
* VT/FS byte, which a spec-clean `@cosyte/hl7` emit never does).
|
|
161
|
+
*/
|
|
162
|
+
readonly allowDelimiterBytesInPayload?: boolean;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Result of {@link buildMllpAck} — a ready-to-write MLLP frame plus the
|
|
166
|
+
* metadata needed to log or assert on what was actually emitted. Frozen
|
|
167
|
+
* (including the `warnings` array) so subscribers cannot mutate shared state.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
172
|
+
* const ack = buildAckAA(inbound);
|
|
173
|
+
* socket.write(ack.frame);
|
|
174
|
+
* console.log(ack.code, ack.correlationId);
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
interface MllpAck {
|
|
178
|
+
/** `VT + payload + FS + CR`, ready to write to a socket. */
|
|
179
|
+
readonly frame: Buffer;
|
|
180
|
+
/** The serialized ACK, unframed. */
|
|
181
|
+
readonly payload: Buffer;
|
|
182
|
+
/** The built ACK `Hl7Message`. */
|
|
183
|
+
readonly ack: Hl7Message;
|
|
184
|
+
/** The disposition the caller asked for. */
|
|
185
|
+
readonly requestedCode: AckCode;
|
|
186
|
+
/** The disposition MSA-1 actually carries (post fail-safe downgrade). */
|
|
187
|
+
readonly code: AckCode;
|
|
188
|
+
/** Inbound MSH-10, echoed in MSA-2. `undefined` when unfindable. */
|
|
189
|
+
readonly correlationId: string | undefined;
|
|
190
|
+
/** Detected from inbound MSH-15/16. `undefined` when inbound unparseable. */
|
|
191
|
+
readonly mode: AckMode | undefined;
|
|
192
|
+
/** Warnings collected while building the ACK. */
|
|
193
|
+
readonly warnings: readonly MllpAckWarning[];
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Build a framed MLLP ACK from an inbound HL7 v2 message — the core
|
|
197
|
+
* `ack-from-hl7` adapter. `inbound` may be an already-parsed `Hl7Message`, or
|
|
198
|
+
* raw bytes/text to parse first.
|
|
199
|
+
*
|
|
200
|
+
* Behavior:
|
|
201
|
+
* - **Fatally unparseable inbound** (`Hl7ParseError`) — never emits a
|
|
202
|
+
* positive code: `AA`→`AE`, `CA`→`CE`; other codes pass through. MSA-2 is
|
|
203
|
+
* empty, `correlationId` and `mode` are `undefined`, and `warnings`
|
|
204
|
+
* contains one {@link MLLP_ACK_INBOUND_UNPARSEABLE} entry naming the fatal
|
|
205
|
+
* code — never any inbound payload bytes.
|
|
206
|
+
* - **Normal path** — detects the ack mode from inbound MSH-15/16, delegates
|
|
207
|
+
* to `@cosyte/hl7`'s `buildAck` (which applies its own no-correlation
|
|
208
|
+
* fail-safe), and forwards `buildAck`'s own warnings (mapped to
|
|
209
|
+
* `{ code, message }`). Inbound parse warnings are NOT forwarded — they are
|
|
210
|
+
* the caller's concern if they parsed the inbound themselves.
|
|
211
|
+
*
|
|
212
|
+
* @throws {MllpPeerMissingError} when `@cosyte/hl7` is not installed.
|
|
213
|
+
* @throws {TypeError} when `options.code` is not a known HL7 Table 0008 code.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* import { buildMllpAck } from '@cosyte/mllp/ack-from-hl7';
|
|
218
|
+
* const ack = buildMllpAck(inboundBuffer, { code: "AA" });
|
|
219
|
+
* socket.write(ack.frame);
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
declare function buildMllpAck(inbound: Hl7Message | Buffer | string, options: BuildMllpAckOptions): MllpAck;
|
|
223
|
+
/**
|
|
224
|
+
* Build an `AA` (Application Accept) MLLP ACK.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* import { buildAckAA } from '@cosyte/mllp/ack-from-hl7';
|
|
229
|
+
* socket.write(buildAckAA(inboundBuffer).frame);
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
declare function buildAckAA(inbound: Hl7Message | Buffer | string, options?: Omit<BuildMllpAckOptions, "code">): MllpAck;
|
|
233
|
+
/**
|
|
234
|
+
* Build an `AE` (Application Error) MLLP ACK.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* import { buildAckAE } from '@cosyte/mllp/ack-from-hl7';
|
|
239
|
+
* socket.write(buildAckAE(inboundBuffer, { error: { conditionCode: "101" } }).frame);
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
declare function buildAckAE(inbound: Hl7Message | Buffer | string, options?: Omit<BuildMllpAckOptions, "code">): MllpAck;
|
|
243
|
+
/**
|
|
244
|
+
* Build an `AR` (Application Reject) MLLP ACK.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* import { buildAckAR } from '@cosyte/mllp/ack-from-hl7';
|
|
249
|
+
* socket.write(buildAckAR(inboundBuffer, { error: { conditionCode: "200" } }).frame);
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
declare function buildAckAR(inbound: Hl7Message | Buffer | string, options?: Omit<BuildMllpAckOptions, "code">): MllpAck;
|
|
253
|
+
/**
|
|
254
|
+
* Build a `CA` (Commit Accept) MLLP ACK (enhanced mode).
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```typescript
|
|
258
|
+
* import { buildAckCA } from '@cosyte/mllp/ack-from-hl7';
|
|
259
|
+
* socket.write(buildAckCA(inboundBuffer).frame);
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
declare function buildAckCA(inbound: Hl7Message | Buffer | string, options?: Omit<BuildMllpAckOptions, "code">): MllpAck;
|
|
263
|
+
/**
|
|
264
|
+
* Build a `CE` (Commit Error) MLLP ACK (enhanced mode).
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* import { buildAckCE } from '@cosyte/mllp/ack-from-hl7';
|
|
269
|
+
* socket.write(buildAckCE(inboundBuffer, { error: { conditionCode: "101" } }).frame);
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
declare function buildAckCE(inbound: Hl7Message | Buffer | string, options?: Omit<BuildMllpAckOptions, "code">): MllpAck;
|
|
273
|
+
/**
|
|
274
|
+
* Build a `CR` (Commit Reject) MLLP ACK (enhanced mode).
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* import { buildAckCR } from '@cosyte/mllp/ack-from-hl7';
|
|
279
|
+
* socket.write(buildAckCR(inboundBuffer, { error: { conditionCode: "200" } }).frame);
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
declare function buildAckCR(inbound: Hl7Message | Buffer | string, options?: Omit<BuildMllpAckOptions, "code">): MllpAck;
|
|
283
|
+
/**
|
|
284
|
+
* Detect the HL7 acknowledgment mode (`"original"` vs `"enhanced"`) of an
|
|
285
|
+
* inbound message, from MSH-15/16. Named `detectMode` (not `detectAckMode`)
|
|
286
|
+
* to avoid colliding with the peer's own `detectAckMode` export for callers
|
|
287
|
+
* who import both from `@cosyte/hl7` and `@cosyte/mllp/ack-from-hl7`.
|
|
288
|
+
*
|
|
289
|
+
* `inbound` may be an already-parsed `Hl7Message`, or raw bytes/text to parse
|
|
290
|
+
* first. A fatal parse failure rethrows the `Hl7ParseError` as-is — unlike
|
|
291
|
+
* {@link buildMllpAck}, there is no ACK to build here, so there is no
|
|
292
|
+
* fallback to construct.
|
|
293
|
+
*
|
|
294
|
+
* **Module-realm note:** this adapter loads `@cosyte/hl7` lazily via
|
|
295
|
+
* `createRequire` (see `peer.ts`). If your code ALSO does
|
|
296
|
+
* `import { Hl7ParseError } from "@cosyte/hl7"` directly, Node's module
|
|
297
|
+
* resolution can — depending on your bundler/runtime — hand you a distinct
|
|
298
|
+
* module instance than the one this adapter loaded, in which case
|
|
299
|
+
* `caught instanceof Hl7ParseError` (your import) may be `false` even though
|
|
300
|
+
* `caught.name === "Hl7ParseError"` and `caught.code` is a valid HL7 fatal
|
|
301
|
+
* code (`NO_MSH_SEGMENT` / `MSH_TOO_SHORT` / `INVALID_ENCODING_CHARACTERS` /
|
|
302
|
+
* `EMPTY_INPUT`). Prefer narrowing on `.code`/`.name`, or obtain the exact
|
|
303
|
+
* class via this subpath's own `loadHl7Peer().Hl7ParseError`.
|
|
304
|
+
*
|
|
305
|
+
* @throws {MllpPeerMissingError} when `@cosyte/hl7` is not installed.
|
|
306
|
+
* @throws {Hl7ParseError} when `inbound` is raw bytes/text that fails to parse.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* import { detectMode } from '@cosyte/mllp/ack-from-hl7';
|
|
311
|
+
* detectMode(inboundBuffer); // "original" | "enhanced"
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
declare function detectMode(inbound: Hl7Message | Buffer | string): AckMode;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Lazy loader for the optional `@cosyte/hl7` peer dependency.
|
|
318
|
+
*
|
|
319
|
+
* `@cosyte/mllp` ships **zero runtime dependencies**; `@cosyte/hl7` is an
|
|
320
|
+
* 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` — via `createRequire`, not at module load
|
|
323
|
+
* time. The public API of this subpath stays synchronous throughout.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* import { loadHl7Peer } from '@cosyte/mllp/ack-from-hl7';
|
|
328
|
+
* const hl7 = loadHl7Peer(); // throws MllpPeerMissingError if not installed
|
|
329
|
+
* ```
|
|
330
|
+
*
|
|
331
|
+
* @packageDocumentation
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The subset of the `@cosyte/hl7` runtime surface the `ack-from-hl7` adapter
|
|
336
|
+
* consumes. Kept narrow and typed via `import type` (erased at compile time)
|
|
337
|
+
* so the runtime dependency stays fully lazy — only the shape is known
|
|
338
|
+
* statically, the values are resolved via `require()` at first call.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* import type { Hl7Peer } from '@cosyte/mllp/ack-from-hl7';
|
|
343
|
+
* function useAck(peer: Hl7Peer) {
|
|
344
|
+
* return peer.buildAck;
|
|
345
|
+
* }
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
interface Hl7Peer {
|
|
349
|
+
readonly parseHL7: (raw: string | Buffer) => Hl7Message;
|
|
350
|
+
readonly buildAck: (inbound: Hl7Message, options: BuildAckOptions) => Hl7Message;
|
|
351
|
+
readonly detectAckMode: (inbound: Hl7Message) => "original" | "enhanced";
|
|
352
|
+
readonly buildMessage: (init: {
|
|
353
|
+
readonly type: string;
|
|
354
|
+
}) => Hl7Message;
|
|
355
|
+
/**
|
|
356
|
+
* The peer's own `Hl7Message` class. Exposed (not just used as a type) so
|
|
357
|
+
* callers can detect a cross-realm instance — see the dual-package-hazard
|
|
358
|
+
* note on `resolveInbound` in `build.ts`. Typed as `abstract new` (rather
|
|
359
|
+
* than a concrete constructor signature) purely so TypeScript accepts it as
|
|
360
|
+
* an `instanceof` right-hand side without also implying callers can `new`
|
|
361
|
+
* it directly with arbitrary args.
|
|
362
|
+
*/
|
|
363
|
+
readonly Hl7Message: abstract new (...args: never[]) => Hl7Message;
|
|
364
|
+
readonly Hl7ParseError: typeof Hl7ParseError;
|
|
365
|
+
readonly FATAL_CODES: typeof FATAL_CODES;
|
|
366
|
+
readonly ACK_CODES: typeof ACK_CODES;
|
|
367
|
+
/**
|
|
368
|
+
* The peer's fail-safe downgrade primitive (`AA`→`AE`, `CA`→`CE`, everything
|
|
369
|
+
* else unchanged) — the single upstream source of truth for the pair; this
|
|
370
|
+
* adapter never carries its own copy of the mapping.
|
|
371
|
+
*/
|
|
372
|
+
readonly downgradePositiveAck: (code: AckCode) => AckCode;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Thrown when `@cosyte/hl7` — the optional peer dependency required by
|
|
376
|
+
* `@cosyte/mllp/ack-from-hl7` — cannot be resolved at runtime.
|
|
377
|
+
*
|
|
378
|
+
* `@cosyte/hl7` is declared as an optional peer dependency, so a consumer of
|
|
379
|
+
* the root `@cosyte/mllp` entry point is never forced to install it. Only
|
|
380
|
+
* code that imports from the `/ack-from-hl7` subpath needs it, and only once
|
|
381
|
+
* that code actually runs (the loader is lazy).
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* import { buildAckAA, MllpPeerMissingError } from '@cosyte/mllp/ack-from-hl7';
|
|
386
|
+
* try {
|
|
387
|
+
* buildAckAA(inboundBuffer);
|
|
388
|
+
* } catch (err) {
|
|
389
|
+
* if (err instanceof MllpPeerMissingError) {
|
|
390
|
+
* console.error(err.message); // explains how to install @cosyte/hl7
|
|
391
|
+
* }
|
|
392
|
+
* }
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
declare class MllpPeerMissingError extends Error {
|
|
396
|
+
readonly name: "MllpPeerMissingError";
|
|
397
|
+
/** Stable machine-readable code identifying this error class. */
|
|
398
|
+
readonly code: "MLLP_PEER_MISSING";
|
|
399
|
+
/**
|
|
400
|
+
* Construct an `MllpPeerMissingError`.
|
|
401
|
+
*
|
|
402
|
+
* @param cause - The original module-resolution error, preserved for debugging.
|
|
403
|
+
*/
|
|
404
|
+
constructor(cause: unknown);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Load the `@cosyte/hl7` peer module, caching the result for subsequent
|
|
408
|
+
* calls. The public adapter functions in `build.ts` call this internally —
|
|
409
|
+
* most consumers never call it directly.
|
|
410
|
+
*
|
|
411
|
+
* @param requireFn - Injectable `require`-like function, used by tests to
|
|
412
|
+
* simulate a missing peer without actually uninstalling it. Defaults to a
|
|
413
|
+
* real `createRequire(import.meta.url)`.
|
|
414
|
+
* @internal
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* import { loadHl7Peer } from '@cosyte/mllp/ack-from-hl7';
|
|
419
|
+
* const hl7 = loadHl7Peer();
|
|
420
|
+
* const ack = hl7.buildAck(hl7.parseHL7(raw), { code: "AA" });
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
declare function loadHl7Peer(requireFn?: (id: string) => unknown): Hl7Peer;
|
|
424
|
+
|
|
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 };
|