@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["#listeners","#socket","#connect","#policy","#sendTimeoutMs","#state","#dial","#send","#closed","#failed","#exchange","#readAcknowledgment","#fail"],"sources":["../src/constants.ts","../src/errors/base.ts","../src/errors/mllp-already-sending-error.ts","../src/errors/mllp-client-closed-error.ts","../src/errors/mllp-connection-error.ts","../src/errors/mllp-connection-failed-error.ts","../src/errors/mllp-connection-timeout-error.ts","../src/errors/mllp-connection-lost-error.ts","../src/errors/mllp-invalid-message-error.ts","../src/errors/mllp-invalid-option-error.ts","../src/errors/mllp-invalid-response-error.ts","../src/utils.ts","../src/errors/nak-exception.ts","../src/errors/mllp-send-aborted-error.ts","../src/errors/mllp-send-timeout-error.ts","../src/assertions.ts","../src/events.ts","../src/messages.ts","../src/reconnect.ts","../src/session.ts","../src/client.ts"],"sourcesContent":["/**\n * The bounds and defaults the client is configured by. Constants only.\n *\n * @module\n */\n\n/** Default time to wait for a connection to open, in milliseconds. */\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;\n\n/** Default time to wait for an acknowledgment after sending, in milliseconds. */\nexport const DEFAULT_SEND_TIMEOUT_MS = 30_000;\n\n/** Default maximum bytes buffered while receiving one message (16 MiB). */\nexport const DEFAULT_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\n\n/**\n * Default number of reconnect attempts after a failed one. With the default\n * delay, the last one starts about 30 seconds after the first failure.\n */\nexport const DEFAULT_RECONNECT_ATTEMPTS = 5;\n\n/**\n * Maximum timeout `setTimeout` supports, in milliseconds (2^31 − 1). Larger\n * values, including `Infinity`, are rejected: the platform would clamp them\n * to about 1 ms.\n */\nexport const MAX_TIMEOUT_MS = 2 ** 31 - 1;\n","/**\n * The base every client error extends, and what each one carries: a fixed\n * `code`, a fixed message, what became of the message, and the lower layer's\n * failure on `cause`.\n *\n * @module\n */\n\nexport const MllpErrorCode = {\n ALREADY_SENDING: \"ALREADY_SENDING\",\n CLOSED: \"CLOSED\",\n CONNECTION_FAILED: \"CONNECTION_FAILED\",\n CONNECTION_LOST: \"CONNECTION_LOST\",\n CONNECTION_TIMEOUT: \"CONNECTION_TIMEOUT\",\n INVALID_MESSAGE: \"INVALID_MESSAGE\",\n INVALID_OPTION: \"INVALID_OPTION\",\n INVALID_RESPONSE: \"INVALID_RESPONSE\",\n SEND_ABORTED: \"SEND_ABORTED\",\n SEND_TIMEOUT: \"SEND_TIMEOUT\",\n} as const;\n\nexport type MllpErrorCode = (typeof MllpErrorCode)[keyof typeof MllpErrorCode];\n\n/**\n * What became of the message. `not-sent`: nothing reached the wire, and the\n * message may be sent again as it is. `unknown`: it may have reached the\n * remote system, and sending it again may deliver it twice.\n */\nexport type MllpDelivery = \"not-sent\" | \"unknown\";\n\nexport abstract class MllpClientError extends Error {\n abstract readonly code: MllpErrorCode;\n abstract readonly delivery: MllpDelivery;\n}\n","import { MllpClientError, MllpErrorCode } from \"./base\";\n\n/**\n * `send()` was called while another message was still waiting for its\n * acknowledgment. The client sends one message at a time.\n */\nexport class MllpAlreadySendingError extends MllpClientError {\n override readonly name = \"MllpAlreadySendingError\";\n readonly code = MllpErrorCode.ALREADY_SENDING;\n readonly delivery = \"not-sent\";\n /** MSH-10 of the message that is still waiting. */\n readonly controlId: string;\n\n constructor(controlId: string) {\n super(\n \"Cannot send: another message is still waiting for its acknowledgment — await the in-flight send() first.\"\n );\n this.controlId = controlId;\n }\n}\n","import { MllpClientError, MllpErrorCode } from \"./base\";\n\n/**\n * The call cannot be served: the client is closed, by `close()`, by\n * `destroy()`, or by a lost connection the reconnect policy did not restore.\n * That last failure is on `cause`.\n */\nexport class MllpClientClosedError extends MllpClientError {\n override readonly name = \"MllpClientClosedError\";\n readonly code = MllpErrorCode.CLOSED;\n readonly delivery = \"not-sent\";\n\n constructor(cause: MllpClientError | null = null) {\n super(\n cause === null\n ? \"The client is closed — construct a new MllpClient to send again.\"\n : \"The client closed after its connection could not be restored — construct a new MllpClient to send again.\",\n { cause: cause ?? undefined }\n );\n }\n}\n","import { MllpClientError } from \"./base\";\n\n/**\n * A failure of the wire: the connection could not be opened, gave no reply in\n * time, or was lost. Raised by the layer that holds the socket; the client\n * relays it unchanged.\n */\nexport abstract class MllpConnectionError extends MllpClientError {}\n","import { MllpErrorCode } from \"./base\";\nimport { MllpConnectionError } from \"./mllp-connection-error\";\n\n/**\n * The connection could not be opened: the host was unreachable, refused the\n * connection, or failed DNS or TLS. The socket's error is on `cause`.\n */\nexport class MllpConnectionFailedError extends MllpConnectionError {\n override readonly name = \"MllpConnectionFailedError\";\n readonly code = MllpErrorCode.CONNECTION_FAILED;\n readonly delivery = \"not-sent\";\n\n constructor(cause: unknown) {\n super(\n \"Connecting failed — check that the host is reachable and the port is listening.\",\n { cause }\n );\n }\n}\n","import { MllpErrorCode } from \"./base\";\nimport { MllpConnectionError } from \"./mllp-connection-error\";\n\n/**\n * The remote system did not accept the connection within `connectTimeoutMs`.\n * The host may be down, overloaded, or silently dropping packets.\n */\nexport class MllpConnectionTimeoutError extends MllpConnectionError {\n override readonly name = \"MllpConnectionTimeoutError\";\n readonly code = MllpErrorCode.CONNECTION_TIMEOUT;\n readonly delivery = \"not-sent\";\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(\n \"Connecting timed out — check that the host is reachable and the port is listening.\"\n );\n this.timeoutMs = timeoutMs;\n }\n}\n","import { MllpErrorCode } from \"./base\";\nimport { MllpConnectionError } from \"./mllp-connection-error\";\n\n/**\n * The connection was lost during a send: the remote system hung up, or the\n * network broke. The remote system may or may not have received the message.\n * A stream error, when there was one, is on `cause`.\n */\nexport class MllpConnectionLostError extends MllpConnectionError {\n override readonly name = \"MllpConnectionLostError\";\n readonly code = MllpErrorCode.CONNECTION_LOST;\n readonly delivery = \"unknown\";\n\n constructor(cause?: unknown) {\n super(\n \"The connection was lost while a message was waiting for its acknowledgment — the remote system may or may not have received it; resend only if the message is safe to repeat.\",\n { cause }\n );\n }\n}\n","import { MllpClientError, MllpErrorCode } from \"./base\";\n\n/**\n * The message cannot be sent as-is: it has no MSH-10 control ID, it could not\n * be serialized, or it contains a byte MLLP reserves. Nothing was written; the\n * reason is on `cause`. Fix the message and send again.\n */\nexport class MllpInvalidMessageError extends MllpClientError {\n override readonly name = \"MllpInvalidMessageError\";\n readonly code = MllpErrorCode.INVALID_MESSAGE;\n readonly delivery = \"not-sent\";\n\n constructor(cause: unknown) {\n super(\n \"The message could not be prepared for sending; nothing was written.\",\n { cause }\n );\n }\n}\n","import { MllpClientError, MllpErrorCode } from \"./base\";\n\n/** An option is out of range. Raised before anything happens. */\nexport class MllpInvalidOptionError extends MllpClientError {\n override readonly name = \"MllpInvalidOptionError\";\n readonly code = MllpErrorCode.INVALID_OPTION;\n readonly delivery = \"not-sent\";\n}\n","import { MllpClientError, MllpErrorCode } from \"./base\";\n\n/**\n * The reply was not a usable acknowledgment of the message that was waiting:\n * it could not be decoded, its acknowledgment code was missing or unknown, or\n * it answered a different message. The connection is ended. The reason is on\n * `cause`.\n */\nexport class MllpInvalidResponseError extends MllpClientError {\n override readonly name = \"MllpInvalidResponseError\";\n readonly code = MllpErrorCode.INVALID_RESPONSE;\n readonly delivery = \"unknown\";\n /** MSH-10 of the message that was waiting, when the thrower knew it. */\n readonly controlId?: string;\n\n constructor(cause: unknown, controlId?: string) {\n super(\n \"The reply is not a usable acknowledgment of the message that was waiting — the connection has been closed.\",\n { cause }\n );\n this.controlId = controlId;\n }\n}\n","/**\n * Reading a single value out of an HL7v2 tree.\n *\n * @module\n */\n\nimport type { Root } from \"@glion/ast\";\nimport { value } from \"@glion/util-query\";\n\n/** The value at `path`, or `\"\"` when the field is absent. */\nexport function read(tree: Root, path: string): string {\n return value(tree, path)?.value ?? \"\";\n}\n","/**\n * The exception a NAK carries. Not an {@link MllpClientError}: the remote\n * system understood the message and refused it, and the class is the one\n * `@glion/ack` defines for that MSA-1 code.\n *\n * @module\n */\n\nimport {\n AckApplicationError,\n AckApplicationReject,\n AckCode,\n AckCommitError,\n AckCommitReject,\n} from \"@glion/ack\";\nimport type { AckException, AckExceptionOptions, AckNakCode } from \"@glion/ack\";\nimport type { Root } from \"@glion/ast\";\n\nimport { read } from \"../utils\";\n\n/** The `@glion/ack` exception class for each NAK code. */\n// TODO(#689): replace with the shared reader from @glion/ack once it exists.\nconst NAK_EXCEPTIONS = {\n [AckCode.ApplicationError]: AckApplicationError,\n [AckCode.ApplicationReject]: AckApplicationReject,\n [AckCode.CommitError]: AckCommitError,\n [AckCode.CommitReject]: AckCommitReject,\n} as const;\n\n/** MSA-3, ERR-3, and ERR-4 as one sentence, or a note that none were given. */\nfunction nakMessage(code: AckNakCode, nak: AckExceptionOptions): string {\n const said: string[] = [];\n if (nak.text !== undefined) {\n said.push(nak.text);\n }\n if (nak.errorCode !== undefined) {\n said.push(`ERR-3 ${nak.errorCode}`);\n }\n if (nak.severity !== undefined) {\n said.push(`ERR-4 ${nak.severity}`);\n }\n const reason =\n said.length === 0 ? \"It gave no reason.\" : `${said.join(\"; \")}.`;\n return `The remote system did not accept message ${nak.controlId} (MSA-1 ${code}). ${reason}`;\n}\n\n/**\n * The exception a NAK of `code` carries, read from `tree`.\n *\n * `code` picks the class; MSA-2 gives `controlId`, ERR-3 `errorCode`, ERR-4\n * `severity`, and MSA-3 (or ERR-8) `text`.\n */\nexport function nakException(tree: Root, code: AckNakCode): AckException {\n const nak: AckExceptionOptions = {\n controlId: read(tree, \"MSA-2[1].1.1\"),\n errorCode: read(tree, \"ERR-3[1].1.1\") || undefined,\n severity: read(tree, \"ERR-4[1].1.1\") || undefined,\n // ERR-8 stands in for MSA-3 when the remote system left MSA-3 empty.\n text: read(tree, \"MSA-3[1].1.1\") || read(tree, \"ERR-8[1].1.1\") || undefined,\n };\n return new NAK_EXCEPTIONS[code](nakMessage(code, nak), nak);\n}\n","import { MllpClientError, MllpErrorCode } from \"./base\";\n\n/**\n * `destroy()` cut off the message in flight. The remote system may or may not\n * have received it.\n */\nexport class MllpSendAbortedError extends MllpClientError {\n override readonly name = \"MllpSendAbortedError\";\n readonly code = MllpErrorCode.SEND_ABORTED;\n readonly delivery = \"unknown\";\n\n constructor() {\n super(\n \"The send was cut off by destroy() — the remote system may or may not have received the message; resend only if the message is safe to repeat.\"\n );\n }\n}\n","import { MllpErrorCode } from \"./base\";\nimport { MllpConnectionError } from \"./mllp-connection-error\";\n\n/**\n * No acknowledgment arrived within the send timeout. The connection is ended.\n * Whether the remote system received the message is unknown.\n */\nexport class MllpSendTimeoutError extends MllpConnectionError {\n override readonly name = \"MllpSendTimeoutError\";\n readonly code = MllpErrorCode.SEND_TIMEOUT;\n readonly delivery = \"unknown\";\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(\n \"No acknowledgment arrived within the send timeout — the connection has been closed, because a late acknowledgment could not be matched safely.\"\n );\n this.timeoutMs = timeoutMs;\n }\n}\n","/**\n * Option validation. Each `assert*` throws {@link MllpInvalidOptionError} for\n * an option a caller passed out of range, before anything happens.\n *\n * @module\n */\n\nimport { MAX_TIMEOUT_MS } from \"./constants\";\nimport { MllpInvalidOptionError } from \"./errors\";\n\nexport function assertTimeoutMs(option: string, ms: number): void {\n if (!Number.isFinite(ms) || ms <= 0 || ms > MAX_TIMEOUT_MS) {\n throw new MllpInvalidOptionError(\n `${option} must be a number of milliseconds between 1 and ${MAX_TIMEOUT_MS}.`\n );\n }\n}\n\nexport function assertByteCap(bytes: number): void {\n if (!Number.isInteger(bytes) || bytes <= 0) {\n throw new MllpInvalidOptionError(\n \"maxBufferedBytes must be a positive integer.\"\n );\n }\n}\n\nexport function assertReconnectAttempts(attempts: number): void {\n const unbounded = attempts === Number.POSITIVE_INFINITY;\n if (!unbounded && (!Number.isInteger(attempts) || attempts < 0)) {\n throw new MllpInvalidOptionError(\n \"reconnect.attempts must be a non-negative integer, or Infinity.\"\n );\n }\n}\n","/**\n * What a client reports as it runs, and the listeners that receive it.\n *\n * @module\n */\n\nimport type { MllpClientError } from \"./errors\";\n\nexport interface MllpClientEvents {\n /** The connection is open and ready for messages. */\n connect(): void;\n /**\n * The client is done and will not connect again. `error` is the failure the\n * reconnect policy did not recover from, or `null` when it closes normally.\n */\n close(error: MllpClientError | null): void;\n}\n\n/** The name of one thing a client reports. */\nexport type MllpClientEvent = keyof MllpClientEvents;\n\n/** A listener for `event`. */\nexport type MllpClientListener<E extends MllpClientEvent> = MllpClientEvents[E];\n\n/**\n * The listeners of a client.\n *\n * A listener that throws propagates to whoever triggered the event, the same\n * as Node's `EventEmitter`.\n */\nexport abstract class MllpClientEmitter {\n readonly #listeners = new Map<\n MllpClientEvent,\n Set<MllpClientEvents[MllpClientEvent]>\n >();\n\n /** Adds a listener for `event`, and returns the client so calls chain. */\n on<E extends MllpClientEvent>(\n event: E,\n listener: MllpClientListener<E>\n ): this {\n const listeners = this.#listeners.get(event) ?? new Set();\n listeners.add(listener);\n this.#listeners.set(event, listeners);\n return this;\n }\n\n /** Removes a listener added with {@link on}. */\n off<E extends MllpClientEvent>(\n event: E,\n listener: MllpClientListener<E>\n ): this {\n this.#listeners.get(event)?.delete(listener);\n return this;\n }\n\n /** Calls every listener for `event`, in the order they were added. */\n protected emit<E extends MllpClientEvent>(\n event: E,\n ...args: Parameters<MllpClientEvents[E]>\n ): void {\n for (const listener of this.#listeners.get(event) ?? []) {\n (listener as (...a: Parameters<MllpClientEvents[E]>) => void)(...args);\n }\n }\n}\n","/**\n * Encoding a message to send, and decoding the acknowledgment that answers it.\n * Pure.\n *\n * @module\n */\n\nimport { isAckNakCode, isAckSuccessCode } from \"@glion/ack\";\nimport type { AckException } from \"@glion/ack\";\nimport type { Root } from \"@glion/ast\";\nimport { parseHL7v2 } from \"@glion/parser\";\nimport { toHl7v2 } from \"@glion/to-hl7v2\";\nimport { decodeBytes, encodeBytes } from \"@glion/util-charset\";\n\nimport {\n MllpInvalidMessageError,\n MllpInvalidResponseError,\n nakException,\n} from \"./errors\";\nimport type { MllpClientResponse } from \"./types\";\nimport { read } from \"./utils\";\n\n/** A message ready to go out. */\nexport interface EncodingResponse {\n /** MSH-10, which the acknowledgment echoes in MSA-2. */\n readonly controlId: string;\n /** The message in canonical HL7v2, encoded. Framing belongs to the connection. */\n readonly bytes: Uint8Array;\n}\n\n/**\n * `tree` as the message to send, in canonical HL7v2.\n *\n * @throws {MllpInvalidMessageError} The message has no MSH-10, or it could not\n * be serialized or encoded.\n */\nexport function encode(tree: Root): EncodingResponse {\n const controlId = read(tree, \"MSH-10[1].1.1\");\n if (controlId === \"\") {\n throw new MllpInvalidMessageError(\n \"the message has no MSH-10 control ID, so its acknowledgment could not be matched\"\n );\n }\n\n try {\n return { bytes: encodeBytes(toHl7v2(tree)), controlId };\n } catch (error) {\n throw new MllpInvalidMessageError(error);\n }\n}\n\n/** A reply, as one of the three things it can be to the message it was read for. */\nexport type DecodeResponse =\n /** MSA-1 accepted the message. */\n | { readonly type: \"accept\"; readonly response: MllpClientResponse }\n /** MSA-1 refused it. The remote system understood the message. */\n | { readonly type: \"nak\"; readonly exception: AckException }\n /**\n * Not a usable acknowledgment of this message: unreadable bytes, an MSA-1\n * outside Table 0008, or an MSA-2 naming another message.\n */\n | { readonly type: \"invalid\"; readonly error: MllpInvalidResponseError };\n\n/**\n * `bytes` as the reply to the message `controlId` identifies.\n *\n * MSA-2 is checked before MSA-1 is read: an accept or a NAK is reported only\n * for the message it answers. MSH-9 and the HL7 version are not checked\n * (#668).\n *\n * Never throws.\n */\nexport function decode(bytes: Uint8Array, controlId: string): DecodeResponse {\n let raw: string;\n let tree: Root;\n try {\n raw = decodeBytes(bytes);\n tree = parseHL7v2(raw);\n } catch (error) {\n return invalid(error, controlId);\n }\n\n const answers = read(tree, \"MSA-2[1].1.1\");\n if (answers !== controlId) {\n return invalid(\n `MSA-2 is \"${answers}\", so it answers a different message — usually a late acknowledgment from an earlier timed-out send.`,\n controlId\n );\n }\n\n const code = read(tree, \"MSA-1[1].1.1\");\n if (isAckNakCode(code)) {\n return { exception: nakException(tree, code), type: \"nak\" };\n }\n if (!isAckSuccessCode(code)) {\n return invalid(\n code === \"\"\n ? \"MSA-1 is empty, so accept or reject cannot be determined.\"\n : `MSA-1 is \"${code}\", which is not one of AA, AE, AR, CA, CE, or CR.`,\n controlId\n );\n }\n\n return {\n response: {\n code,\n controlId: answers,\n // MSH-10 of the acknowledgment itself. MUST NOT be used for correlation.\n id: read(tree, \"MSH-10[1].1.1\"),\n raw,\n text: read(tree, \"MSA-3[1].1.1\") || undefined,\n tree,\n },\n type: \"accept\",\n };\n}\n\nfunction invalid(reason: unknown, controlId: string): DecodeResponse {\n return {\n error: new MllpInvalidResponseError(reason, controlId),\n type: \"invalid\",\n };\n}\n","/**\n * The reconnect policy, its default delay, and the wait between attempts.\n *\n * @module\n */\n\n/** A reconnect policy with every option resolved. */\nexport interface ReconnectPolicy {\n readonly attempts: number;\n readonly delay: (attempt: number) => number;\n}\n\n/** Ceiling of the first default wait, in milliseconds. Doubles per attempt. */\nconst BACKOFF_BASE_MS = 1000;\n\n/** Ceiling of every default wait, in milliseconds. */\nconst BACKOFF_CAP_MS = 30_000;\n\n/**\n * Full-jitter exponential backoff: a wait drawn uniformly from zero to\n * `min(30 s, 1 s × 2^(attempt − 1))`.\n */\nexport function defaultReconnectDelay(attempt: number): number {\n const ceiling = Math.min(\n BACKOFF_CAP_MS,\n BACKOFF_BASE_MS * 2 ** (attempt - 1)\n );\n return Math.floor(Math.random() * ceiling);\n}\n\n/** Resolves after `ms`, or at once when `signal` aborts first. Never rejects. */\nexport function sleep(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) {\n return Promise.resolve();\n }\n // oxlint-disable-next-line promise/avoid-new -- wrapping a timer\n return new Promise<void>((resolve) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", done);\n // oxlint-disable-next-line promise/no-multiple-resolved -- each path unhooks the other first\n resolve();\n };\n const timer = setTimeout(done, ms);\n signal.addEventListener(\"abort\", done, { once: true });\n });\n}\n","/**\n * One session over a socket: the message layer over its bytes. A message\n * written here is framed, and bytes read here are unframed.\n *\n * @module\n */\n\nimport { MllpCodecError, frame, unframe } from \"@glion/mllp-codec\";\n\nimport {\n MllpConnectionFailedError,\n MllpConnectionTimeoutError,\n MllpConnectionLostError,\n MllpInvalidMessageError,\n MllpSendAbortedError,\n MllpSendTimeoutError,\n} from \"./errors\";\nimport type { MllpSocket, MllpStreams } from \"./types\";\n\n/**\n * One session over one socket, for that socket's lifetime. `ready` resolves\n * once it is open; `destroy()` ends it from any point.\n */\nexport interface MllpSession {\n /**\n * Resolves once the session is open. Rejects with\n * {@link MllpConnectionFailedError} or {@link MllpConnectionTimeoutError};\n * when destroyed while opening, with the former, and only once the socket\n * is down. The owner MUST await it, or observe it, before destroying a\n * session that has not opened.\n */\n readonly ready: Promise<void>;\n /**\n * Sends one message, once the session is open, and returns the next one the\n * remote system sends back, unframed. `timeoutMs` bounds the write and the\n * wait for the reply; it does not cover the opening.\n *\n * @throws {MllpInvalidMessageError} The message contains a reserved MLLP\n * byte, so nothing was written.\n * @throws {MllpConnectionFailedError} The session never opened.\n * @throws {MllpConnectionTimeoutError} The session never opened in time.\n * @throws {MllpSendTimeoutError} Nothing came back within `timeoutMs`. The\n * session is destroyed.\n * @throws {MllpSendAbortedError} `destroy()` ended the session while the\n * reply was awaited.\n * @throws {MllpConnectionLostError} The remote system closed, or the stream\n * failed, before a reply came.\n * @throws {MllpCodecError} The remote system sent bytes that are not an MLLP\n * frame.\n */\n exchange(message: Uint8Array, timeoutMs: number): Promise<Uint8Array>;\n /**\n * Ends the session now, and resolves once the socket is down. A `ready`\n * still pending rejects; so does an `exchange` still waiting.\n *\n * Never rejects. Idempotent. Bounded.\n */\n destroy(): Promise<void>;\n}\n\nexport interface ConnectOptions {\n /** Time the remote system has to accept the connection. */\n readonly timeoutMs: number;\n /** Maximum bytes buffered while reading one frame. */\n readonly maxBufferedBytes: number;\n}\n\n/** The socket's streams, framed and locked for this session's use. */\ninterface Framing {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly writer: WritableStreamDefaultWriter<Uint8Array>;\n}\n\n/**\n * Opens `socket` and takes ownership of its streams.\n *\n * @throws {MllpConnectionTimeoutError} No socket within `timeoutMs`.\n * @throws {MllpConnectionFailedError} The socket failed to open, or `signal`\n * cancelled the attempt; the adapter's rejection is on `cause`.\n */\nasync function openSocketStreams(\n socket: MllpSocket,\n signal: AbortSignal,\n opts: ConnectOptions\n): Promise<Framing> {\n const timeout = AbortSignal.timeout(opts.timeoutMs);\n\n let streams: MllpStreams;\n try {\n streams = await socket.connect(AbortSignal.any([signal, timeout]));\n } catch (error) {\n if (timeout.aborted) {\n throw new MllpConnectionTimeoutError(opts.timeoutMs);\n }\n throw new MllpConnectionFailedError(error);\n }\n\n try {\n return {\n reader: streams.readable\n .pipeThrough(unframe({ maxBufferedBytes: opts.maxBufferedBytes }))\n .getReader(),\n writer: streams.writable.getWriter(),\n };\n } catch (error) {\n // `destroy` ends only an attempt that fulfilled; this one is about to reject.\n await socket.close();\n throw new MllpConnectionFailedError(error);\n }\n}\n\n/**\n * Starts opening a session over `socket`, and hands it back at once. The\n * session ends itself when `signal` aborts.\n *\n * Never throws: every failure, an adapter's synchronous throw included,\n * reaches `ready`. Nothing is left open on any path: an attempt that fails or\n * is cancelled closes the socket before `ready` rejects.\n */\nexport function createSession(\n socket: MllpSocket,\n opts: ConnectOptions,\n signal?: AbortSignal\n): MllpSession {\n const abort = new AbortController();\n const streams = openSocketStreams(socket, abort.signal, opts);\n let closing: Promise<void> | null = null;\n const cancel = () => void destroy();\n\n const ready = (async () => {\n signal?.addEventListener(\"abort\", cancel, { once: true });\n try {\n await streams;\n } catch (error) {\n signal?.removeEventListener(\"abort\", cancel);\n throw error;\n }\n if (closing !== null) {\n // Destroyed while opening: the socket is down before this settles.\n await closing;\n throw new MllpConnectionFailedError(abort.signal.reason);\n }\n })();\n\n const destroy = (): Promise<void> => {\n closing ??= (async () => {\n // Aborting cancels a dial still in flight, through the signal the\n // adapter was given, and marks the session as ended, so that a read or\n // write released below reports SEND_ABORTED rather than a stream error.\n abort.abort();\n signal?.removeEventListener(\"abort\", cancel);\n // Opening settles either way once aborted. An open that failed has\n // already closed whatever it opened; only one that succeeded leaves\n // streams to release and a socket to close.\n const [opened] = await Promise.allSettled([streams]);\n if (opened.status === \"fulfilled\") {\n // Release the streams, do not cancel them: cancelling would destroy\n // the socket under the adapter and skip its graceful close.\n opened.value.reader.releaseLock();\n opened.value.writer.releaseLock();\n await socket.close();\n }\n })();\n return closing;\n };\n\n const exchange = async (\n message: Uint8Array,\n timeoutMs: number\n ): Promise<Uint8Array> => {\n let framed: Uint8Array;\n try {\n framed = frame(message);\n } catch (error) {\n // Raised before the writer is touched: nothing reached the wire.\n throw new MllpInvalidMessageError(error);\n }\n\n await ready;\n\n // Destroying is what wakes the read parked below. `clearTimeout` runs in a\n // microtask and this fires as a macrotask, so a deadline that fires at all\n // is one whose exchange is still in flight.\n let timedOut = false;\n const deadline = setTimeout(() => {\n timedOut = true;\n void destroy();\n }, timeoutMs);\n try {\n const { reader, writer } = await streams;\n await writer.write(framed);\n const reply = await reader.read();\n if (reply.done) {\n throw new MllpConnectionLostError();\n }\n return reply.value;\n } catch (error) {\n if (timedOut) {\n throw new MllpSendTimeoutError(timeoutMs);\n }\n if (abort.signal.aborted) {\n throw new MllpSendAbortedError();\n }\n throw error instanceof MllpCodecError ||\n error instanceof MllpConnectionLostError\n ? error\n : new MllpConnectionLostError(error);\n } finally {\n clearTimeout(deadline);\n }\n };\n\n return { destroy, exchange, ready };\n}\n","import type { Root } from \"@glion/ast\";\nimport { MllpCodecError } from \"@glion/mllp-codec\";\n\nimport {\n assertByteCap,\n assertReconnectAttempts,\n assertTimeoutMs,\n} from \"./assertions\";\nimport {\n DEFAULT_CONNECT_TIMEOUT_MS,\n DEFAULT_MAX_BUFFERED_BYTES,\n DEFAULT_RECONNECT_ATTEMPTS,\n DEFAULT_SEND_TIMEOUT_MS,\n} from \"./constants\";\nimport {\n MllpAlreadySendingError,\n MllpClientClosedError,\n MllpClientError,\n MllpConnectionError,\n MllpInvalidMessageError,\n MllpInvalidResponseError,\n} from \"./errors\";\nimport { MllpClientEmitter } from \"./events\";\nimport { decode, encode } from \"./messages\";\nimport { defaultReconnectDelay, sleep } from \"./reconnect\";\nimport type { ReconnectPolicy } from \"./reconnect\";\nimport { createSession } from \"./session\";\nimport type { ConnectOptions, MllpSession } from \"./session\";\nimport type { State } from \"./state\";\nimport type {\n MllpClientOptions,\n MllpClientResponse,\n MllpClientState,\n MllpSendOptions,\n MllpSocket,\n} from \"./types\";\n\n/**\n * Sends HL7v2 messages to one remote system over MLLP and returns each\n * message's acknowledgment.\n *\n * One client owns one socket and sends one message at a time. `send()` writes\n * the message, waits for the acknowledgment, checks that it answers this\n * message, and resolves with it.\n *\n * ```ts\n * import { MllpClient } from \"@glion/mllp-client\";\n * import { nodeSocket } from \"@glion/mllp-client/node\";\n *\n * await using client = new MllpClient({\n * socket: nodeSocket({ host: \"hl7.example.org\", port: 2575 }),\n * });\n *\n * const ack = await client.send(adtMessage);\n * ack.code; // \"AA\" or \"CA\"\n * ```\n *\n * ## Lifecycle\n *\n * `send()` connects on first use; `connect()` opens the connection ahead of\n * time. `state` reports the phase. A connection that fails to open is dialed\n * again under the `reconnect` policy. A connection that is lost closes the\n * client: the message in flight rejects and is not sent again. After\n * `close()`, or once the client has closed on a failure, every call rejects\n * with {@link MllpClientClosedError}.\n *\n * ## Errors\n *\n * When the remote system answers `AE`, `AR`, `CE`, or `CR`, `send()` rejects\n * with the matching `@glion/ack` exception and the connection stays open.\n */\nexport class MllpClient extends MllpClientEmitter {\n readonly #socket: MllpSocket;\n readonly #connect: ConnectOptions;\n readonly #policy: ReconnectPolicy;\n readonly #sendTimeoutMs: number;\n #state: State = { phase: \"idle\" };\n\n /**\n * @throws {MllpInvalidOptionError} A timeout, byte cap, or attempt count is\n * out of range.\n */\n constructor(opts: MllpClientOptions) {\n super();\n const connectTimeoutMs =\n opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n const maxBufferedBytes =\n opts.maxBufferedBytes ?? DEFAULT_MAX_BUFFERED_BYTES;\n const attempts =\n opts.reconnect === false\n ? 0\n : (opts.reconnect?.attempts ?? DEFAULT_RECONNECT_ATTEMPTS);\n const delay =\n opts.reconnect === false\n ? defaultReconnectDelay\n : (opts.reconnect?.delay ?? defaultReconnectDelay);\n this.#sendTimeoutMs = opts.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;\n\n assertTimeoutMs(\"connectTimeoutMs\", connectTimeoutMs);\n assertTimeoutMs(\"sendTimeoutMs\", this.#sendTimeoutMs);\n assertByteCap(maxBufferedBytes);\n assertReconnectAttempts(attempts);\n\n this.#socket = opts.socket;\n this.#connect = { maxBufferedBytes, timeoutMs: connectTimeoutMs };\n this.#policy = { attempts, delay };\n }\n\n /**\n * The current phase: `idle`, `connecting`, `connected`, `sending`,\n * `closing`, or `closed`.\n */\n get state(): MllpClientState {\n return this.#state.phase;\n }\n\n /** Whether the connection is open. True in both `connected` and `sending`. */\n get connected(): boolean {\n const { phase } = this.#state;\n return phase === \"connected\" || phase === \"sending\";\n }\n\n /**\n * Opens the connection ahead of the first `send()`, dialing again under the\n * `reconnect` policy when an attempt fails.\n *\n * Idempotent: a connected client resolves at once, and a call arriving while\n * an attempt is in flight waits for that attempt and shares its outcome.\n *\n * @throws {MllpConnectionFailedError} The last attempt could not open the\n * socket.\n * @throws {MllpConnectionTimeoutError} The last attempt did not open in time.\n * @throws {MllpClientClosedError} The client is closed, or `close()` arrived\n * while the connection was still being established.\n */\n async connect(): Promise<void> {\n const state = this.#state;\n switch (state.phase) {\n case \"connected\":\n case \"sending\": {\n return;\n }\n case \"connecting\": {\n await state.opening;\n return;\n }\n case \"closing\": {\n throw new MllpClientClosedError();\n }\n case \"closed\": {\n throw new MllpClientClosedError(state.reason);\n }\n case \"idle\": {\n const abort = new AbortController();\n const opening = this.#dial(abort.signal);\n this.#state = { abort, opening, phase: \"connecting\" };\n await opening;\n }\n }\n }\n\n /**\n * Sends one message and returns its acknowledgment. Connects first when the\n * client is not connected yet.\n *\n * Nothing is written when an option, phase, or message error is thrown.\n * Every other failure also closes the client.\n *\n * @throws {AckException} The remote system answered with a NAK. The\n * connection stays open.\n * @throws {MllpInvalidOptionError} `timeoutMs` is out of range.\n * @throws {MllpInvalidMessageError} The message cannot be sent as-is.\n * @throws {MllpClientClosedError} The client is closed.\n * @throws {MllpAlreadySendingError} Another send is in flight.\n * @throws {MllpConnectionFailedError} The connection could not be opened.\n * @throws {MllpConnectionTimeoutError} The connection did not open in time.\n * @throws {MllpSendTimeoutError} No acknowledgment arrived in time.\n * @throws {MllpConnectionLostError} The connection was lost.\n * @throws {MllpSendAbortedError} `destroy()` cut the send off.\n * @throws {MllpInvalidResponseError} The reply is not a usable\n * acknowledgment of this message.\n */\n async send(\n message: Root,\n opts: MllpSendOptions = {}\n ): Promise<MllpClientResponse> {\n const timeoutMs = opts.timeoutMs ?? this.#sendTimeoutMs;\n assertTimeoutMs(\"timeoutMs\", timeoutMs); // throws {MllpInvalidOptionError}\n\n const { bytes, controlId } = encode(message); // throws {MllpInvalidMessageError}\n\n if (!this.connected) {\n // Connect if not already connected.\n // note: if the state is closed or closing, connect() will throw before\n // we get here.\n await this.connect();\n }\n // No await from here to the move into `sending`: the phase read here is\n // the phase the move starts from.\n const state = this.#state;\n switch (state.phase) {\n case \"sending\": {\n // A send arriving mid-flight is refused, not queued: the caller owns\n // the sequence. Waiting instead is under review in #754.\n throw new MllpAlreadySendingError(state.controlId);\n }\n case \"closing\": {\n throw new MllpClientClosedError();\n }\n case \"closed\": {\n throw new MllpClientClosedError(state.reason);\n }\n case \"idle\":\n case \"connecting\": {\n throw new Error(\n `send() found the client ${state.phase} after it connected. This is a bug in @glion/mllp-client; please report it.`\n );\n }\n case \"connected\": {\n break;\n }\n }\n\n const done = this.#send(state.session, controlId, bytes, timeoutMs);\n this.#state = { controlId, done, phase: \"sending\", session: state.session };\n return await done;\n }\n\n /**\n * Ends the connection once the message in flight is acknowledged, and\n * resolves when it is down. New sends are refused from the moment this is\n * called. An attempt to connect stops at once.\n *\n * Resolves from any phase. Never rejects. Idempotent. The wait is bounded by\n * the in-flight send's own deadline; {@link destroy} does not wait at all.\n */\n async close(): Promise<void> {\n const from = this.#state;\n if (from.phase === \"sending\") {\n this.#state = {\n done: from.done,\n phase: \"closing\",\n session: from.session,\n };\n await Promise.allSettled([from.done]);\n }\n await this.destroy();\n }\n\n /**\n * Ends the connection now, and resolves when it is down. A message in flight\n * rejects with {@link MllpSendAbortedError}. An attempt to connect stops at\n * once.\n *\n * Resolves from any phase. Never rejects. Idempotent.\n */\n async destroy(): Promise<void> {\n const from = this.#state;\n this.#closed(null);\n switch (from.phase) {\n case \"idle\":\n case \"closed\": {\n return;\n }\n case \"connecting\": {\n from.abort.abort();\n // Its outcome belongs to the calls waiting on it.\n await Promise.allSettled([from.opening]);\n return;\n }\n case \"connected\":\n case \"sending\":\n case \"closing\": {\n await from.session.destroy();\n }\n }\n }\n\n /** Calls {@link close}. Enables `await using`. */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n // ── The connection ──────────────────────────────────────────────────\n\n /**\n * Dials under the policy until a session opens, then moves to `connected`\n * and reports `connect`. Each attempt after the first waits\n * `delay(attempt)` first. Once the policy gives up, closes the client with\n * the last attempt's failure.\n *\n * @throws {MllpClientClosedError} `signal` aborted: the owner closed the\n * client. Nothing is left open.\n * @throws {MllpConnectionFailedError} The last attempt could not open the\n * socket.\n * @throws {MllpConnectionTimeoutError} The last attempt did not open in\n * time.\n */\n async #dial(signal: AbortSignal): Promise<void> {\n for (let attempt = 0; ; attempt += 1) {\n if (attempt > 0) {\n await sleep(this.#policy.delay(attempt), signal);\n }\n if (signal.aborted) {\n throw new MllpClientClosedError();\n }\n const session = createSession(this.#socket, this.#connect, signal);\n try {\n await session.ready;\n } catch (error) {\n this.#failed(attempt, error, signal);\n continue;\n }\n if (signal.aborted) {\n // Aborted between the socket opening and this running; the session\n // ends itself on the signal.\n await session.destroy();\n throw new MllpClientClosedError();\n }\n this.#state = { phase: \"connected\", session };\n this.emit(\"connect\");\n return;\n }\n }\n\n /**\n * Attempt `attempt` failed with `error`: returns when the policy allows\n * another. Throws `MllpClientClosedError` once `signal` has aborted, and\n * `error` once the policy is out of attempts, the client closed with it.\n */\n #failed(attempt: number, error: unknown, signal: AbortSignal): void {\n if (signal.aborted) {\n throw new MllpClientClosedError();\n }\n if (attempt < this.#policy.attempts) {\n return;\n }\n if (error instanceof MllpConnectionError) {\n this.#closed(error);\n }\n throw error;\n }\n\n /**\n * The terminal move, from any phase: `reason` is the failure the client\n * closes with, or `null` when the owner closes it. Once.\n */\n #closed(reason: MllpClientError | null): void {\n if (this.#state.phase === \"closed\") {\n return;\n }\n this.#state = { phase: \"closed\", reason };\n this.emit(\"close\", reason);\n }\n\n // ── The exchange ────────────────────────────────────────────────────\n\n /**\n * One message on the wire, and the acknowledgment it comes back with.\n *\n * Settles when the send is over, however it ended.\n */\n async #send(\n session: MllpSession,\n controlId: string,\n bytes: Uint8Array,\n timeoutMs: number\n ): Promise<MllpClientResponse> {\n try {\n const reply = await this.#exchange(session, controlId, bytes, timeoutMs);\n return await this.#readAcknowledgment(session, controlId, reply);\n } finally {\n if (this.#state.phase === \"sending\") {\n this.#state = { phase: \"connected\", session };\n }\n }\n }\n\n /**\n * The message on the wire, and the reply it must produce to be a send.\n * Anything else closes the client.\n */\n async #exchange(\n session: MllpSession,\n controlId: string,\n bytes: Uint8Array,\n timeoutMs: number\n ): Promise<Uint8Array> {\n try {\n return await session.exchange(bytes, timeoutMs);\n } catch (error) {\n if (error instanceof MllpInvalidMessageError) {\n // Raised before the writer was touched: the wire is still in step.\n throw error;\n }\n if (error instanceof MllpCodecError) {\n await this.#fail(\n session,\n new MllpInvalidResponseError(error, controlId)\n );\n }\n if (error instanceof MllpClientError) {\n await this.#fail(session, error);\n }\n throw error;\n }\n }\n\n /**\n * `reply` as this message's acknowledgment.\n *\n * A reply that cannot be read, or that answers another message, closes the\n * client. A NAK does not.\n */\n async #readAcknowledgment(\n session: MllpSession,\n controlId: string,\n reply: Uint8Array\n ): Promise<MllpClientResponse> {\n const answer = decode(reply, controlId);\n switch (answer.type) {\n case \"accept\": {\n return answer.response;\n }\n case \"nak\": {\n throw answer.exception;\n }\n case \"invalid\": {\n return await this.#fail(session, answer.error);\n }\n }\n }\n\n /**\n * Closes the client with `reason` and ends `session`. Throws `reason` once\n * the socket is down.\n */\n async #fail(session: MllpSession, reason: MllpClientError): Promise<never> {\n this.#closed(reason);\n await session.destroy();\n throw reason;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Encoding a message to send, and decoding the acknowledgment that answers it.
3
+ * Pure.
4
+ *
5
+ * @module
6
+ */
7
+ import type { AckException } from "@glion/ack";
8
+ import type { Root } from "@glion/ast";
9
+ import { MllpInvalidResponseError } from "./errors";
10
+ import type { MllpClientResponse } from "./types";
11
+ /** A message ready to go out. */
12
+ export interface EncodingResponse {
13
+ /** MSH-10, which the acknowledgment echoes in MSA-2. */
14
+ readonly controlId: string;
15
+ /** The message in canonical HL7v2, encoded. Framing belongs to the connection. */
16
+ readonly bytes: Uint8Array;
17
+ }
18
+ /**
19
+ * `tree` as the message to send, in canonical HL7v2.
20
+ *
21
+ * @throws {MllpInvalidMessageError} The message has no MSH-10, or it could not
22
+ * be serialized or encoded.
23
+ */
24
+ export declare function encode(tree: Root): EncodingResponse;
25
+ /** A reply, as one of the three things it can be to the message it was read for. */
26
+ export type DecodeResponse =
27
+ /** MSA-1 accepted the message. */
28
+ {
29
+ readonly type: "accept";
30
+ readonly response: MllpClientResponse;
31
+ }
32
+ /** MSA-1 refused it. The remote system understood the message. */
33
+ | {
34
+ readonly type: "nak";
35
+ readonly exception: AckException;
36
+ }
37
+ /**
38
+ * Not a usable acknowledgment of this message: unreadable bytes, an MSA-1
39
+ * outside Table 0008, or an MSA-2 naming another message.
40
+ */
41
+ | {
42
+ readonly type: "invalid";
43
+ readonly error: MllpInvalidResponseError;
44
+ };
45
+ /**
46
+ * `bytes` as the reply to the message `controlId` identifies.
47
+ *
48
+ * MSA-2 is checked before MSA-1 is read: an accept or a NAK is reported only
49
+ * for the message it answers. MSH-9 and the HL7 version are not checked
50
+ * (#668).
51
+ *
52
+ * Never throws.
53
+ */
54
+ export declare function decode(bytes: Uint8Array, controlId: string): DecodeResponse;
55
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,OAAO,EAEL,wBAAwB,EAEzB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlD,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,gBAAgB,CAanD;AAED,oFAAoF;AACpF,MAAM,MAAM,cAAc;AACxB,kCAAkC;AAChC;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAA;CAAE;AACpE,kEAAkE;GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAA;CAAE;AAC5D;;;GAGG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,CAAC;AAE3E;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,CA2C3E"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The reconnect policy, its default delay, and the wait between attempts.
3
+ *
4
+ * @module
5
+ */
6
+ /** A reconnect policy with every option resolved. */
7
+ export interface ReconnectPolicy {
8
+ readonly attempts: number;
9
+ readonly delay: (attempt: number) => number;
10
+ }
11
+ /**
12
+ * Full-jitter exponential backoff: a wait drawn uniformly from zero to
13
+ * `min(30 s, 1 s × 2^(attempt − 1))`.
14
+ */
15
+ export declare function defaultReconnectDelay(attempt: number): number;
16
+ /** Resolves after `ms`, or at once when `signal` aborts first. Never rejects. */
17
+ export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
18
+ //# sourceMappingURL=reconnect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconnect.d.ts","sourceRoot":"","sources":["../src/reconnect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7C;AAQD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAepE"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Node runtime adapter for `MllpClient`: one `net.Socket`, ended gracefully
3
+ * first and forced after a grace period. Closing resolves in bounded time.
4
+ *
5
+ * @module
6
+ */
7
+ import type { MllpSocket } from "../types";
8
+ /** Default time a socket gets to end gracefully before it is destroyed. */
9
+ export declare const DEFAULT_GRACEFUL_CLOSE_MS = 1000;
10
+ /** Default idle time before the first keepalive probe. */
11
+ export declare const DEFAULT_KEEPALIVE_IDLE_MS = 30000;
12
+ export interface NodeSocketOptions {
13
+ /** Host name or address of the remote system. */
14
+ readonly host: string;
15
+ /** TCP port of the remote system. */
16
+ readonly port: number;
17
+ /**
18
+ * Time a socket gets to end gracefully before it is destroyed, in
19
+ * milliseconds.
20
+ *
21
+ * @default 1_000.
22
+ */
23
+ readonly gracefulCloseMs?: number;
24
+ /**
25
+ * Idle time before the first keepalive probe, in milliseconds. A silent NAT
26
+ * or firewall drop surfaces after this rather than at the next send.
27
+ *
28
+ * @default 30_000.
29
+ */
30
+ readonly keepAliveIdleMs?: number;
31
+ }
32
+ /**
33
+ * A TCP socket to one remote system, over Node's `net` module.
34
+ *
35
+ * Each `connect()` dials a fresh `net.Socket`: Node cannot reconnect one that
36
+ * has been destroyed.
37
+ */
38
+ export declare function nodeSocket(opts: NodeSocketOptions): MllpSocket;
39
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/runtime/node.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAExD,2EAA2E;AAC3E,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AA8ED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAoB9D"}
@@ -0,0 +1,100 @@
1
+ import { Socket } from "node:net";
2
+ import { Duplex } from "node:stream";
3
+ //#region src/runtime/node.ts
4
+ /**
5
+ * Node runtime adapter for `MllpClient`: one `net.Socket`, ended gracefully
6
+ * first and forced after a grace period. Closing resolves in bounded time.
7
+ *
8
+ * @module
9
+ */
10
+ /** Default time a socket gets to end gracefully before it is destroyed. */
11
+ const DEFAULT_GRACEFUL_CLOSE_MS = 1e3;
12
+ /** Default idle time before the first keepalive probe. */
13
+ const DEFAULT_KEEPALIVE_IDLE_MS = 3e4;
14
+ /**
15
+ * Dials one `net.Socket` and resolves it once the handshake completes.
16
+ *
17
+ * Rejects with the socket's error, or with `signal.reason` when cancelled.
18
+ * Destroys the socket before rejecting.
19
+ */
20
+ function dial(host, port, keepAliveIdleMs, signal) {
21
+ return new Promise((resolve, reject) => {
22
+ const socket = new Socket();
23
+ socket.setNoDelay(true);
24
+ socket.setKeepAlive(true, keepAliveIdleMs);
25
+ const done = () => {
26
+ socket.removeListener("error", onError);
27
+ socket.removeListener("connect", onConnect);
28
+ signal.removeEventListener("abort", onAbort);
29
+ };
30
+ const onError = (error) => {
31
+ done();
32
+ socket.destroy();
33
+ reject(error);
34
+ };
35
+ const onAbort = () => {
36
+ done();
37
+ socket.destroy();
38
+ reject(signal.reason);
39
+ };
40
+ const onConnect = () => {
41
+ done();
42
+ resolve(socket);
43
+ };
44
+ if (signal.aborted) {
45
+ onAbort();
46
+ return;
47
+ }
48
+ socket.once("error", onError);
49
+ socket.once("connect", onConnect);
50
+ signal.addEventListener("abort", onAbort, { once: true });
51
+ socket.connect(port, host);
52
+ });
53
+ }
54
+ /**
55
+ * Ends a socket: FIN first, then destroy if the remote system does not answer.
56
+ *
57
+ * The grace window avoids the FIN-immediately-followed-by-RST sequence that
58
+ * some integration engines log as a protocol error.
59
+ */
60
+ function end(socket, gracefulCloseMs) {
61
+ if (socket.destroyed || socket.closed) return Promise.resolve();
62
+ return new Promise((resolve) => {
63
+ const grace = setTimeout(() => {
64
+ if (!socket.destroyed) socket.destroy();
65
+ }, gracefulCloseMs);
66
+ socket.once("close", () => {
67
+ clearTimeout(grace);
68
+ resolve();
69
+ });
70
+ socket.end();
71
+ });
72
+ }
73
+ /**
74
+ * A TCP socket to one remote system, over Node's `net` module.
75
+ *
76
+ * Each `connect()` dials a fresh `net.Socket`: Node cannot reconnect one that
77
+ * has been destroyed.
78
+ */
79
+ function nodeSocket(opts) {
80
+ const gracefulCloseMs = opts.gracefulCloseMs ?? 1e3;
81
+ const keepAliveIdleMs = opts.keepAliveIdleMs ?? 3e4;
82
+ /** Ends whatever `connect()` last opened. Nothing is open to begin with. */
83
+ let closeOpen = () => Promise.resolve();
84
+ return {
85
+ close: () => closeOpen(),
86
+ async connect(signal) {
87
+ const socket = await dial(opts.host, opts.port, keepAliveIdleMs, signal);
88
+ closeOpen = () => end(socket, gracefulCloseMs);
89
+ const web = Duplex.toWeb(socket);
90
+ return {
91
+ readable: web.readable,
92
+ writable: web.writable
93
+ };
94
+ }
95
+ };
96
+ }
97
+ //#endregion
98
+ export { DEFAULT_GRACEFUL_CLOSE_MS, DEFAULT_KEEPALIVE_IDLE_MS, nodeSocket };
99
+
100
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","names":[],"sources":["../../src/runtime/node.ts"],"sourcesContent":["/**\n * Node runtime adapter for `MllpClient`: one `net.Socket`, ended gracefully\n * first and forced after a grace period. Closing resolves in bounded time.\n *\n * @module\n */\n\nimport { Socket } from \"node:net\";\nimport { Duplex } from \"node:stream\";\n\nimport type { MllpSocket, MllpStreams } from \"../types\";\n\n/** Default time a socket gets to end gracefully before it is destroyed. */\nexport const DEFAULT_GRACEFUL_CLOSE_MS = 1000;\n\n/** Default idle time before the first keepalive probe. */\nexport const DEFAULT_KEEPALIVE_IDLE_MS = 30_000;\n\nexport interface NodeSocketOptions {\n /** Host name or address of the remote system. */\n readonly host: string;\n /** TCP port of the remote system. */\n readonly port: number;\n /**\n * Time a socket gets to end gracefully before it is destroyed, in\n * milliseconds.\n *\n * @default 1_000.\n */\n readonly gracefulCloseMs?: number;\n /**\n * Idle time before the first keepalive probe, in milliseconds. A silent NAT\n * or firewall drop surfaces after this rather than at the next send.\n *\n * @default 30_000.\n */\n readonly keepAliveIdleMs?: number;\n}\n\n/**\n * Dials one `net.Socket` and resolves it once the handshake completes.\n *\n * Rejects with the socket's error, or with `signal.reason` when cancelled.\n * Destroys the socket before rejecting.\n */\nfunction dial(\n host: string,\n port: number,\n keepAliveIdleMs: number,\n signal: AbortSignal\n): Promise<Socket> {\n // oxlint-disable-next-line promise/avoid-new -- wrapping a Node event emitter\n return new Promise<Socket>((resolve, reject) => {\n const socket = new Socket();\n // No Nagle — MLLP acknowledgments are tiny and latency-sensitive.\n socket.setNoDelay(true);\n socket.setKeepAlive(true, keepAliveIdleMs);\n\n const done = () => {\n socket.removeListener(\"error\", onError);\n socket.removeListener(\"connect\", onConnect);\n signal.removeEventListener(\"abort\", onAbort);\n };\n const onError = (error: Error) => {\n done();\n socket.destroy();\n reject(error);\n };\n const onAbort = () => {\n done();\n socket.destroy();\n reject(signal.reason);\n };\n const onConnect = () => {\n done();\n resolve(socket);\n };\n\n if (signal.aborted) {\n onAbort();\n return;\n }\n socket.once(\"error\", onError);\n socket.once(\"connect\", onConnect);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n socket.connect(port, host);\n });\n}\n\n/**\n * Ends a socket: FIN first, then destroy if the remote system does not answer.\n *\n * The grace window avoids the FIN-immediately-followed-by-RST sequence that\n * some integration engines log as a protocol error.\n */\nfunction end(socket: Socket, gracefulCloseMs: number): Promise<void> {\n if (socket.destroyed || socket.closed) {\n return Promise.resolve();\n }\n // oxlint-disable-next-line promise/avoid-new -- wrapping a Node event emitter\n return new Promise<void>((resolve) => {\n const grace = setTimeout(() => {\n if (!socket.destroyed) {\n socket.destroy();\n }\n }, gracefulCloseMs);\n socket.once(\"close\", () => {\n clearTimeout(grace);\n // oxlint-disable-next-line promise/no-multiple-resolved -- `once`\n resolve();\n });\n socket.end();\n });\n}\n\n/**\n * A TCP socket to one remote system, over Node's `net` module.\n *\n * Each `connect()` dials a fresh `net.Socket`: Node cannot reconnect one that\n * has been destroyed.\n */\nexport function nodeSocket(opts: NodeSocketOptions): MllpSocket {\n const gracefulCloseMs = opts.gracefulCloseMs ?? DEFAULT_GRACEFUL_CLOSE_MS;\n const keepAliveIdleMs = opts.keepAliveIdleMs ?? DEFAULT_KEEPALIVE_IDLE_MS;\n /** Ends whatever `connect()` last opened. Nothing is open to begin with. */\n let closeOpen = (): Promise<void> => Promise.resolve();\n\n return {\n close: () => closeOpen(),\n\n async connect(signal: AbortSignal): Promise<MllpStreams> {\n const socket = await dial(opts.host, opts.port, keepAliveIdleMs, signal);\n closeOpen = () => end(socket, gracefulCloseMs);\n\n const web = Duplex.toWeb(socket);\n return {\n readable: web.readable as ReadableStream<Uint8Array>,\n writable: web.writable as WritableStream<Uint8Array>,\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;AAaA,MAAa,4BAA4B;;AAGzC,MAAa,4BAA4B;;;;;;;AA6BzC,SAAS,KACP,MACA,MACA,iBACA,QACiB;AAEjB,QAAO,IAAI,SAAiB,SAAS,WAAW;EAC9C,MAAM,SAAS,IAAI,QAAQ;AAE3B,SAAO,WAAW,KAAK;AACvB,SAAO,aAAa,MAAM,gBAAgB;EAE1C,MAAM,aAAa;AACjB,UAAO,eAAe,SAAS,QAAQ;AACvC,UAAO,eAAe,WAAW,UAAU;AAC3C,UAAO,oBAAoB,SAAS,QAAQ;;EAE9C,MAAM,WAAW,UAAiB;AAChC,SAAM;AACN,UAAO,SAAS;AAChB,UAAO,MAAM;;EAEf,MAAM,gBAAgB;AACpB,SAAM;AACN,UAAO,SAAS;AAChB,UAAO,OAAO,OAAO;;EAEvB,MAAM,kBAAkB;AACtB,SAAM;AACN,WAAQ,OAAO;;AAGjB,MAAI,OAAO,SAAS;AAClB,YAAS;AACT;;AAEF,SAAO,KAAK,SAAS,QAAQ;AAC7B,SAAO,KAAK,WAAW,UAAU;AACjC,SAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AACzD,SAAO,QAAQ,MAAM,KAAK;GAC1B;;;;;;;;AASJ,SAAS,IAAI,QAAgB,iBAAwC;AACnE,KAAI,OAAO,aAAa,OAAO,OAC7B,QAAO,QAAQ,SAAS;AAG1B,QAAO,IAAI,SAAe,YAAY;EACpC,MAAM,QAAQ,iBAAiB;AAC7B,OAAI,CAAC,OAAO,UACV,QAAO,SAAS;KAEjB,gBAAgB;AACnB,SAAO,KAAK,eAAe;AACzB,gBAAa,MAAM;AAEnB,YAAS;IACT;AACF,SAAO,KAAK;GACZ;;;;;;;;AASJ,SAAgB,WAAW,MAAqC;CAC9D,MAAM,kBAAkB,KAAK,mBAAA;CAC7B,MAAM,kBAAkB,KAAK,mBAAA;;CAE7B,IAAI,kBAAiC,QAAQ,SAAS;AAEtD,QAAO;EACL,aAAa,WAAW;EAExB,MAAM,QAAQ,QAA2C;GACvD,MAAM,SAAS,MAAM,KAAK,KAAK,MAAM,KAAK,MAAM,iBAAiB,OAAO;AACxE,qBAAkB,IAAI,QAAQ,gBAAgB;GAE9C,MAAM,MAAM,OAAO,MAAM,OAAO;AAChC,UAAO;IACL,UAAU,IAAI;IACd,UAAU,IAAI;IACf;;EAEJ"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * One session over a socket: the message layer over its bytes. A message
3
+ * written here is framed, and bytes read here are unframed.
4
+ *
5
+ * @module
6
+ */
7
+ import type { MllpSocket } from "./types";
8
+ /**
9
+ * One session over one socket, for that socket's lifetime. `ready` resolves
10
+ * once it is open; `destroy()` ends it from any point.
11
+ */
12
+ export interface MllpSession {
13
+ /**
14
+ * Resolves once the session is open. Rejects with
15
+ * {@link MllpConnectionFailedError} or {@link MllpConnectionTimeoutError};
16
+ * when destroyed while opening, with the former, and only once the socket
17
+ * is down. The owner MUST await it, or observe it, before destroying a
18
+ * session that has not opened.
19
+ */
20
+ readonly ready: Promise<void>;
21
+ /**
22
+ * Sends one message, once the session is open, and returns the next one the
23
+ * remote system sends back, unframed. `timeoutMs` bounds the write and the
24
+ * wait for the reply; it does not cover the opening.
25
+ *
26
+ * @throws {MllpInvalidMessageError} The message contains a reserved MLLP
27
+ * byte, so nothing was written.
28
+ * @throws {MllpConnectionFailedError} The session never opened.
29
+ * @throws {MllpConnectionTimeoutError} The session never opened in time.
30
+ * @throws {MllpSendTimeoutError} Nothing came back within `timeoutMs`. The
31
+ * session is destroyed.
32
+ * @throws {MllpSendAbortedError} `destroy()` ended the session while the
33
+ * reply was awaited.
34
+ * @throws {MllpConnectionLostError} The remote system closed, or the stream
35
+ * failed, before a reply came.
36
+ * @throws {MllpCodecError} The remote system sent bytes that are not an MLLP
37
+ * frame.
38
+ */
39
+ exchange(message: Uint8Array, timeoutMs: number): Promise<Uint8Array>;
40
+ /**
41
+ * Ends the session now, and resolves once the socket is down. A `ready`
42
+ * still pending rejects; so does an `exchange` still waiting.
43
+ *
44
+ * Never rejects. Idempotent. Bounded.
45
+ */
46
+ destroy(): Promise<void>;
47
+ }
48
+ export interface ConnectOptions {
49
+ /** Time the remote system has to accept the connection. */
50
+ readonly timeoutMs: number;
51
+ /** Maximum bytes buffered while reading one frame. */
52
+ readonly maxBufferedBytes: number;
53
+ }
54
+ /**
55
+ * Starts opening a session over `socket`, and hands it back at once. The
56
+ * session ends itself when `signal` aborts.
57
+ *
58
+ * Never throws: every failure, an adapter's synchronous throw included,
59
+ * reaches `ready`. Nothing is left open on any path: an attempt that fails or
60
+ * is cancelled closes the socket before `ready` rejects.
61
+ */
62
+ export declare function createSession(socket: MllpSocket, opts: ConnectOptions, signal?: AbortSignal): MllpSession;
63
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,SAAS,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtE;;;;;OAKG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AA8CD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,WAAW,CA0Fb"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The client's phases, and what each one is holding: the session, the message
3
+ * in flight, and the close.
4
+ *
5
+ * Types only. A phase changes by replacing the object, never by mutating it.
6
+ *
7
+ * @module
8
+ */
9
+ import type { MllpClientError } from "./errors";
10
+ import type { MllpSession } from "./session";
11
+ export type State = {
12
+ readonly phase: "idle";
13
+ } | {
14
+ readonly phase: "connecting";
15
+ /** The dialing. Settles as `connect()` does. */
16
+ readonly opening: Promise<void>;
17
+ /** Aborted by `close()` and `destroy()`. Stops the dialing. */
18
+ readonly abort: AbortController;
19
+ } | {
20
+ readonly phase: "connected";
21
+ readonly session: MllpSession;
22
+ } | {
23
+ readonly phase: "sending";
24
+ readonly session: MllpSession;
25
+ /** MSH-10 of the message waiting for its acknowledgment. */
26
+ readonly controlId: string;
27
+ /** Settles when the send is over, however it ended. */
28
+ readonly done: Promise<unknown>;
29
+ } | {
30
+ readonly phase: "closing";
31
+ readonly session: MllpSession;
32
+ /** The send `close()` is waiting out. */
33
+ readonly done: Promise<unknown>;
34
+ } | {
35
+ readonly phase: "closed";
36
+ /** The failure that closed the client, or `null` when the owner did. */
37
+ readonly reason: MllpClientError | null;
38
+ };
39
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,MAAM,KAAK,GACb;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1B;IACE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;CACzC,CAAC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The public types of `@glion/mllp-client`. Types only — no runtime values.
3
+ *
4
+ * @module
5
+ */
6
+ import type { AckSuccessCode } from "@glion/ack";
7
+ import type { Root } from "@glion/ast";
8
+ /** The byte streams an open socket carries. */
9
+ export interface MllpStreams {
10
+ /** Bytes from the remote system. */
11
+ readonly readable: ReadableStream<Uint8Array>;
12
+ /** Bytes to the remote system. */
13
+ readonly writable: WritableStream<Uint8Array>;
14
+ }
15
+ /** A socket to one remote system. */
16
+ export interface MllpSocket {
17
+ /**
18
+ * Opens the socket and returns its byte streams.
19
+ *
20
+ * Rejects with `signal.reason` when cancelled. A rejection leaves nothing
21
+ * open. Called again only after `close()`.
22
+ */
23
+ connect(signal: AbortSignal): Promise<MllpStreams>;
24
+ /**
25
+ * Ends the socket `connect()` opened, and resolves once it is down.
26
+ *
27
+ * Never rejects. Idempotent. Bounded, even when the remote system does not
28
+ * answer. An attempt still in flight is cancelled through its signal.
29
+ */
30
+ close(): Promise<void>;
31
+ }
32
+ /** The client's phase. */
33
+ export type MllpClientState = "closed" | "closing" | "connected" | "connecting" | "idle" | "sending";
34
+ export interface MllpReconnectOptions {
35
+ /**
36
+ * Attempts after a failed one before the client closes. `Infinity` for no
37
+ * limit.
38
+ *
39
+ * @default 5
40
+ */
41
+ readonly attempts?: number;
42
+ /**
43
+ * Time to wait before `attempt` in milliseconds.
44
+ *
45
+ * @default full-jitter exponential backoff from 1 s, capped at 30 s
46
+ */
47
+ readonly delay?: (attempt: number) => number;
48
+ }
49
+ export interface MllpSendOptions {
50
+ /** Overrides the default send deadline: write plus acknowledgment wait. */
51
+ readonly timeoutMs?: number;
52
+ }
53
+ export interface MllpClientOptions {
54
+ /** Runtime adapter; e.g. `nodeSocket` from `@glion/mllp-client/node`. */
55
+ readonly socket: MllpSocket;
56
+ /**
57
+ * Time to wait for the connection to open, in milliseconds.
58
+ *
59
+ * @default 10_000
60
+ */
61
+ readonly connectTimeoutMs?: number;
62
+ /**
63
+ * Time to wait for an acknowledgment after sending, in milliseconds.
64
+ *
65
+ * @default 30_000
66
+ */
67
+ readonly sendTimeoutMs?: number;
68
+ /**
69
+ * Maximum bytes buffered while receiving one message. A remote system that
70
+ * never finishes one is dropped once it exceeds this.
71
+ *
72
+ * @default 16 MiB
73
+ */
74
+ readonly maxBufferedBytes?: number;
75
+ /**
76
+ * How the client dials again after a connection attempt fails. `false`: it
77
+ * dials once.
78
+ *
79
+ * @default 5 attempts with full-jitter backoff, about 30 seconds in all
80
+ */
81
+ readonly reconnect?: MllpReconnectOptions | false;
82
+ }
83
+ /**
84
+ * An acknowledgment that accepted the message: MSA-1 is `AA` or `CA`.
85
+ *
86
+ * A NAK never reaches here — `send()` throws the matching `@glion/ack`
87
+ * exception instead.
88
+ */
89
+ export interface MllpClientResponse {
90
+ /**
91
+ * MSH-10 of the acknowledgment itself, for tracing. MUST NOT be used for
92
+ * correlation; see `controlId`.
93
+ */
94
+ readonly id: string;
95
+ /**
96
+ * MSA-2: the MSH-10 of the message this one answers. HL7v2 names both
97
+ * fields Message Control ID. The only field correlation may use.
98
+ */
99
+ readonly controlId: string;
100
+ /** The acknowledgment, parsed. */
101
+ readonly tree: Root;
102
+ /** The acknowledgment as received, decoded to text. */
103
+ readonly raw: string;
104
+ /** MSA-3: the remote system's own diagnostic, when it gave one. */
105
+ readonly text?: string;
106
+ /** MSA-1: `AA` or `CA`. */
107
+ readonly code: AckSuccessCode;
108
+ }
109
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CAC/C;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACnD;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAID,0BAA0B;AAC1B,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,YAAY,GACZ,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CAC9C;AAED,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,GAAG,KAAK,CAAC;CACnD;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,uDAAuD;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Reading a single value out of an HL7v2 tree.
3
+ *
4
+ * @module
5
+ */
6
+ import type { Root } from "@glion/ast";
7
+ /** The value at `path`, or `""` when the field is absent. */
8
+ export declare function read(tree: Root, path: string): string;
9
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,6DAA6D;AAC7D,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD"}