@glion/mllp-client 0.16.0 → 0.19.0

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.
Files changed (86) hide show
  1. package/README.md +370 -195
  2. package/dist/assertions.d.ts +10 -0
  3. package/dist/assertions.d.ts.map +1 -0
  4. package/dist/client.d.ts +108 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/constants.d.ts +23 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/errors/base.d.ts +31 -0
  9. package/dist/errors/base.d.ts.map +1 -0
  10. package/dist/errors/index.d.ts +15 -0
  11. package/dist/errors/index.d.ts.map +1 -0
  12. package/dist/errors/mllp-already-sending-error.d.ts +14 -0
  13. package/dist/errors/mllp-already-sending-error.d.ts.map +1 -0
  14. package/dist/errors/mllp-client-closed-error.d.ts +13 -0
  15. package/dist/errors/mllp-client-closed-error.d.ts.map +1 -0
  16. package/dist/errors/mllp-connection-error.d.ts +9 -0
  17. package/dist/errors/mllp-connection-error.d.ts.map +1 -0
  18. package/dist/errors/mllp-connection-failed-error.d.ts +12 -0
  19. package/dist/errors/mllp-connection-failed-error.d.ts.map +1 -0
  20. package/dist/errors/mllp-connection-lost-error.d.ts +13 -0
  21. package/dist/errors/mllp-connection-lost-error.d.ts.map +1 -0
  22. package/dist/errors/mllp-connection-timeout-error.d.ts +13 -0
  23. package/dist/errors/mllp-connection-timeout-error.d.ts.map +1 -0
  24. package/dist/errors/mllp-invalid-message-error.d.ts +13 -0
  25. package/dist/errors/mllp-invalid-message-error.d.ts.map +1 -0
  26. package/dist/errors/mllp-invalid-option-error.d.ts +8 -0
  27. package/dist/errors/mllp-invalid-option-error.d.ts.map +1 -0
  28. package/dist/errors/mllp-invalid-response-error.d.ts +16 -0
  29. package/dist/errors/mllp-invalid-response-error.d.ts.map +1 -0
  30. package/dist/errors/mllp-send-aborted-error.d.ts +12 -0
  31. package/dist/errors/mllp-send-aborted-error.d.ts.map +1 -0
  32. package/dist/errors/mllp-send-timeout-error.d.ts +13 -0
  33. package/dist/errors/mllp-send-timeout-error.d.ts.map +1 -0
  34. package/dist/errors/nak-exception.d.ts +17 -0
  35. package/dist/errors/nak-exception.d.ts.map +1 -0
  36. package/dist/events.d.ts +35 -0
  37. package/dist/events.d.ts.map +1 -0
  38. package/dist/index.d.ts +13 -20
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +805 -3
  41. package/dist/index.js.map +1 -0
  42. package/dist/messages.d.ts +55 -0
  43. package/dist/messages.d.ts.map +1 -0
  44. package/dist/reconnect.d.ts +18 -0
  45. package/dist/reconnect.d.ts.map +1 -0
  46. package/dist/runtime/node.d.ts +39 -0
  47. package/dist/runtime/node.d.ts.map +1 -0
  48. package/dist/runtime/node.js +100 -0
  49. package/dist/runtime/node.js.map +1 -0
  50. package/dist/session.d.ts +63 -0
  51. package/dist/session.d.ts.map +1 -0
  52. package/dist/state.d.ts +39 -0
  53. package/dist/state.d.ts.map +1 -0
  54. package/dist/types.d.ts +109 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/utils.d.ts +9 -0
  57. package/dist/utils.d.ts.map +1 -0
  58. package/package.json +22 -48
  59. package/dist/client.js +0 -549
  60. package/dist/client.js.map +0 -1
  61. package/dist/core/acknowledgment.d.ts +0 -103
  62. package/dist/core/acknowledgment.d.ts.map +0 -1
  63. package/dist/core/client.d.ts +0 -256
  64. package/dist/core/client.d.ts.map +0 -1
  65. package/dist/core/connect.d.ts +0 -101
  66. package/dist/core/connect.d.ts.map +0 -1
  67. package/dist/core/errors.d.ts +0 -81
  68. package/dist/core/errors.d.ts.map +0 -1
  69. package/dist/core/index.d.ts +0 -23
  70. package/dist/core/index.d.ts.map +0 -1
  71. package/dist/core/index.js +0 -2
  72. package/dist/core/internal/ack-parser-stream.d.ts +0 -22
  73. package/dist/core/internal/ack-parser-stream.d.ts.map +0 -1
  74. package/dist/core/internal/subscribe-abort.d.ts +0 -30
  75. package/dist/core/internal/subscribe-abort.d.ts.map +0 -1
  76. package/dist/node.js +0 -214
  77. package/dist/node.js.map +0 -1
  78. package/dist/runtimes/internal/node-error-mapping.d.ts +0 -68
  79. package/dist/runtimes/internal/node-error-mapping.d.ts.map +0 -1
  80. package/dist/runtimes/node.d.ts +0 -47
  81. package/dist/runtimes/node.d.ts.map +0 -1
  82. package/dist/runtimes/node.js +0 -3
  83. package/dist/runtimes/workers.d.ts +0 -64
  84. package/dist/runtimes/workers.d.ts.map +0 -1
  85. package/dist/runtimes/workers.js +0 -143
  86. package/dist/runtimes/workers.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","names":["#host","#port","#timeout","#maxAckSize","#tls","#connect","#exchange"],"sources":["../src/core/errors.ts","../src/core/acknowledgment.ts","../src/core/internal/ack-parser-stream.ts","../src/core/internal/subscribe-abort.ts","../src/core/client.ts"],"sourcesContent":["import { MllpError } from \"@glion/mllp-transport\";\nimport type { MllpErrorOptions } from \"@glion/mllp-transport\";\n\n/**\n * Machine-readable codes for `MllpClient` transport failures.\n *\n * These cover everything that can go wrong between the moment\n * `MllpClient.send()` is called and the moment a complete ACK frame\n * has been decoded — i.e. everything that is not an HL7v2\n * application-level NAK. NAK responses (MSA-1 ∈ {AE, AR, CE, CR})\n * surface as `AckException` subclasses thrown directly from\n * `@glion/ack`, not as transport errors.\n */\nexport const MllpClientErrorCode = {\n /** No ACK arrived within the configured `timeout`. */\n /** The peer closed the connection before a complete ACK arrived. */\n CONNECTION_CLOSED: \"CONNECTION_CLOSED\",\n /** TCP connection could not be established (refused, DNS, routing). */\n CONNECTION_REFUSED: \"CONNECTION_REFUSED\",\n /**\n * The caller passed a payload that could not be MLLP-encoded —\n * typically because it was not a `string` or `Uint8Array`. Also\n * surfaced when constructor options fail validation (bad\n * host/port/timeout/etc). Thrown before any socket is opened, so\n * no connection is made and no resources need cleanup.\n */\n INVALID_INPUT: \"INVALID_INPUT\",\n MALFORMED_ACK: \"MALFORMED_ACK\",\n /** The remote sent bytes that did not form a valid MLLP frame. */\n MALFORMED_FRAME: \"MALFORMED_FRAME\",\n /** A frame was received but could not be parsed as a valid HL7v2 ACK. */\n TIMEOUT: \"TIMEOUT\",\n /**\n * TLS handshake failed — typically because of certificate\n * verification, hostname mismatch, expired cert, untrusted CA, or\n * a TLS protocol error. Distinct from `CONNECTION_REFUSED` (the\n * TCP connection succeeded but the TLS upgrade failed) so\n * operators can identify TLS misconfiguration quickly in\n * healthcare deployments where it is a recurring incident class.\n */\n TLS_HANDSHAKE_FAILED: \"TLS_HANDSHAKE_FAILED\",\n} as const;\n\nexport type MllpClientErrorCode =\n (typeof MllpClientErrorCode)[keyof typeof MllpClientErrorCode];\n\n/**\n * Error thrown by `MllpClient.send()` for transport-level failures.\n *\n * Extends {@link MllpError} so callers can catch any MLLP error —\n * server- or client-side — with a single `instanceof MllpError`\n * check. The {@link MllpClientErrorCode | code} field discriminates\n * the failure mode for typed handling.\n *\n * Application-level NAK responses are **not** thrown as\n * `MllpClientError`; they are thrown as `AckException` subclasses\n * from `@glion/ack`. A typical `catch` block handles both:\n *\n * ```ts\n * import { AckException } from \"@glion/ack\";\n * import { MllpClientError } from \"@glion/mllp-client\";\n *\n * try {\n * await client.send(message);\n * } catch (error) {\n * if (error instanceof AckException) {\n * // Application-level NAK.\n * } else if (error instanceof MllpClientError) {\n * // Transport-level failure.\n * }\n * }\n * ```\n */\nexport class MllpClientError extends MllpError {\n /**\n * Narrowed view of the inherited `MllpError.code` field. `declare`\n * keeps the runtime value (set by the base constructor) while\n * exposing the discriminated {@link MllpClientErrorCode} union to\n * TypeScript — callers can `switch` on `error.code` with full\n * exhaustiveness checking.\n */\n declare readonly code: MllpClientErrorCode;\n\n constructor(\n code: MllpClientErrorCode,\n message: string,\n options?: MllpErrorOptions\n ) {\n super(code, message, options);\n this.name = \"MllpClientError\";\n }\n}\n","/**\n * Acknowledgment parsing for the MLLP client.\n *\n * This module owns three things:\n *\n * 1. The {@link Acknowledgment} interface — a structured view of an ACK message\n * exposing the fields callers most often need (MSA-1/2/3, ERR-3/4) without\n * forcing them to walk the AST themselves.\n * 2. {@link parseAck} — turns a raw wire-format ACK string into an\n * {@link Acknowledgment}, or throws {@link MllpClientError} when the payload\n * is not a recognisable ACK.\n * 3. {@link throwOnNak} — inspects an {@link Acknowledgment} and throws the\n * matching `AckException` subclass when MSA-1 indicates a NAK.\n *\n * Both helpers are internal to the package — only the {@link Acknowledgment}\n * type is re-exported from `@glion/mllp-client`. Application code interacts\n * with parsed acknowledgments through the return value (and thrown errors)\n * of `MllpClient.send()`.\n *\n * The HL7v2 ACK structure used here (Section 2.9.2 of the HL7v2 standard):\n *\n * MSH | header (sender, receiver, timestamp, message control id) MSA |\n * acknowledgment code (MSA-1), original control id (MSA-2), text (MSA-3) ERR |\n * optional error segment with HL7 condition code (ERR-3) and severity (ERR-4)\n *\n * @module\n */\n\nimport {\n AckApplicationError,\n AckApplicationReject,\n AckCode,\n AckCommitError,\n AckCommitReject,\n Hl7ErrorCode,\n Severity,\n} from \"@glion/ack\";\nimport type {\n AckCodeValue,\n AckException,\n AckExceptionOptions,\n Hl7ErrorCodeValue,\n SeverityValue,\n} from \"@glion/ack\";\nimport type { Root } from \"@glion/ast\";\nimport { parseHL7v2 } from \"@glion/parser\";\nimport { value } from \"@glion/util-query\";\n\nimport { MllpClientErrorCode, MllpClientError } from \"./errors\";\n\n// ---------------------------------------------------------------------------\n// Public type\n// ---------------------------------------------------------------------------\n\n/**\n * An HL7v2 acknowledgment message returned by an MLLP receiver, with\n * structured access to the fields callers most often need.\n *\n * Returned by `MllpClient.send()` on AA/CA. On NAK (AE/AR/CE/CR) an\n * `AckException` is thrown instead — the same shape is available there\n * via `error.message` (MSA-3), `error.errorCode` (ERR-3), `error.severity`\n * (ERR-4), and `error.raw` (the wire ACK).\n */\nexport interface Acknowledgment {\n /** The raw HL7v2 ACK message as received from the wire. */\n readonly raw: string;\n /**\n * The full ACK message AST, typed against `@glion/ast`. Use for\n * advanced inspection beyond the structured fields below; consumers\n * that walk the tree should install `@glion/ast` for the type.\n */\n readonly tree: Root;\n /**\n * MSA-1 acknowledgment code.\n *\n * Typed as the standard {@link AckCodeValue} union (`AA`, `AE`, `AR`,\n * `CA`, `CE`, `CR`) widened with an opaque `string` escape hatch — the\n * `(string & {})` form preserves auto-completion for the standard\n * codes while still accepting vendor-specific values that real\n * receivers occasionally send.\n */\n // oxlint-disable-next-line typescript-eslint/ban-types\n readonly code: AckCodeValue | (string & {});\n /** MSA-2 message control ID — references the original message's MSH-10. */\n readonly controlId: string;\n /** MSA-3 text message. Typically populated on NAK; optional on accept ACKs. */\n readonly textMessage: string | undefined;\n /**\n * ERR-3 HL7 error condition code (Table 0357). Present when an ERR segment is\n * included.\n */\n readonly errorCode: string | undefined;\n /** ERR-4 severity (Table 0516). Present when an ERR segment is included. */\n readonly severity: string | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Internal constants\n// ---------------------------------------------------------------------------\n\n/**\n * MSA-1 codes that mean \"the receiver accepted the message\". Anything\n * outside this set is a NAK and produces a thrown `AckException`.\n *\n * See HL7v2 Table 0008 — Acknowledgment Codes.\n */\nconst SUCCESS_CODES: ReadonlySet<string> = new Set<string>([\n AckCode.ApplicationAccept,\n AckCode.CommitAccept,\n]);\n\n/**\n * MSA-1 codes that are **intermediate** in HL7v2 enhanced\n * acknowledgment mode — the receiver will follow up with a separate\n * final acknowledgment later in the exchange. The MLLP client's read\n * loop continues past these by default and only resolves on a final\n * code.\n *\n * Per HL7v2 Section 2.9.2, only `CA` (Commit Accept) is intermediate;\n * `CE` and `CR` are final at the commit level. `AA`, `AE`, `AR` are\n * final at the application level.\n */\nconst INTERMEDIATE_ACK_CODES: ReadonlySet<string> = new Set<string>([\n AckCode.CommitAccept,\n]);\n\n/**\n * Returns `true` for any acknowledgment code that should terminate the\n * client's read loop. Intermediate codes (CA) return `false`; every\n * other code — including vendor-specific values — is treated as final\n * to avoid hanging on responses we do not recognise.\n */\nexport function isFinalAckCode(code: string): boolean {\n return !INTERMEDIATE_ACK_CODES.has(code);\n}\n\n// ---------------------------------------------------------------------------\n// parseAck\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a raw ACK frame string into an {@link Acknowledgment}.\n *\n * Field extraction is delegated to `@glion/util-query`'s `value()` helper —\n * this is the same path used elsewhere in the Glion stack and avoids\n * hand-rolling AST traversal here.\n *\n * Throws `MllpClientError(MALFORMED_ACK)` when:\n *\n * - The parser fails to produce a tree (malformed HL7v2), or\n * - MSA-1 is missing (the message is not a valid acknowledgment).\n *\n * Other missing fields (MSA-3, ERR-3, ERR-4) are not fatal — they are\n * exposed as `undefined` because real-world ACKs frequently omit them.\n */\nexport function parseAck(raw: string): Acknowledgment {\n const tree = parseTreeOrThrow(raw);\n const code = value(tree, \"MSA-1\")?.value;\n if (!code) {\n throw new MllpClientError(\n MllpClientErrorCode.MALFORMED_ACK,\n \"ACK response is missing MSA-1 acknowledgment code\"\n );\n }\n\n return {\n code,\n controlId: value(tree, \"MSA-2\")?.value ?? \"\",\n errorCode: value(tree, \"ERR-3\")?.value ?? undefined,\n raw,\n severity: value(tree, \"ERR-4\")?.value ?? undefined,\n textMessage: value(tree, \"MSA-3\")?.value ?? undefined,\n tree,\n };\n}\n\n/**\n * Run the HL7v2 parser, wrapping any thrown error in\n * `MllpClientError(MALFORMED_ACK)` so callers see one error shape\n * regardless of which lower-level component failed.\n */\nfunction parseTreeOrThrow(raw: string): Root {\n try {\n return parseHL7v2(raw);\n } catch (error) {\n throw new MllpClientError(\n MllpClientErrorCode.MALFORMED_ACK,\n `Failed to parse ACK response: ${formatCause(error)}`,\n { cause: error }\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// throwOnNak\n// ---------------------------------------------------------------------------\n\n/**\n * Inspect an {@link Acknowledgment} and throw the matching `AckException`\n * subclass when MSA-1 indicates a NAK. Returns the acknowledgment unchanged\n * on AA/CA.\n *\n * The thrown exception mirrors the receiver-side exception types — a\n * caller that already handles `AckApplicationError` from a server's\n * middleware can reuse the same `catch` blocks here.\n */\nexport function throwOnNak(ack: Acknowledgment): Acknowledgment {\n if (SUCCESS_CODES.has(ack.code)) {\n return ack;\n }\n throw buildAckException(ack);\n}\n\n/**\n * Convert a NAK acknowledgment into an `AckException` subclass.\n *\n * The fallbacks below preserve a useful exception even when the receiver\n * sent a sparse NAK: a missing MSA-3 yields a synthesised message; a\n * missing ERR segment yields the catch-all `ApplicationInternalError`\n * code (`207`) and severity Error (`E`). This matches the receiver-side\n * conventions in `@glion/ack`.\n */\nfunction buildAckException(ack: Acknowledgment): AckException {\n const message = ack.textMessage ?? `Acknowledgment ${ack.code} from receiver`;\n const options: AckExceptionOptions = {\n errorCode: coerceErrorCode(ack.errorCode),\n raw: ack.raw,\n severity: coerceSeverity(ack.severity),\n };\n\n switch (ack.code) {\n case AckCode.ApplicationError: {\n return new AckApplicationError(message, options);\n }\n case AckCode.ApplicationReject: {\n return new AckApplicationReject(message, options);\n }\n case AckCode.CommitError: {\n return new AckCommitError(message, options);\n }\n case AckCode.CommitReject: {\n return new AckCommitReject(message, options);\n }\n default: {\n // Unreachable in practice — `throwOnNak` filters AA/CA before\n // calling us, and any other value of MSA-1 maps to one of the four\n // cases above. Map unknown codes to ApplicationError so the caller\n // still gets a typed exception rather than a silent return.\n return new AckApplicationError(message, options);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Coercion helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Set of HL7 condition codes (Table 0357) we recognise. Built once from\n * the `Hl7ErrorCode` enum so the lookup stays cheap and the membership\n * stays in sync with the source of truth in `@glion/ack`.\n */\nconst KNOWN_ERROR_CODES: ReadonlySet<string> = new Set<string>(\n Object.values(Hl7ErrorCode)\n);\n\n/**\n * Set of severity codes (Table 0516) we recognise.\n */\nconst KNOWN_SEVERITIES: ReadonlySet<string> = new Set<string>(\n Object.values(Severity)\n);\n\n/**\n * Map an ERR-3 string to a typed {@link Hl7ErrorCodeValue}. Unknown or\n * missing values fall back to `ApplicationInternalError` (`207`) so the\n * exception always carries a well-defined code, mirroring receiver-side\n * conventions in `@glion/ack`.\n *\n * The widening cast is intentional and confined here — receivers\n * occasionally send vendor-specific codes outside Table 0357, and the\n * client must still produce a usable exception rather than reject the\n * NAK over an unknown code.\n */\nfunction coerceErrorCode(input: string | undefined): Hl7ErrorCodeValue {\n if (input !== undefined && KNOWN_ERROR_CODES.has(input)) {\n return input as Hl7ErrorCodeValue;\n }\n return Hl7ErrorCode.ApplicationInternalError;\n}\n\n/**\n * Map an ERR-4 string to a typed {@link SeverityValue}. Unknown or\n * missing values fall back to `Severity.Error` (`E`).\n */\nfunction coerceSeverity(input: string | undefined): SeverityValue {\n if (input !== undefined && KNOWN_SEVERITIES.has(input)) {\n return input as SeverityValue;\n }\n return Severity.Error;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Render an unknown thrown value as a human-readable string for error messages. */\nfunction formatCause(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","/**\n * `TransformStream<DecodedMessage, Acknowledgment>` that parses each\n * decoded MLLP frame as an HL7v2 acknowledgment. Internal to\n * `@glion/mllp-client`.\n *\n * On a malformed ACK the transform errors the readable with the\n * typed `MllpClientError(MALFORMED_ACK)` from {@link parseAck}.\n * Downstream `for await ... of` consumers reject with that error;\n * `MllpClient.send()` translates it at its single catch site.\n *\n * @module\n */\n\nimport type { DecodedMessage } from \"@glion/mllp-transport\";\n\nimport type { Acknowledgment } from \"../acknowledgment\";\nimport { parseAck } from \"../acknowledgment\";\n\n/**\n * Build a transform that consumes decoded MLLP frames and produces\n * parsed `Acknowledgment` objects. A parse failure errors the\n * readable side rather than enqueuing — the consumer's `for await`\n * loop rejects with the typed error.\n */\nexport function createAckParserStream(): TransformStream<\n DecodedMessage,\n Acknowledgment\n> {\n return new TransformStream<DecodedMessage, Acknowledgment>({\n transform(frame, controller) {\n try {\n controller.enqueue(parseAck(frame.text));\n } catch (error) {\n controller.error(error);\n }\n },\n });\n}\n","/**\n * Subscribe to an `AbortSignal` and run a callback once if it\n * aborts. Returns a disposer that unsubscribes — call it from a\n * `finally` block so we never leak listeners.\n *\n * If the signal is already aborted at subscription time, the\n * callback fires synchronously and the returned disposer is a\n * no-op. This collapses the \"aborted vs aborting\" branch every\n * caller would otherwise need to write by hand.\n *\n * Internal to `@glion/mllp-client`.\n *\n * @module\n */\n\n/**\n * Subscribe `onAbort` to fire once when `signal` aborts. Returns a\n * disposer that removes the listener.\n *\n * @example\n * ```ts\n * const dispose = subscribeAbort(signal, () => socket.destroy());\n * try {\n * await waitForReady(socket);\n * } finally {\n * dispose();\n * }\n * ```;\n */\nexport function subscribeAbort(\n signal: AbortSignal,\n onAbort: () => void\n): () => void {\n if (signal.aborted) {\n onAbort();\n return noop;\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n return () => signal.removeEventListener(\"abort\", onAbort);\n}\n\nfunction noop(): void {\n /* nothing to dispose */\n}\n","/**\n * Runtime-free core of `@glion/mllp-client`.\n *\n * This module owns {@link MllpClient}, the class application code\n * instantiates to send HL7v2 messages and receive acknowledgments.\n * The class is **transport-agnostic**: it composes a Web-Streams\n * pipeline (`socket → decoder → ack-parser`) and consumes parsed\n * acknowledgments from the readable side. It does **not** know how\n * to open a TCP/TLS connection. That concern is supplied by a\n * {@link MllpConnect} function passed in via\n * {@link MllpClientOptions.connect}. Per-runtime adapters\n * (`@glion/mllp-client/node`, `/deno`, `/workers`) ship pre-wired\n * `MllpClient` subclasses so the common case stays a single import.\n *\n * Each call to `client.send()` performs one short-lived round trip:\n *\n * ┌─────────────┐ ① connect ┌──────────┐\n * │ client │ ───────────────▶ │ duplex │\n * │ │ └────┬─────┘\n * │ │ ② write MLLP frame │\n * │ │ ─────────────────────▶│ <VT> ... <FS><CR>\n * │ │ │\n * │ │ ③ read frames via │\n * │ │ decoder + parser │\n * │ │ ◀─────────────────────│ <VT> ACK <FS><CR> ...\n * │ │ ┌────┴─────┐\n * │ │ ④ close + parse │ duplex │ closed\n * └─────────────┘ └──────────┘\n *\n * The four phases share a single deadline ({@link MllpClientOptions.timeout}).\n * If any phase exceeds the budget, the duplex is closed and a typed\n * `MllpClientError(TIMEOUT)` is thrown. Connections are deliberately\n * **not** pooled or reused — each `send()` opens its own duplex.\n *\n * @module\n */\n\nimport { AckException } from \"@glion/ack\";\nimport { createDecoderStream, encode } from \"@glion/mllp-transport\";\n\nimport type { Acknowledgment } from \"./acknowledgment\";\nimport { isFinalAckCode, throwOnNak } from \"./acknowledgment\";\nimport type { MllpConnect } from \"./connect\";\nimport { MllpClientError, MllpClientErrorCode } from \"./errors\";\nimport { createAckParserStream } from \"./internal/ack-parser-stream\";\nimport { subscribeAbort } from \"./internal/subscribe-abort\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * TLS configuration for an MLLP client connection.\n *\n * Mirrors the relevant subset of TLS options across runtimes. When\n * this object is provided to {@link MllpClientOptions.tls}, the\n * runtime adapter establishes a TLS connection (rather than plain\n * TCP). Server certificates are **always** verified unless\n * {@link insecure} is explicitly set to `true`.\n *\n * Not every runtime adapter supports every field — `passphrase` and\n * mutual-TLS material (`cert`/`key`) are honoured by the Node\n * adapter; Cloudflare Workers and Deno expose a smaller subset.\n * Adapters silently ignore fields they cannot use.\n */\nexport interface MllpClientTlsOptions {\n /** Trusted CA certificate(s) for verifying the server. */\n ca?: string | Uint8Array;\n /** Client certificate for mutual TLS. */\n cert?: string | Uint8Array;\n /** Client private key for mutual TLS. */\n key?: string | Uint8Array;\n /** Passphrase for the private key, if encrypted. */\n passphrase?: string;\n /**\n * Override the server name used for SNI and certificate\n * verification. Defaults to {@link MllpClientOptions.host}.\n *\n * Setting this to anything other than the real server hostname\n * effectively disables strict hostname verification — use only\n * when SNI multiplexing requires a different name from the\n * connection target.\n */\n servername?: string;\n /**\n * Disable server-certificate verification.\n *\n * **DO NOT enable this in production.** When set to `true` the\n * client accepts self-signed, expired, and otherwise invalid\n * certificates, making the connection vulnerable to active\n * man-in-the-middle attacks. The option exists so that local\n * development and integration tests can connect to throwaway\n * servers — never as a workaround for real certificate problems.\n *\n * Only the literal value `true` opts out. There is no `false`\n * form because the secure default is non-negotiable.\n */\n insecure?: true;\n}\n\n/**\n * Configuration for an {@link MllpClient}. All fields are immutable\n * once the client is constructed; create a new client to change\n * them.\n *\n * Application code that imports from `@glion/mllp-client/node`,\n * `/deno`, or `/workers` does **not** need to pass `connect` — the\n * runtime adapter provides it.\n */\nexport interface MllpClientOptions {\n /** Hostname or IP of the MLLP server. */\n host: string;\n /** TCP port of the MLLP server. */\n port: number;\n /**\n * Function that opens a connection to the receiver and returns a\n * Web-Streams duplex. Runtime adapters supply this for you.\n */\n connect: MllpConnect;\n /**\n * Maximum total milliseconds for the connect → write → read-ACK\n * round trip. The same budget covers all phases — if connecting\n * consumes 4s of a 10s budget, the remaining 6s is what's left to\n * receive the ACK.\n *\n * @default 30000\n */\n timeout?: number;\n /**\n * Maximum bytes accepted for an inbound ACK frame. When omitted,\n * no limit is enforced. Set this when receiving from untrusted\n * peers to cap memory use from a malicious or malformed stream.\n */\n maxAckSize?: number;\n /**\n * TLS configuration. **Defaults to `true`** — HL7v2 messages\n * commonly carry PHI, so the secure default is TLS-on. Callers\n * who genuinely want plain TCP (e.g. trusted hospital intranet,\n * or a TLS terminator hop in front of the receiver) must opt out\n * explicitly with `tls: false`.\n *\n * @example\n * ```ts\n * // TLS on, defaults (system trust, hostname verification)\n * new MllpClient({ host, port });\n *\n * // Same — explicit form\n * new MllpClient({ host, port, tls: true });\n *\n * // TLS with config (mutual TLS, custom CA, SNI override, ...)\n * new MllpClient({ host, port, tls: { servername: \"h\" } });\n *\n * // Plain TCP — caller takes the security trade-off explicitly\n * new MllpClient({ host, port, tls: false });\n * ```;\n *\n * @default true\n */\n tls?: boolean | MllpClientTlsOptions;\n}\n\n/**\n * Construction options accepted by the **runtime adapters'**\n * `MllpClient` subclasses (`@glion/mllp-client/node`, `/deno`,\n * `/workers`). Identical to {@link MllpClientOptions} except that\n * `connect` is omitted — the adapter binds it for you.\n */\nexport type BoundMllpClientOptions = Omit<MllpClientOptions, \"connect\">;\n\n/**\n * HL7v2 acknowledgment level the send waits for. Mirrors the two\n * acknowledgment levels in HL7v2 §2.9.2.\n *\n * - `\"OnApplication\"` (default) — return when the receiver's application-level\n * ACK arrives (`AA`/`AE`/`AR`). Intermediate `CA` (Commit Accept) frames sent\n * by enhanced-mode receivers are surfaced via iteration but do not resolve\n * the send. Use this when \"did the receiver successfully process my message?\"\n * is the question.\n * - `\"OnCommit\"` — return on the first frame regardless of code, typically a\n * commit-level ACK (`CA`/`CE`/`CR`). The send resolves as soon as the\n * receiver confirms receipt of the message; later processing-level frames are\n * not observed because the connection is closed once the resolving frame\n * arrives. Use this for receivers that only send commit-level ACKs (basic\n * mode) or when the caller wants the commit confirmation without waiting for\n * the processing result.\n */\nexport type SendMode = \"OnApplication\" | \"OnCommit\";\n\n/**\n * Per-call options accepted by {@link MllpClient.send}.\n */\nexport interface SendOptions {\n /**\n * Cancel the in-flight `send()` from outside. Composed with the\n * client's internal `timeout` deadline via `AbortSignal.any`, so\n * either source aborts the send.\n *\n * Typical use: tie a batch of `send()`s to an app-shutdown\n * `AbortController` so they all cancel cleanly when the process\n * exits.\n *\n * When this signal aborts, the resulting `MllpClientError` has\n * `code: TIMEOUT` and forwards the caller's `signal.reason` as\n * its `cause` (when the reason is itself an `Error`).\n */\n signal?: AbortSignal;\n /**\n * HL7v2 acknowledgment level to wait for. See {@link SendMode}.\n *\n * @default \"OnApplication\"\n */\n mode?: SendMode;\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\n/** Default `timeout` when {@link MllpClientOptions.timeout} is omitted. */\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n// ---------------------------------------------------------------------------\n// MllpClient\n// ---------------------------------------------------------------------------\n\n/**\n * Runtime-free MLLP client. Instantiate via a runtime adapter\n * (`@glion/mllp-client/node`, `/deno`, `/workers`) for the common\n * case.\n *\n * **TLS-on by default.** `MllpClientOptions.tls` defaults to `true`;\n * pass `tls: false` for plain TCP. See {@link MllpClientOptions.tls}.\n *\n * @example\n * Send a message and read the ACK on Node:\n *\n * ```ts\n * import { MllpClient } from \"@glion/mllp-client/node\";\n *\n * const client = new MllpClient({ host: \"127.0.0.1\", port: 2575 });\n * const ack = await client.send(rawHl7Message);\n * console.log(ack.code, ack.controlId); // \"AA\" \"MSG001\"\n * ```\n */\nexport class MllpClient {\n readonly #host: string;\n readonly #port: number;\n readonly #timeout: number;\n readonly #maxAckSize: number | undefined;\n readonly #tls: MllpClientTlsOptions | undefined;\n readonly #connect: MllpConnect;\n\n constructor(options: MllpClientOptions) {\n validateOptions(options);\n this.#host = options.host;\n this.#port = options.port;\n this.#connect = options.connect;\n this.#timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n this.#maxAckSize = options.maxAckSize;\n // Normalise the user-facing `boolean | TlsOptions | undefined` shape\n // down to `TlsOptions | undefined` so adapters only see the object\n // form. Default is TLS-on; only explicit `tls: false` opts out.\n if (options.tls === false) {\n this.#tls = undefined;\n } else if (options.tls === true || options.tls === undefined) {\n this.#tls = {};\n } else {\n this.#tls = options.tls;\n }\n }\n\n /** The host this client connects to. */\n get host(): string {\n return this.#host;\n }\n\n /** The port this client connects to. */\n get port(): number {\n return this.#port;\n }\n\n /**\n * Send a single HL7v2 message and resolve with the resolving accept ACK.\n *\n * Throws:\n *\n * - {@link MllpClientError} for transport failures. `code` identifies the mode:\n * `INVALID_INPUT`, `CONNECTION_REFUSED`, `TIMEOUT`, `CONNECTION_CLOSED`,\n * `MALFORMED_FRAME`, or `MALFORMED_ACK`.\n * - An `AckException` subclass from `@glion/ack` (`AckApplicationError`,\n * `AckApplicationReject`, `AckCommitError`, `AckCommitReject`) when MSA-1 ∈\n * {AE, AR, CE, CR}. The thrown exception's `raw` attribute holds the\n * wire-format ACK.\n *\n * To observe each accept ACK as it arrives (e.g. seeing the\n * intermediate `CA` frame in HL7v2 enhanced mode), use\n * {@link MllpClient.stream} instead.\n */\n async send(\n message: string | Uint8Array,\n options: SendOptions = {}\n ): Promise<Acknowledgment> {\n let last: Acknowledgment | undefined;\n for await (const ack of this.#exchange(message, options)) {\n last = ack;\n }\n if (!last) {\n // `#exchange` throws on every termination path before yielding,\n // so this branch should be unreachable. We keep it because the\n // alternative is a non-null assertion the type checker cannot\n // verify — surfacing a typed error if the generator ever breaks\n // its contract is cheaper than letting `undefined` propagate.\n throw new MllpClientError(\n MllpClientErrorCode.CONNECTION_CLOSED,\n \"Connection closed before a complete ACK was received\"\n );\n }\n return last;\n }\n\n /**\n * Send a single HL7v2 message and yield each accept ACK as it\n * arrives. The iterable completes after the resolving frame.\n *\n * In HL7v2 enhanced mode the iterable yields `CA` then the\n * application-level ACK; in basic mode it yields a single\n * application-level ACK. NAK codes (`AE`/`AR`/`CE`/`CR`) throw the\n * matching `AckException` from the iterator with the wire-format\n * ACK on `error.raw`.\n *\n * Use {@link MllpClient.send} for the simple \"give me the resolving\n * answer\" case.\n */\n stream(\n message: string | Uint8Array,\n options: SendOptions = {}\n ): AsyncIterable<Acknowledgment> {\n return this.#exchange(message, options);\n }\n\n /**\n * Internal exchange generator shared by {@link send} and {@link stream}.\n *\n * Owns the connect → write → read loop. NAK codes throw\n * `AckException` instead of yielding so both consumption shapes see\n * the same exception. Cleanup (socket teardown, abort\n * unsubscription) runs in the generator's `finally`, so a consumer\n * that breaks out of iteration still releases the socket.\n *\n * @yields Each accept ACK in arrival order. `return`s after the\n * resolving frame.\n */\n async *#exchange(\n message: string | Uint8Array,\n options: SendOptions\n ): AsyncGenerator<Acknowledgment, void, void> {\n // Encode eagerly so bad input fails fast as INVALID_INPUT, before\n // we open a connection.\n const frame = encodeOrThrow(message);\n const mode: SendMode = options.mode ?? \"OnApplication\";\n\n // Compose the deadline with the caller's signal. The runtime's\n // built-in `AbortSignal.timeout(ms)` uses an unref'd timer, so it\n // does not hold the event loop alive.\n const sources: AbortSignal[] = [AbortSignal.timeout(this.#timeout)];\n if (options.signal) {\n sources.push(options.signal);\n }\n const signal = AbortSignal.any(sources);\n\n const duplex = await this.#connect({\n host: this.#host,\n port: this.#port,\n signal,\n tls: this.#tls,\n });\n\n // Streams pipeline: socket bytes → MLLP frames → parsed ACKs. The\n // decoder runs in fatal mode (its `onError` throws), so a malformed\n // frame errors the readable; the parser stream errors the readable\n // on a malformed ACK. Both surface as `reader.read()` rejections.\n const ackStream = duplex.readable\n .pipeThrough(\n createDecoderStream({\n maxMessageSize: this.#maxAckSize,\n onError: (frameError) => {\n throw new MllpClientError(\n MllpClientErrorCode.MALFORMED_FRAME,\n `Invalid ACK frame: ${frameError.message}`,\n { cause: frameError }\n );\n },\n })\n )\n .pipeThrough(createAckParserStream());\n\n const reader = ackStream.getReader();\n const writer = duplex.writable.getWriter();\n\n // Wire the abort signal to interrupt pending I/O by tearing down\n // the underlying transport. `duplex.close()` destroys the socket;\n // that propagates through the streams and the pending\n // `reader.read()` resolves (done) or rejects (errored). The\n // generator's catch turns whichever outcome it is into the typed\n // `MllpClientError` via `normaliseSendError`. The abort path\n // fires-and-forgets the close — we don't need to await teardown\n // before the abort signal's listeners settle. Adapters MUST resolve\n // (never reject) from `close()` per the `MllpDuplexStream.close`\n // contract.\n const unsubscribe = subscribeAbort(signal, () => {\n void duplex.close();\n });\n\n try {\n await writer.write(frame);\n\n while (true) {\n const { done, value: ack } = await reader.read();\n if (done) {\n throw new MllpClientError(\n MllpClientErrorCode.CONNECTION_CLOSED,\n \"Connection closed before a complete ACK was received\"\n );\n }\n // NAK codes (AE/AR/CE/CR) throw the matching `AckException`;\n // the throw flies out of the generator and surfaces as the\n // consumer's rejection (whether they are `await`ing the\n // resolving frame in `send` or iterating via `stream`).\n throwOnNak(ack);\n yield ack;\n if (mode === \"OnCommit\" || isFinalAckCode(ack.code)) {\n return;\n }\n }\n } catch (error) {\n // `AckException` is the receiver's NAK — a successful exchange\n // with a negative result. Pass it through unchanged rather than\n // wrapping as a transport error.\n if (error instanceof AckException) {\n throw error;\n }\n throw normaliseSendError(error, signal, this.#timeout);\n } finally {\n unsubscribe();\n await duplex.close();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * MLLP-encode a payload, mapping any failure to a typed\n * {@link MllpClientError} with code `INVALID_INPUT`.\n */\nfunction encodeOrThrow(message: string | Uint8Array): Uint8Array {\n try {\n return encode(message);\n } catch (error) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n `Invalid message payload: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error instanceof Error ? error : undefined }\n );\n }\n}\n\n/**\n * Translate a thrown value from any phase of {@link readAck} into a\n * typed client error.\n *\n * Precedence:\n *\n * 1. If the signal aborted with a typed {@link MllpClientError} reason, surface it\n * verbatim. (Connector or pipeline-internal failures keep their original\n * code.)\n * 2. Else if the signal aborted with the standard timeout `DOMException` produced\n * by `AbortSignal.timeout(ms)`, wrap as `TIMEOUT` with the original budget\n * in the message.\n * 3. Else if the signal aborted with any other reason (a caller's own\n * `AbortController.abort(reason)`), wrap as `TIMEOUT` with the caller's\n * reason chained as `cause` — caller cancellation conceptually IS a timeout\n * from the protocol's perspective.\n * 4. Else if the underlying stream rejection is already typed, return it unchanged\n * so adapter-specific failures are preserved.\n * 5. Otherwise wrap as `CONNECTION_CLOSED`.\n */\nfunction normaliseSendError(\n error: unknown,\n signal: AbortSignal,\n timeoutMs: number\n): MllpClientError {\n if (signal.aborted) {\n if (signal.reason instanceof MllpClientError) {\n return signal.reason;\n }\n if (isTimeoutAbort(signal.reason)) {\n return new MllpClientError(\n MllpClientErrorCode.TIMEOUT,\n `MLLP round trip exceeded ${timeoutMs}ms`\n );\n }\n return new MllpClientError(\n MllpClientErrorCode.TIMEOUT,\n \"Send aborted by caller\",\n {\n cause: signal.reason instanceof Error ? signal.reason : undefined,\n }\n );\n }\n if (error instanceof MllpClientError) {\n return error;\n }\n const wrapped = error instanceof Error ? error : new Error(String(error));\n return new MllpClientError(\n MllpClientErrorCode.CONNECTION_CLOSED,\n `Connection failed during MLLP exchange: ${wrapped.message}`,\n { cause: wrapped }\n );\n}\n\n/**\n * Detect the standard `DOMException` produced by\n * `AbortSignal.timeout(ms)` when the timer fires. The runtime\n * convention is `name: \"TimeoutError\"` — duck-typed because\n * `DOMException` exists in Node, Bun, Deno, and Workers but tests\n * run in environments where the constructor identity may differ.\n */\nfunction isTimeoutAbort(reason: unknown): boolean {\n return (\n reason !== null &&\n typeof reason === \"object\" &&\n (reason as { name?: unknown }).name === \"TimeoutError\"\n );\n}\n\n/**\n * Validate {@link MllpClientOptions} at construction time. Catches\n * the sharp edges that would otherwise surface as confusing runtime\n * symptoms.\n */\nfunction validateOptions(options: MllpClientOptions): void {\n if (typeof options.host !== \"string\" || options.host.length === 0) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n \"MllpClientOptions.host must be a non-empty string\"\n );\n }\n if (\n !Number.isInteger(options.port) ||\n options.port < 1 ||\n options.port > 65_535\n ) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n `MllpClientOptions.port must be an integer in 1..65535, got ${options.port}`\n );\n }\n if (typeof options.connect !== \"function\") {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n \"MllpClientOptions.connect must be a function — import the adapter for your runtime (`@glion/mllp-client/node`, `/deno`, `/workers`)\"\n );\n }\n if (\n options.timeout !== undefined &&\n (!Number.isFinite(options.timeout) || options.timeout <= 0)\n ) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n `MllpClientOptions.timeout must be a positive finite number of milliseconds, got ${options.timeout}`\n );\n }\n if (\n options.maxAckSize !== undefined &&\n (!Number.isInteger(options.maxAckSize) || options.maxAckSize <= 0)\n ) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n `MllpClientOptions.maxAckSize must be a positive integer, got ${options.maxAckSize}`\n );\n }\n if (\n options.tls !== undefined &&\n typeof options.tls !== \"boolean\" &&\n (typeof options.tls !== \"object\" || options.tls === null)\n ) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n \"MllpClientOptions.tls must be `true`, `false`, or an MllpClientTlsOptions object\"\n );\n }\n}\n"],"mappings":"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"}
@@ -1,103 +0,0 @@
1
- /**
2
- * Acknowledgment parsing for the MLLP client.
3
- *
4
- * This module owns three things:
5
- *
6
- * 1. The {@link Acknowledgment} interface — a structured view of an ACK message
7
- * exposing the fields callers most often need (MSA-1/2/3, ERR-3/4) without
8
- * forcing them to walk the AST themselves.
9
- * 2. {@link parseAck} — turns a raw wire-format ACK string into an
10
- * {@link Acknowledgment}, or throws {@link MllpClientError} when the payload
11
- * is not a recognisable ACK.
12
- * 3. {@link throwOnNak} — inspects an {@link Acknowledgment} and throws the
13
- * matching `AckException` subclass when MSA-1 indicates a NAK.
14
- *
15
- * Both helpers are internal to the package — only the {@link Acknowledgment}
16
- * type is re-exported from `@glion/mllp-client`. Application code interacts
17
- * with parsed acknowledgments through the return value (and thrown errors)
18
- * of `MllpClient.send()`.
19
- *
20
- * The HL7v2 ACK structure used here (Section 2.9.2 of the HL7v2 standard):
21
- *
22
- * MSH | header (sender, receiver, timestamp, message control id) MSA |
23
- * acknowledgment code (MSA-1), original control id (MSA-2), text (MSA-3) ERR |
24
- * optional error segment with HL7 condition code (ERR-3) and severity (ERR-4)
25
- *
26
- * @module
27
- */
28
- import type { AckCodeValue } from "@glion/ack";
29
- import type { Root } from "@glion/ast";
30
- /**
31
- * An HL7v2 acknowledgment message returned by an MLLP receiver, with
32
- * structured access to the fields callers most often need.
33
- *
34
- * Returned by `MllpClient.send()` on AA/CA. On NAK (AE/AR/CE/CR) an
35
- * `AckException` is thrown instead — the same shape is available there
36
- * via `error.message` (MSA-3), `error.errorCode` (ERR-3), `error.severity`
37
- * (ERR-4), and `error.raw` (the wire ACK).
38
- */
39
- export interface Acknowledgment {
40
- /** The raw HL7v2 ACK message as received from the wire. */
41
- readonly raw: string;
42
- /**
43
- * The full ACK message AST, typed against `@glion/ast`. Use for
44
- * advanced inspection beyond the structured fields below; consumers
45
- * that walk the tree should install `@glion/ast` for the type.
46
- */
47
- readonly tree: Root;
48
- /**
49
- * MSA-1 acknowledgment code.
50
- *
51
- * Typed as the standard {@link AckCodeValue} union (`AA`, `AE`, `AR`,
52
- * `CA`, `CE`, `CR`) widened with an opaque `string` escape hatch — the
53
- * `(string & {})` form preserves auto-completion for the standard
54
- * codes while still accepting vendor-specific values that real
55
- * receivers occasionally send.
56
- */
57
- readonly code: AckCodeValue | (string & {});
58
- /** MSA-2 message control ID — references the original message's MSH-10. */
59
- readonly controlId: string;
60
- /** MSA-3 text message. Typically populated on NAK; optional on accept ACKs. */
61
- readonly textMessage: string | undefined;
62
- /**
63
- * ERR-3 HL7 error condition code (Table 0357). Present when an ERR segment is
64
- * included.
65
- */
66
- readonly errorCode: string | undefined;
67
- /** ERR-4 severity (Table 0516). Present when an ERR segment is included. */
68
- readonly severity: string | undefined;
69
- }
70
- /**
71
- * Returns `true` for any acknowledgment code that should terminate the
72
- * client's read loop. Intermediate codes (CA) return `false`; every
73
- * other code — including vendor-specific values — is treated as final
74
- * to avoid hanging on responses we do not recognise.
75
- */
76
- export declare function isFinalAckCode(code: string): boolean;
77
- /**
78
- * Parse a raw ACK frame string into an {@link Acknowledgment}.
79
- *
80
- * Field extraction is delegated to `@glion/util-query`'s `value()` helper —
81
- * this is the same path used elsewhere in the Glion stack and avoids
82
- * hand-rolling AST traversal here.
83
- *
84
- * Throws `MllpClientError(MALFORMED_ACK)` when:
85
- *
86
- * - The parser fails to produce a tree (malformed HL7v2), or
87
- * - MSA-1 is missing (the message is not a valid acknowledgment).
88
- *
89
- * Other missing fields (MSA-3, ERR-3, ERR-4) are not fatal — they are
90
- * exposed as `undefined` because real-world ACKs frequently omit them.
91
- */
92
- export declare function parseAck(raw: string): Acknowledgment;
93
- /**
94
- * Inspect an {@link Acknowledgment} and throw the matching `AckException`
95
- * subclass when MSA-1 indicates a NAK. Returns the acknowledgment unchanged
96
- * on AA/CA.
97
- *
98
- * The thrown exception mirrors the receiver-side exception types — a
99
- * caller that already handles `AckApplicationError` from a server's
100
- * middleware can reuse the same `catch` blocks here.
101
- */
102
- export declare function throwOnNak(ack: Acknowledgment): Acknowledgment;
103
- //# sourceMappingURL=acknowledgment.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"acknowledgment.d.ts","sourceRoot":"","sources":["../../src/core/acknowledgment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,OAAO,KAAK,EACV,YAAY,EAKb,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAUvC;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB;;;;;;;;OAQG;IAEH,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAgCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAmBpD;AAuBD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,cAAc,CAK9D"}
@@ -1,256 +0,0 @@
1
- /**
2
- * Runtime-free core of `@glion/mllp-client`.
3
- *
4
- * This module owns {@link MllpClient}, the class application code
5
- * instantiates to send HL7v2 messages and receive acknowledgments.
6
- * The class is **transport-agnostic**: it composes a Web-Streams
7
- * pipeline (`socket → decoder → ack-parser`) and consumes parsed
8
- * acknowledgments from the readable side. It does **not** know how
9
- * to open a TCP/TLS connection. That concern is supplied by a
10
- * {@link MllpConnect} function passed in via
11
- * {@link MllpClientOptions.connect}. Per-runtime adapters
12
- * (`@glion/mllp-client/node`, `/deno`, `/workers`) ship pre-wired
13
- * `MllpClient` subclasses so the common case stays a single import.
14
- *
15
- * Each call to `client.send()` performs one short-lived round trip:
16
- *
17
- * ┌─────────────┐ ① connect ┌──────────┐
18
- * │ client │ ───────────────▶ │ duplex │
19
- * │ │ └────┬─────┘
20
- * │ │ ② write MLLP frame │
21
- * │ │ ─────────────────────▶│ <VT> ... <FS><CR>
22
- * │ │ │
23
- * │ │ ③ read frames via │
24
- * │ │ decoder + parser │
25
- * │ │ ◀─────────────────────│ <VT> ACK <FS><CR> ...
26
- * │ │ ┌────┴─────┐
27
- * │ │ ④ close + parse │ duplex │ closed
28
- * └─────────────┘ └──────────┘
29
- *
30
- * The four phases share a single deadline ({@link MllpClientOptions.timeout}).
31
- * If any phase exceeds the budget, the duplex is closed and a typed
32
- * `MllpClientError(TIMEOUT)` is thrown. Connections are deliberately
33
- * **not** pooled or reused — each `send()` opens its own duplex.
34
- *
35
- * @module
36
- */
37
- import type { Acknowledgment } from "./acknowledgment";
38
- import type { MllpConnect } from "./connect";
39
- /**
40
- * TLS configuration for an MLLP client connection.
41
- *
42
- * Mirrors the relevant subset of TLS options across runtimes. When
43
- * this object is provided to {@link MllpClientOptions.tls}, the
44
- * runtime adapter establishes a TLS connection (rather than plain
45
- * TCP). Server certificates are **always** verified unless
46
- * {@link insecure} is explicitly set to `true`.
47
- *
48
- * Not every runtime adapter supports every field — `passphrase` and
49
- * mutual-TLS material (`cert`/`key`) are honoured by the Node
50
- * adapter; Cloudflare Workers and Deno expose a smaller subset.
51
- * Adapters silently ignore fields they cannot use.
52
- */
53
- export interface MllpClientTlsOptions {
54
- /** Trusted CA certificate(s) for verifying the server. */
55
- ca?: string | Uint8Array;
56
- /** Client certificate for mutual TLS. */
57
- cert?: string | Uint8Array;
58
- /** Client private key for mutual TLS. */
59
- key?: string | Uint8Array;
60
- /** Passphrase for the private key, if encrypted. */
61
- passphrase?: string;
62
- /**
63
- * Override the server name used for SNI and certificate
64
- * verification. Defaults to {@link MllpClientOptions.host}.
65
- *
66
- * Setting this to anything other than the real server hostname
67
- * effectively disables strict hostname verification — use only
68
- * when SNI multiplexing requires a different name from the
69
- * connection target.
70
- */
71
- servername?: string;
72
- /**
73
- * Disable server-certificate verification.
74
- *
75
- * **DO NOT enable this in production.** When set to `true` the
76
- * client accepts self-signed, expired, and otherwise invalid
77
- * certificates, making the connection vulnerable to active
78
- * man-in-the-middle attacks. The option exists so that local
79
- * development and integration tests can connect to throwaway
80
- * servers — never as a workaround for real certificate problems.
81
- *
82
- * Only the literal value `true` opts out. There is no `false`
83
- * form because the secure default is non-negotiable.
84
- */
85
- insecure?: true;
86
- }
87
- /**
88
- * Configuration for an {@link MllpClient}. All fields are immutable
89
- * once the client is constructed; create a new client to change
90
- * them.
91
- *
92
- * Application code that imports from `@glion/mllp-client/node`,
93
- * `/deno`, or `/workers` does **not** need to pass `connect` — the
94
- * runtime adapter provides it.
95
- */
96
- export interface MllpClientOptions {
97
- /** Hostname or IP of the MLLP server. */
98
- host: string;
99
- /** TCP port of the MLLP server. */
100
- port: number;
101
- /**
102
- * Function that opens a connection to the receiver and returns a
103
- * Web-Streams duplex. Runtime adapters supply this for you.
104
- */
105
- connect: MllpConnect;
106
- /**
107
- * Maximum total milliseconds for the connect → write → read-ACK
108
- * round trip. The same budget covers all phases — if connecting
109
- * consumes 4s of a 10s budget, the remaining 6s is what's left to
110
- * receive the ACK.
111
- *
112
- * @default 30000
113
- */
114
- timeout?: number;
115
- /**
116
- * Maximum bytes accepted for an inbound ACK frame. When omitted,
117
- * no limit is enforced. Set this when receiving from untrusted
118
- * peers to cap memory use from a malicious or malformed stream.
119
- */
120
- maxAckSize?: number;
121
- /**
122
- * TLS configuration. **Defaults to `true`** — HL7v2 messages
123
- * commonly carry PHI, so the secure default is TLS-on. Callers
124
- * who genuinely want plain TCP (e.g. trusted hospital intranet,
125
- * or a TLS terminator hop in front of the receiver) must opt out
126
- * explicitly with `tls: false`.
127
- *
128
- * @example
129
- * ```ts
130
- * // TLS on, defaults (system trust, hostname verification)
131
- * new MllpClient({ host, port });
132
- *
133
- * // Same — explicit form
134
- * new MllpClient({ host, port, tls: true });
135
- *
136
- * // TLS with config (mutual TLS, custom CA, SNI override, ...)
137
- * new MllpClient({ host, port, tls: { servername: "h" } });
138
- *
139
- * // Plain TCP — caller takes the security trade-off explicitly
140
- * new MllpClient({ host, port, tls: false });
141
- * ```;
142
- *
143
- * @default true
144
- */
145
- tls?: boolean | MllpClientTlsOptions;
146
- }
147
- /**
148
- * Construction options accepted by the **runtime adapters'**
149
- * `MllpClient` subclasses (`@glion/mllp-client/node`, `/deno`,
150
- * `/workers`). Identical to {@link MllpClientOptions} except that
151
- * `connect` is omitted — the adapter binds it for you.
152
- */
153
- export type BoundMllpClientOptions = Omit<MllpClientOptions, "connect">;
154
- /**
155
- * HL7v2 acknowledgment level the send waits for. Mirrors the two
156
- * acknowledgment levels in HL7v2 §2.9.2.
157
- *
158
- * - `"OnApplication"` (default) — return when the receiver's application-level
159
- * ACK arrives (`AA`/`AE`/`AR`). Intermediate `CA` (Commit Accept) frames sent
160
- * by enhanced-mode receivers are surfaced via iteration but do not resolve
161
- * the send. Use this when "did the receiver successfully process my message?"
162
- * is the question.
163
- * - `"OnCommit"` — return on the first frame regardless of code, typically a
164
- * commit-level ACK (`CA`/`CE`/`CR`). The send resolves as soon as the
165
- * receiver confirms receipt of the message; later processing-level frames are
166
- * not observed because the connection is closed once the resolving frame
167
- * arrives. Use this for receivers that only send commit-level ACKs (basic
168
- * mode) or when the caller wants the commit confirmation without waiting for
169
- * the processing result.
170
- */
171
- export type SendMode = "OnApplication" | "OnCommit";
172
- /**
173
- * Per-call options accepted by {@link MllpClient.send}.
174
- */
175
- export interface SendOptions {
176
- /**
177
- * Cancel the in-flight `send()` from outside. Composed with the
178
- * client's internal `timeout` deadline via `AbortSignal.any`, so
179
- * either source aborts the send.
180
- *
181
- * Typical use: tie a batch of `send()`s to an app-shutdown
182
- * `AbortController` so they all cancel cleanly when the process
183
- * exits.
184
- *
185
- * When this signal aborts, the resulting `MllpClientError` has
186
- * `code: TIMEOUT` and forwards the caller's `signal.reason` as
187
- * its `cause` (when the reason is itself an `Error`).
188
- */
189
- signal?: AbortSignal;
190
- /**
191
- * HL7v2 acknowledgment level to wait for. See {@link SendMode}.
192
- *
193
- * @default "OnApplication"
194
- */
195
- mode?: SendMode;
196
- }
197
- /**
198
- * Runtime-free MLLP client. Instantiate via a runtime adapter
199
- * (`@glion/mllp-client/node`, `/deno`, `/workers`) for the common
200
- * case.
201
- *
202
- * **TLS-on by default.** `MllpClientOptions.tls` defaults to `true`;
203
- * pass `tls: false` for plain TCP. See {@link MllpClientOptions.tls}.
204
- *
205
- * @example
206
- * Send a message and read the ACK on Node:
207
- *
208
- * ```ts
209
- * import { MllpClient } from "@glion/mllp-client/node";
210
- *
211
- * const client = new MllpClient({ host: "127.0.0.1", port: 2575 });
212
- * const ack = await client.send(rawHl7Message);
213
- * console.log(ack.code, ack.controlId); // "AA" "MSG001"
214
- * ```
215
- */
216
- export declare class MllpClient {
217
- #private;
218
- constructor(options: MllpClientOptions);
219
- /** The host this client connects to. */
220
- get host(): string;
221
- /** The port this client connects to. */
222
- get port(): number;
223
- /**
224
- * Send a single HL7v2 message and resolve with the resolving accept ACK.
225
- *
226
- * Throws:
227
- *
228
- * - {@link MllpClientError} for transport failures. `code` identifies the mode:
229
- * `INVALID_INPUT`, `CONNECTION_REFUSED`, `TIMEOUT`, `CONNECTION_CLOSED`,
230
- * `MALFORMED_FRAME`, or `MALFORMED_ACK`.
231
- * - An `AckException` subclass from `@glion/ack` (`AckApplicationError`,
232
- * `AckApplicationReject`, `AckCommitError`, `AckCommitReject`) when MSA-1 ∈
233
- * {AE, AR, CE, CR}. The thrown exception's `raw` attribute holds the
234
- * wire-format ACK.
235
- *
236
- * To observe each accept ACK as it arrives (e.g. seeing the
237
- * intermediate `CA` frame in HL7v2 enhanced mode), use
238
- * {@link MllpClient.stream} instead.
239
- */
240
- send(message: string | Uint8Array, options?: SendOptions): Promise<Acknowledgment>;
241
- /**
242
- * Send a single HL7v2 message and yield each accept ACK as it
243
- * arrives. The iterable completes after the resolving frame.
244
- *
245
- * In HL7v2 enhanced mode the iterable yields `CA` then the
246
- * application-level ACK; in basic mode it yields a single
247
- * application-level ACK. NAK codes (`AE`/`AR`/`CE`/`CR`) throw the
248
- * matching `AckException` from the iterator with the wire-format
249
- * ACK on `error.raw`.
250
- *
251
- * Use {@link MllpClient.send} for the simple "give me the resolving
252
- * answer" case.
253
- */
254
- stream(message: string | Uint8Array, options?: SendOptions): AsyncIterable<Acknowledgment>;
255
- }
256
- //# sourceMappingURL=client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAS7C;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IACzB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC3B,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,OAAO,EAAE,WAAW,CAAC;IACrB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,GAAG,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACtC;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;AAExE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG,UAAU,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAaD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,UAAU;;gBAQT,OAAO,EAAE,iBAAiB;IAmBtC,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,IAAI,CACR,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,cAAc,CAAC;IAmB1B;;;;;;;;;;;;OAYG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,OAAO,GAAE,WAAgB,GACxB,aAAa,CAAC,cAAc,CAAC;CA+GjC"}
@@ -1,101 +0,0 @@
1
- /**
2
- * Public types for the runtime-free core of `@glion/mllp-client`.
3
- *
4
- * These types describe the shape of the pluggable transport that the
5
- * client uses to read and write bytes on the wire. The core does not
6
- * import any runtime-specific module — every connection is opened by
7
- * a {@link MllpConnect} function the caller supplies. Per-runtime
8
- * adapters (`@glion/mllp-client/node`, `/deno`, `/workers`) ship
9
- * pre-wired implementations so most callers never write a `connect`
10
- * function themselves.
11
- *
12
- * @module
13
- */
14
- import type { MllpClientTlsOptions } from "./client";
15
- /**
16
- * Connect to an MLLP receiver and return a {@link MllpDuplexStream}
17
- * over which the client will write the framed message and read the
18
- * acknowledgment.
19
- *
20
- * Implementations must:
21
- *
22
- * - Resolve once the underlying transport is ready for I/O (TCP `connect` has
23
- * fired, or the TLS handshake has completed when `tls` is set).
24
- * - Reject with a thrown `Error` if the connection cannot be established. The
25
- * client maps native errors into typed `MllpClientError` instances.
26
- * - Honour `signal` if the caller passes one — abort the connect attempt and
27
- * reject when the signal aborts. Future revisions of the client may pass a
28
- * signal here to enforce the connect-phase deadline.
29
- *
30
- * The returned duplex must remain usable until {@link MllpDuplexStream.close}
31
- * is called.
32
- */
33
- export type MllpConnect = (params: MllpConnectParams) => Promise<MllpDuplexStream>;
34
- /**
35
- * Parameters supplied to a {@link MllpConnect} implementation.
36
- */
37
- export interface MllpConnectParams {
38
- /** Hostname or IP of the receiver. */
39
- host: string;
40
- /** TCP port of the receiver. */
41
- port: number;
42
- /**
43
- * TLS configuration. When set, the connector should establish a TLS
44
- * connection (rather than plain TCP).
45
- */
46
- tls?: MllpClientTlsOptions;
47
- /**
48
- * Optional abort signal. Implementations should listen for `abort`
49
- * and tear the partial connection down so that the client's deadline
50
- * fires cleanly.
51
- */
52
- signal?: AbortSignal;
53
- }
54
- /**
55
- * A Web-Streams duplex over which the client reads and writes bytes.
56
- *
57
- * The runtime adapters wrap their native socket types into this shape.
58
- * The core has no awareness of the underlying transport — TCP, TLS,
59
- * a WebSocket bridge, or an in-memory pipe (for tests) all work
60
- * provided they expose Web Streams.
61
- */
62
- export interface MllpDuplexStream {
63
- /** Inbound byte stream from the receiver. */
64
- readonly readable: ReadableStream<Uint8Array>;
65
- /** Outbound byte stream to the receiver. */
66
- readonly writable: WritableStream<Uint8Array>;
67
- /**
68
- * Tear the connection down. The client calls `close()` both from
69
- * the abort signal handler (to interrupt pending I/O) and from a
70
- * `finally` block (final cleanup).
71
- *
72
- * Implementations **must**:
73
- *
74
- * - Be **idempotent** — `close()` may be called multiple times on the same
75
- * duplex (typically twice: once from the abort handler when the signal
76
- * fires, once from the `finally` block as the generator unwinds).
77
- * - **Resolve, never reject.** The client does **not** wrap `close()` in
78
- * `try/catch` — error handling for cleanup belongs at the adapter layer,
79
- * where each runtime knows what its socket primitives can fail with. A
80
- * rejection from the abort handler would surface as an unhandled promise
81
- * rejection; a rejection from the `finally` block would replace any
82
- * in-flight error (NAK exception, transport error) with a confusing
83
- * close-time error. Adapters must absorb their own idempotency and teardown
84
- * errors internally and resolve cleanly.
85
- * - **Propagate the close into the readable side** so that any pending
86
- * `reader.read()` settles. Real sockets do this naturally via the
87
- * underlying transport's close. Test fakes must close their readable
88
- * controller from inside `close()` to honour the same contract.
89
- *
90
- * The return type is `Promise<void>` so that adapters whose underlying
91
- * transport has async destruction (e.g. Cloudflare Workers'
92
- * `socket.close()`) can model it honestly. Adapters with sync teardown
93
- * (e.g. Node's `socket.destroy()`) wrap as a trivially-resolving async
94
- * function. The client awaits `close()` in `finally` blocks so resource
95
- * release is observable; the abort handler fires-and-forgets via
96
- * `void duplex.close()` since the abort path does not need to await
97
- * teardown.
98
- */
99
- close(): Promise<void>;
100
- }
101
- //# sourceMappingURL=connect.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/core/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAErD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,iBAAiB,KACtB,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAE/B;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,GAAG,CAAC,EAAE,oBAAoB,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}