@glion/mllp-client 0.16.0 → 0.20.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 (92) hide show
  1. package/README.md +466 -193
  2. package/dist/assertions.d.ts +17 -0
  3. package/dist/assertions.d.ts.map +1 -0
  4. package/dist/chunks/mllp-invalid-option-error.js +32 -0
  5. package/dist/chunks/mllp-invalid-option-error.js.map +1 -0
  6. package/dist/client.d.ts +127 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/constants.d.ts +23 -0
  9. package/dist/constants.d.ts.map +1 -0
  10. package/dist/errors/base.d.ts +30 -0
  11. package/dist/errors/base.d.ts.map +1 -0
  12. package/dist/errors/index.d.ts +14 -0
  13. package/dist/errors/index.d.ts.map +1 -0
  14. package/dist/errors/mllp-client-closed-error.d.ts +12 -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 +843 -3
  41. package/dist/index.js.map +1 -0
  42. package/dist/messages.d.ts +37 -0
  43. package/dist/messages.d.ts.map +1 -0
  44. package/dist/queue.d.ts +25 -0
  45. package/dist/queue.d.ts.map +1 -0
  46. package/dist/reconnect.d.ts +18 -0
  47. package/dist/reconnect.d.ts.map +1 -0
  48. package/dist/runtime/node.d.ts +61 -0
  49. package/dist/runtime/node.d.ts.map +1 -0
  50. package/dist/runtime/node.js +121 -0
  51. package/dist/runtime/node.js.map +1 -0
  52. package/dist/runtime/workers.d.ts +33 -0
  53. package/dist/runtime/workers.d.ts.map +1 -0
  54. package/dist/runtime/workers.js +70 -0
  55. package/dist/runtime/workers.js.map +1 -0
  56. package/dist/session.d.ts +63 -0
  57. package/dist/session.d.ts.map +1 -0
  58. package/dist/state.d.ts +42 -0
  59. package/dist/state.d.ts.map +1 -0
  60. package/dist/types.d.ts +109 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/utils.d.ts +9 -0
  63. package/dist/utils.d.ts.map +1 -0
  64. package/package.json +37 -46
  65. package/dist/client.js +0 -549
  66. package/dist/client.js.map +0 -1
  67. package/dist/core/acknowledgment.d.ts +0 -103
  68. package/dist/core/acknowledgment.d.ts.map +0 -1
  69. package/dist/core/client.d.ts +0 -256
  70. package/dist/core/client.d.ts.map +0 -1
  71. package/dist/core/connect.d.ts +0 -101
  72. package/dist/core/connect.d.ts.map +0 -1
  73. package/dist/core/errors.d.ts +0 -81
  74. package/dist/core/errors.d.ts.map +0 -1
  75. package/dist/core/index.d.ts +0 -23
  76. package/dist/core/index.d.ts.map +0 -1
  77. package/dist/core/index.js +0 -2
  78. package/dist/core/internal/ack-parser-stream.d.ts +0 -22
  79. package/dist/core/internal/ack-parser-stream.d.ts.map +0 -1
  80. package/dist/core/internal/subscribe-abort.d.ts +0 -30
  81. package/dist/core/internal/subscribe-abort.d.ts.map +0 -1
  82. package/dist/node.js +0 -214
  83. package/dist/node.js.map +0 -1
  84. package/dist/runtimes/internal/node-error-mapping.d.ts +0 -68
  85. package/dist/runtimes/internal/node-error-mapping.d.ts.map +0 -1
  86. package/dist/runtimes/node.d.ts +0 -47
  87. package/dist/runtimes/node.d.ts.map +0 -1
  88. package/dist/runtimes/node.js +0 -3
  89. package/dist/runtimes/workers.d.ts +0 -64
  90. package/dist/runtimes/workers.d.ts.map +0 -1
  91. package/dist/runtimes/workers.js +0 -143
  92. package/dist/runtimes/workers.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["#listeners","#socket","#connect","#policy","#sendTimeoutMs","#queue","#state","#dial","#send","#destroy","#sendFailed"],"sources":["../src/constants.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-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/queue.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","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 failure. The 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 on a failure, see cause — 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/**\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, and the client's invariants. Each option `assert*`\n * throws {@link MllpInvalidOptionError} for an option a caller passed out of\n * range, before anything happens. {@link assertPhase} throws for a phase the\n * client cannot be in where it is asserted: a bug.\n *\n * @module\n */\n\nimport { MAX_TIMEOUT_MS } from \"./constants\";\nimport { MllpInvalidOptionError } from \"./errors\";\nimport type { State } from \"./state\";\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\n/** `state` is in `phase`; `where` names the site for the report. */\nexport function assertPhase<P extends State[\"phase\"]>(\n state: State,\n phase: P,\n where: string\n): asserts state is Extract<State, { phase: P }> {\n if (state.phase !== phase) {\n throw new Error(\n `${where} found the client ${state.phase}. This is a bug in @glion/mllp-client; please report it.`\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 connection is down and the client will not connect again. `error` is\n * the failure that closed it, or `null` when the owner did.\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 { 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/**\n * `bytes` as the acknowledgment of 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 * @throws {AckException} MSA-1 refused the message. The remote system\n * understood it.\n * @throws {MllpInvalidResponseError} Not a usable acknowledgment of this\n * message: unreadable bytes, an MSA-1 outside Table 0008, or an MSA-2\n * naming another message.\n */\nexport function decode(\n bytes: Uint8Array,\n controlId: string\n): MllpClientResponse {\n let raw: string;\n let tree: Root;\n try {\n raw = decodeBytes(bytes);\n tree = parseHL7v2(raw);\n } catch (error) {\n throw new MllpInvalidResponseError(error, controlId);\n }\n\n const answers = read(tree, \"MSA-2[1].1.1\");\n if (answers !== controlId) {\n throw new MllpInvalidResponseError(\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 throw nakException(tree, code);\n }\n if (!isAckSuccessCode(code)) {\n throw new MllpInvalidResponseError(\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 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}\n","/**\n * The queue of sends: the head is the send in progress, the rest wait their\n * turn in `enter()` order.\n *\n * @module\n */\n\n/** A place in the queue. */\nexport interface Turn {\n /** Resolves when the send may start. `null` when it may start now. */\n readonly wait: Promise<unknown> | null;\n}\n\ninterface Entry extends Turn {\n resolve(): void;\n reject(error: unknown): void;\n}\n\nexport interface Queue {\n /** How many turns wait behind the head. */\n readonly pending: number;\n /** Resolves once every turn has left. At once when none is in. */\n drained(): Promise<void>;\n /** Takes the last place. */\n enter(): Turn;\n /** Gives `turn`'s place up. Starts the next send when `turn` was the head. */\n leave(turn: Turn): void;\n /** Rejects every waiting `Turn` with `error`. The head is not touched. */\n rejectWaiting(error: unknown): void;\n}\n\nexport function createQueue(): Queue {\n const entries: Entry[] = [];\n let empty: PromiseWithResolvers<void> | null = null;\n return {\n async drained() {\n if (entries.length === 0) {\n return;\n }\n empty ??= Promise.withResolvers();\n await empty.promise;\n },\n enter() {\n const { promise, resolve, reject } = Promise.withResolvers();\n const entry: Entry = {\n reject,\n resolve: () => resolve(null),\n wait: entries.length === 0 ? null : promise,\n };\n entries.push(entry);\n return entry;\n },\n leave(turn) {\n const place = entries.indexOf(turn as Entry);\n if (place === -1) {\n throw new Error(\n \"A turn left the queue twice. This is a bug in @glion/mllp-client; please report it.\"\n );\n }\n entries.splice(place, 1);\n if (place === 0) {\n entries[0]?.resolve();\n }\n if (entries.length === 0) {\n empty?.resolve();\n empty = null;\n }\n },\n get pending() {\n return Math.max(0, entries.length - 1);\n },\n rejectWaiting(error) {\n for (const entry of entries.slice(1)) {\n entry.reject(error);\n }\n },\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 assertPhase,\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 MllpClientClosedError,\n MllpClientError,\n MllpConnectionError,\n MllpInvalidResponseError,\n MllpSendAbortedError,\n} from \"./errors\";\nimport { MllpClientEmitter } from \"./events\";\nimport { decode, encode } from \"./messages\";\nimport { createQueue } from \"./queue\";\nimport { defaultReconnectDelay, sleep } from \"./reconnect\";\nimport type { ReconnectPolicy } from \"./reconnect\";\nimport { createSession } from \"./session\";\nimport type { ConnectOptions } from \"./session\";\nimport type { Open, 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 puts one message on the wire at a time.\n * `send()` writes the message, waits for the acknowledgment, checks that it\n * answers this message, and resolves with it. A send arriving while another\n * is in flight waits its turn; sends go out in the order they were called.\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 readonly #queue = createQueue();\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 /** How many sends wait their turn, the one in flight excluded. */\n get pending(): number {\n return this.#queue.pending;\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()` or\n * `destroy()` arrived while the connection was still being established.\n */\n async connect(): Promise<void> {\n const state = this.#state;\n let opening: Promise<void>;\n switch (state.phase) {\n case \"connected\":\n case \"sending\": {\n return;\n }\n case \"closing\":\n case \"closed\": {\n throw new MllpClientClosedError(state.reason);\n }\n case \"connecting\": {\n opening = state.opening;\n break;\n }\n case \"idle\": {\n const abort = new AbortController();\n opening = this.#dial(abort.signal);\n this.#state = { abort, opening, phase: \"connecting\" };\n break;\n }\n }\n try {\n await opening;\n } catch (error) {\n if (error instanceof MllpConnectionError) {\n await this.destroy(error);\n }\n throw error;\n }\n }\n\n /**\n * Sends one message and returns its acknowledgment. Connects first when the\n * client is not connected yet, and waits for the send in flight, if any,\n * before writing.\n *\n * Sends go out in the order `send()` was called. The queue has no bound.\n * `timeoutMs` runs from the moment the write starts, not from the call. A\n * send still waiting when the client closes rejects with\n * {@link MllpClientClosedError}; one waiting behind a dial that fails\n * rejects with the dial's error, as `connect()` does.\n *\n * Nothing is written when an option, phase, or message error is thrown.\n * Every wire failure also closes the client; a NAK does not.\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, or closed while\n * this send was waiting its turn.\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 // Refused before taking a place, and again by connect() at its turn.\n const at = this.#state;\n if (at.phase === \"closing\" || at.phase === \"closed\") {\n throw new MllpClientClosedError(at.reason);\n }\n\n const turn = this.#queue.enter();\n try {\n if (turn.wait !== null) {\n await turn.wait;\n }\n if (!this.connected) {\n await this.connect();\n }\n return await this.#send(controlId, bytes, timeoutMs);\n } finally {\n this.#queue.leave(turn);\n }\n }\n\n /**\n * Ends the connection once the message in flight is acknowledged, and\n * resolves when it is down. New sends, and sends waiting their turn, are\n * refused from the moment this is called. An attempt to connect stops at\n * 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 * If the message it waits for fails, the client closes with that failure;\n * otherwise with `null`.\n */\n async close(): Promise<void> {\n const from = this.#state;\n switch (from.phase) {\n case \"closing\": {\n await from.closed;\n return;\n }\n case \"sending\": {\n const closed = (async () => {\n await this.#queue.drained();\n await this.#destroy(from, null);\n })();\n this.#state = {\n closed,\n phase: \"closing\",\n reason: null,\n session: from.session,\n };\n this.#queue.rejectWaiting(new MllpClientClosedError());\n await closed;\n return;\n }\n case \"idle\":\n case \"connecting\":\n case \"connected\":\n case \"closed\": {\n await this.destroy();\n }\n }\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. The client closes with `reason`: the failure it reports on `close`\n * and on every later call, or `null` for the owner's own decision.\n *\n * Resolves from any phase. Never rejects. Idempotent. Sends waiting their\n * turn are rejected with `reason` when its delivery is `not-sent`, and with\n * {@link MllpClientClosedError} carrying `reason` otherwise. Arriving while\n * the client is already closing, it cuts the message in flight and joins\n * the ending under way; `reason` is recorded when no failure is known yet.\n */\n async destroy(reason: MllpClientError | null = null): Promise<void> {\n const from = this.#state;\n switch (from.phase) {\n case \"closed\": {\n return;\n }\n case \"closing\": {\n if (from.reason === null && reason !== null) {\n this.#state = { ...from, reason };\n }\n // A close() waiting out the message in flight: cut it.\n await from.session?.destroy();\n await from.closed;\n return;\n }\n case \"idle\": {\n this.#state = { phase: \"closed\", reason };\n this.emit(\"close\", reason);\n return;\n }\n case \"connecting\":\n case \"connected\":\n case \"sending\": {\n const closed = this.#destroy(from, reason);\n this.#state = {\n closed,\n phase: \"closing\",\n reason,\n session: \"session\" in from ? from.session : null,\n };\n // A waiter's message was never written, so a failure of unknown\n // delivery is not its own.\n this.#queue.rejectWaiting(\n reason?.delivery === \"not-sent\"\n ? reason\n : new MllpClientClosedError(reason)\n );\n await closed;\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.\n *\n * @throws {MllpClientClosedError} `signal` aborted: the owner closed the\n * client. Nothing is left open.\n * @throws {MllpConnectionFailedError} The policy gave up; the last attempt\n * could not open the socket.\n * @throws {MllpConnectionTimeoutError} The policy gave up; the last attempt\n * did not open in 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(signal.reason);\n }\n const session = createSession(this.#socket, this.#connect, signal);\n try {\n await session.ready;\n } catch (error) {\n if (signal.aborted) {\n throw new MllpClientClosedError(signal.reason);\n }\n if (attempt >= this.#policy.attempts) {\n throw error;\n }\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(signal.reason);\n }\n this.#state = { phase: \"connected\", session };\n this.emit(\"connect\");\n return;\n }\n }\n\n // ── The end ─────────────────────────────────────────────────────────\n\n /**\n * Ends what `from` holds, the dial or the session, then moves to `closed`\n * with the reason `closing` holds by then and reports `close`. A dial is\n * aborted with `reason`.\n *\n * MUST NOT touch `#state` before its first `await`: the caller moves to\n * `closing` after calling it.\n */\n async #destroy(from: Open, reason: MllpClientError | null): Promise<void> {\n switch (from.phase) {\n case \"connecting\": {\n from.abort.abort(reason);\n // Its outcome belongs to the calls waiting on it.\n await Promise.allSettled([from.opening]);\n break;\n }\n case \"connected\":\n case \"sending\": {\n await from.session.destroy();\n break;\n }\n }\n const ending = this.#state;\n assertPhase(ending, \"closing\", \"The teardown\");\n this.#state = { phase: \"closed\", reason: ending.reason };\n this.emit(\"close\", ending.reason);\n }\n\n // ── The exchange ────────────────────────────────────────────────────\n\n /**\n * The `sending` phase: the message on the wire and its acknowledgment,\n * from `connected` and back to it.\n *\n * A failure of unknown delivery closes the client with it. A NAK, or a\n * message refused before the write, leaves the connection in step.\n */\n async #send(\n controlId: string,\n bytes: Uint8Array,\n timeoutMs: number\n ): Promise<MllpClientResponse> {\n const at = this.#state;\n assertPhase(at, \"connected\", \"send() at its turn\");\n const { session } = at;\n this.#state = { phase: \"sending\", session };\n try {\n const reply = await session.exchange(bytes, timeoutMs);\n return decode(reply, controlId);\n } catch (error) {\n return await this.#sendFailed(error, controlId);\n } finally {\n if (this.#state.phase === \"sending\") {\n this.#state = { phase: \"connected\", session };\n }\n }\n }\n\n /**\n * The send of `controlId` failed with `error`. Throws it as the client\n * reports it: a reply the codec could not read is\n * {@link MllpInvalidResponseError}. A failure of unknown delivery, other\n * than a send cut off by `destroy()`, closes the client first.\n *\n * From `closing`, the ending under way is waiting for this send: started,\n * not awaited.\n */\n async #sendFailed(error: unknown, controlId: string): Promise<never> {\n // Bytes that are not MLLP are not an acknowledgment of this message.\n const reason =\n error instanceof MllpCodecError\n ? new MllpInvalidResponseError(error, controlId)\n : error;\n\n // A NAK, or a message refused before the write: the wire is in step.\n if (!(reason instanceof MllpClientError) || reason.delivery !== \"unknown\") {\n throw reason;\n }\n\n // Cut off by destroy(): that call's ending, not this send's failure.\n if (reason instanceof MllpSendAbortedError) {\n throw reason;\n }\n\n // The wire is at an unknown position: the client closes with the failure.\n // From `closing`, the ending under way is waiting for this send, so it is\n // started, not awaited.\n if (this.#state.phase === \"sending\") {\n await this.destroy(reason);\n } else {\n void this.destroy(reason);\n }\n throw reason;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Encoding a message to send, and decoding the acknowledgment that answers it.
3
+ * Pure.
4
+ *
5
+ * @module
6
+ */
7
+ import type { Root } from "@glion/ast";
8
+ import type { MllpClientResponse } from "./types";
9
+ /** A message ready to go out. */
10
+ export interface EncodingResponse {
11
+ /** MSH-10, which the acknowledgment echoes in MSA-2. */
12
+ readonly controlId: string;
13
+ /** The message in canonical HL7v2, encoded. Framing belongs to the connection. */
14
+ readonly bytes: Uint8Array;
15
+ }
16
+ /**
17
+ * `tree` as the message to send, in canonical HL7v2.
18
+ *
19
+ * @throws {MllpInvalidMessageError} The message has no MSH-10, or it could not
20
+ * be serialized or encoded.
21
+ */
22
+ export declare function encode(tree: Root): EncodingResponse;
23
+ /**
24
+ * `bytes` as the acknowledgment of the message `controlId` identifies.
25
+ *
26
+ * MSA-2 is checked before MSA-1 is read: an accept or a NAK is reported only
27
+ * for the message it answers. MSH-9 and the HL7 version are not checked
28
+ * (#668).
29
+ *
30
+ * @throws {AckException} MSA-1 refused the message. The remote system
31
+ * understood it.
32
+ * @throws {MllpInvalidResponseError} Not a usable acknowledgment of this
33
+ * message: unreadable bytes, an MSA-1 outside Table 0008, or an MSA-2
34
+ * naming another message.
35
+ */
36
+ export declare function decode(bytes: Uint8Array, controlId: string): MllpClientResponse;
37
+ //# 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,IAAI,EAAE,MAAM,YAAY,CAAC;AAUvC,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;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,GAChB,kBAAkB,CAwCpB"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The queue of sends: the head is the send in progress, the rest wait their
3
+ * turn in `enter()` order.
4
+ *
5
+ * @module
6
+ */
7
+ /** A place in the queue. */
8
+ export interface Turn {
9
+ /** Resolves when the send may start. `null` when it may start now. */
10
+ readonly wait: Promise<unknown> | null;
11
+ }
12
+ export interface Queue {
13
+ /** How many turns wait behind the head. */
14
+ readonly pending: number;
15
+ /** Resolves once every turn has left. At once when none is in. */
16
+ drained(): Promise<void>;
17
+ /** Takes the last place. */
18
+ enter(): Turn;
19
+ /** Gives `turn`'s place up. Starts the next send when `turn` was the head. */
20
+ leave(turn: Turn): void;
21
+ /** Rejects every waiting `Turn` with `error`. The head is not touched. */
22
+ rejectWaiting(error: unknown): void;
23
+ }
24
+ export declare function createQueue(): Queue;
25
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4BAA4B;AAC5B,MAAM,WAAW,IAAI;IACnB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CACxC;AAOD,MAAM,WAAW,KAAK;IACpB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,4BAA4B;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,8EAA8E;IAC9E,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,0EAA0E;IAC1E,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED,wBAAgB,WAAW,IAAI,KAAK,CA8CnC"}
@@ -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,61 @@
1
+ /**
2
+ * Node runtime adapter for `MllpClient`: one `net.Socket`, or `tls.TLSSocket`
3
+ * when `tls` is set, ended gracefully first and forced after a grace period.
4
+ * Closing resolves in bounded time.
5
+ *
6
+ * @module
7
+ */
8
+ import type { ConnectionOptions } from "node:tls";
9
+ import type { MllpSocket } from "../types";
10
+ /** Default time a socket gets to end gracefully before it is destroyed. */
11
+ export declare const DEFAULT_GRACEFUL_CLOSE_MS = 1000;
12
+ /** Default idle time before the first keepalive probe. */
13
+ export declare const DEFAULT_KEEPALIVE_IDLE_MS = 30000;
14
+ /** TLS settings for {@link nodeSocket}, as Node's `tls.connect()` takes them. */
15
+ export type NodeTlsOptions = Pick<ConnectionOptions, "ca" | "cert" | "key" | "passphrase" | "pfx" | "rejectUnauthorized" | "servername">;
16
+ export interface NodeSocketOptions {
17
+ /** Host name or address of the remote system. */
18
+ readonly host: string;
19
+ /** TCP port of the remote system. */
20
+ readonly port: number;
21
+ /**
22
+ * TLS for the connection. `true`: verify the remote system's certificate
23
+ * against the runtime's trusted CAs, for `host`, sent as the server name
24
+ * (SNI) unless `host` is an IP address. An object: the same, with Node's
25
+ * `tls.connect()` options applied. `ca` replaces the runtime's trusted CAs.
26
+ * `cert` and `key` present a client certificate. `servername` replaces
27
+ * `host` as the server name. `false`: plain TCP.
28
+ *
29
+ * A remote system dialed by IP address MUST present a certificate for that
30
+ * address, or `servername` MUST name one the certificate lists.
31
+ *
32
+ * @default false
33
+ */
34
+ readonly tls?: boolean | NodeTlsOptions;
35
+ /**
36
+ * Time a socket gets to end gracefully before it is destroyed, in
37
+ * milliseconds.
38
+ *
39
+ * @default 1_000.
40
+ */
41
+ readonly gracefulCloseMs?: number;
42
+ /**
43
+ * Idle time before the first keepalive probe, in milliseconds. Once the
44
+ * probes fail, the OS ends the socket, and the next send fails at once with
45
+ * `CONNECTION_LOST` instead of waiting out `sendTimeoutMs`.
46
+ *
47
+ * @default 30_000.
48
+ */
49
+ readonly keepAliveIdleMs?: number;
50
+ }
51
+ /**
52
+ * A TCP socket to one remote system, over Node's `net` module, or its `tls`
53
+ * module when `tls` is set.
54
+ *
55
+ * Each `connect()` dials a fresh socket: Node cannot reconnect one that has
56
+ * been destroyed.
57
+ *
58
+ * @throws {MllpInvalidOptionError} `tls` is neither a boolean nor an object.
59
+ */
60
+ export declare function nodeSocket(opts: NodeSocketOptions): MllpSocket;
61
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/runtime/node.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGlD,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,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,iBAAiB,EACf,IAAI,GACJ,MAAM,GACN,KAAK,GACL,YAAY,GACZ,KAAK,GACL,oBAAoB,GACpB,YAAY,CACf,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAmGD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CA2B9D"}
@@ -0,0 +1,121 @@
1
+ import { t as MllpInvalidOptionError } from "../chunks/mllp-invalid-option-error.js";
2
+ import { connect, isIP } from "node:net";
3
+ import { Duplex } from "node:stream";
4
+ import { connect as connect$1 } from "node:tls";
5
+ //#region src/runtime/node.ts
6
+ /**
7
+ * Node runtime adapter for `MllpClient`: one `net.Socket`, or `tls.TLSSocket`
8
+ * when `tls` is set, ended gracefully first and forced after a grace period.
9
+ * Closing resolves in bounded time.
10
+ *
11
+ * @module
12
+ */
13
+ /** Default time a socket gets to end gracefully before it is destroyed. */
14
+ const DEFAULT_GRACEFUL_CLOSE_MS = 1e3;
15
+ /** Default idle time before the first keepalive probe. */
16
+ const DEFAULT_KEEPALIVE_IDLE_MS = 3e4;
17
+ /**
18
+ * Dials one socket and resolves it once the connection is open: after the TLS
19
+ * handshake when `tls` is set, plain TCP when it is `undefined`.
20
+ *
21
+ * Rejects with the socket's error, or with `signal.reason` when cancelled.
22
+ * Destroys the socket before rejecting.
23
+ */
24
+ function dial(host, port, tls, keepAliveIdleMs, signal) {
25
+ return new Promise((resolve, reject) => {
26
+ signal.throwIfAborted();
27
+ const done = () => {
28
+ socket.removeListener("error", onError);
29
+ signal.removeEventListener("abort", onAbort);
30
+ };
31
+ const onOpened = () => {
32
+ done();
33
+ resolve(socket);
34
+ };
35
+ const onError = (error) => {
36
+ done();
37
+ socket.destroy();
38
+ reject(error);
39
+ };
40
+ const onAbort = () => {
41
+ done();
42
+ socket.destroy();
43
+ reject(signal.reason);
44
+ };
45
+ const servername = isIP(host) ? void 0 : host;
46
+ const socket = tls ? connect$1({
47
+ servername,
48
+ ...tls,
49
+ host,
50
+ port
51
+ }, onOpened) : connect({
52
+ host,
53
+ port
54
+ }, onOpened);
55
+ socket.once("error", onError);
56
+ signal.addEventListener("abort", onAbort, { once: true });
57
+ socket.setNoDelay(true);
58
+ socket.setKeepAlive(true, keepAliveIdleMs);
59
+ });
60
+ }
61
+ /**
62
+ * Ends a socket: FIN first, then destroy if the remote system does not answer.
63
+ *
64
+ * The grace window avoids the FIN-immediately-followed-by-RST sequence that
65
+ * some integration engines log as a protocol error.
66
+ */
67
+ function end(socket, gracefulCloseMs) {
68
+ if (socket.destroyed || socket.closed) return Promise.resolve();
69
+ return new Promise((resolve) => {
70
+ const grace = setTimeout(() => {
71
+ if (!socket.destroyed) socket.destroy();
72
+ }, gracefulCloseMs);
73
+ socket.once("close", () => {
74
+ clearTimeout(grace);
75
+ resolve();
76
+ });
77
+ socket.end();
78
+ });
79
+ }
80
+ /**
81
+ * The TLS settings `dial` applies, or `undefined` for plain TCP.
82
+ *
83
+ * @throws {MllpInvalidOptionError} `tls` is neither a boolean nor an object.
84
+ */
85
+ function tlsSettings(tls) {
86
+ if (tls === void 0 || typeof tls === "boolean") return tls === true ? {} : void 0;
87
+ if (typeof tls !== "object" || tls === null) throw new MllpInvalidOptionError("tls must be true, false, or an object of TLS settings.");
88
+ return tls;
89
+ }
90
+ /**
91
+ * A TCP socket to one remote system, over Node's `net` module, or its `tls`
92
+ * module when `tls` is set.
93
+ *
94
+ * Each `connect()` dials a fresh socket: Node cannot reconnect one that has
95
+ * been destroyed.
96
+ *
97
+ * @throws {MllpInvalidOptionError} `tls` is neither a boolean nor an object.
98
+ */
99
+ function nodeSocket(opts) {
100
+ const gracefulCloseMs = opts.gracefulCloseMs ?? 1e3;
101
+ const keepAliveIdleMs = opts.keepAliveIdleMs ?? 3e4;
102
+ const tls = tlsSettings(opts.tls);
103
+ /** Ends whatever `connect()` last opened. Nothing is open to begin with. */
104
+ let closeOpen = () => Promise.resolve();
105
+ return {
106
+ close: () => closeOpen(),
107
+ async connect(signal) {
108
+ const socket = await dial(opts.host, opts.port, tls, keepAliveIdleMs, signal);
109
+ closeOpen = () => end(socket, gracefulCloseMs);
110
+ const web = Duplex.toWeb(socket);
111
+ return {
112
+ readable: web.readable,
113
+ writable: web.writable
114
+ };
115
+ }
116
+ };
117
+ }
118
+ //#endregion
119
+ export { DEFAULT_GRACEFUL_CLOSE_MS, DEFAULT_KEEPALIVE_IDLE_MS, nodeSocket };
120
+
121
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","names":["connectTls","connectTcp"],"sources":["../../src/runtime/node.ts"],"sourcesContent":["/**\n * Node runtime adapter for `MllpClient`: one `net.Socket`, or `tls.TLSSocket`\n * when `tls` is set, ended gracefully first and forced after a grace period.\n * Closing resolves in bounded time.\n *\n * @module\n */\n\nimport { connect as connectTcp, isIP } from \"node:net\";\nimport type { Socket } from \"node:net\";\nimport { Duplex } from \"node:stream\";\nimport { connect as connectTls } from \"node:tls\";\nimport type { ConnectionOptions } from \"node:tls\";\n\nimport { MllpInvalidOptionError } from \"../errors/mllp-invalid-option-error\";\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\n/** TLS settings for {@link nodeSocket}, as Node's `tls.connect()` takes them. */\nexport type NodeTlsOptions = Pick<\n ConnectionOptions,\n | \"ca\"\n | \"cert\"\n | \"key\"\n | \"passphrase\"\n | \"pfx\"\n | \"rejectUnauthorized\"\n | \"servername\"\n>;\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 * TLS for the connection. `true`: verify the remote system's certificate\n * against the runtime's trusted CAs, for `host`, sent as the server name\n * (SNI) unless `host` is an IP address. An object: the same, with Node's\n * `tls.connect()` options applied. `ca` replaces the runtime's trusted CAs.\n * `cert` and `key` present a client certificate. `servername` replaces\n * `host` as the server name. `false`: plain TCP.\n *\n * A remote system dialed by IP address MUST present a certificate for that\n * address, or `servername` MUST name one the certificate lists.\n *\n * @default false\n */\n readonly tls?: boolean | NodeTlsOptions;\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. Once the\n * probes fail, the OS ends the socket, and the next send fails at once with\n * `CONNECTION_LOST` instead of waiting out `sendTimeoutMs`.\n *\n * @default 30_000.\n */\n readonly keepAliveIdleMs?: number;\n}\n\n/**\n * Dials one socket and resolves it once the connection is open: after the TLS\n * handshake when `tls` is set, plain TCP when it is `undefined`.\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 tls: NodeTlsOptions | undefined,\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 signal.throwIfAborted();\n\n const done = () => {\n socket.removeListener(\"error\", onError);\n signal.removeEventListener(\"abort\", onAbort);\n };\n const onOpened = () => {\n done();\n resolve(socket);\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\n // `tls.connect()` sends SNI only when `servername` is set. An IP address is\n // never a server name (RFC 6066 §3).\n const servername = isIP(host) ? undefined : host;\n const socket = tls\n ? connectTls({ servername, ...tls, host, port }, onOpened)\n : connectTcp({ host, port }, onOpened);\n socket.once(\"error\", onError);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n // No Nagle — MLLP acknowledgments are tiny and latency-sensitive.\n socket.setNoDelay(true);\n socket.setKeepAlive(true, keepAliveIdleMs);\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 * The TLS settings `dial` applies, or `undefined` for plain TCP.\n *\n * @throws {MllpInvalidOptionError} `tls` is neither a boolean nor an object.\n */\nfunction tlsSettings(\n tls: NodeSocketOptions[\"tls\"]\n): NodeTlsOptions | undefined {\n if (tls === undefined || typeof tls === \"boolean\") {\n return tls === true ? {} : undefined;\n }\n if (typeof tls !== \"object\" || tls === null) {\n throw new MllpInvalidOptionError(\n \"tls must be true, false, or an object of TLS settings.\"\n );\n }\n return tls;\n}\n\n/**\n * A TCP socket to one remote system, over Node's `net` module, or its `tls`\n * module when `tls` is set.\n *\n * Each `connect()` dials a fresh socket: Node cannot reconnect one that has\n * been destroyed.\n *\n * @throws {MllpInvalidOptionError} `tls` is neither a boolean nor an object.\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 const tls = tlsSettings(opts.tls);\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(\n opts.host,\n opts.port,\n tls,\n keepAliveIdleMs,\n signal\n );\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":";;;;;;;;;;;;;AAkBA,MAAa,4BAA4B;;AAGzC,MAAa,4BAA4B;;;;;;;;AAyDzC,SAAS,KACP,MACA,MACA,KACA,iBACA,QACiB;AAEjB,QAAO,IAAI,SAAiB,SAAS,WAAW;AAC9C,SAAO,gBAAgB;EAEvB,MAAM,aAAa;AACjB,UAAO,eAAe,SAAS,QAAQ;AACvC,UAAO,oBAAoB,SAAS,QAAQ;;EAE9C,MAAM,iBAAiB;AACrB,SAAM;AACN,WAAQ,OAAO;;EAEjB,MAAM,WAAW,UAAiB;AAChC,SAAM;AACN,UAAO,SAAS;AAChB,UAAO,MAAM;;EAEf,MAAM,gBAAgB;AACpB,SAAM;AACN,UAAO,SAAS;AAChB,UAAO,OAAO,OAAO;;EAKvB,MAAM,aAAa,KAAK,KAAK,GAAG,KAAA,IAAY;EAC5C,MAAM,SAAS,MACXA,UAAW;GAAE;GAAY,GAAG;GAAK;GAAM;GAAM,EAAE,SAAS,GACxDC,QAAW;GAAE;GAAM;GAAM,EAAE,SAAS;AACxC,SAAO,KAAK,SAAS,QAAQ;AAC7B,SAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AAGzD,SAAO,WAAW,KAAK;AACvB,SAAO,aAAa,MAAM,gBAAgB;GAC1C;;;;;;;;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;;;;;;;AAQJ,SAAS,YACP,KAC4B;AAC5B,KAAI,QAAQ,KAAA,KAAa,OAAO,QAAQ,UACtC,QAAO,QAAQ,OAAO,EAAE,GAAG,KAAA;AAE7B,KAAI,OAAO,QAAQ,YAAY,QAAQ,KACrC,OAAM,IAAI,uBACR,yDACD;AAEH,QAAO;;;;;;;;;;;AAYT,SAAgB,WAAW,MAAqC;CAC9D,MAAM,kBAAkB,KAAK,mBAAA;CAC7B,MAAM,kBAAkB,KAAK,mBAAA;CAC7B,MAAM,MAAM,YAAY,KAAK,IAAI;;CAEjC,IAAI,kBAAiC,QAAQ,SAAS;AAEtD,QAAO;EACL,aAAa,WAAW;EAExB,MAAM,QAAQ,QAA2C;GACvD,MAAM,SAAS,MAAM,KACnB,KAAK,MACL,KAAK,MACL,KACA,iBACA,OACD;AACD,qBAAkB,IAAI,QAAQ,gBAAgB;GAE9C,MAAM,MAAM,OAAO,MAAM,OAAO;AAChC,UAAO;IACL,UAAU,IAAI;IACd,UAAU,IAAI;IACf;;EAEJ"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Cloudflare Workers runtime adapter for `MllpClient`: one socket over
3
+ * `cloudflare:sockets`. Framing and stream ownership belong to the layer
4
+ * above.
5
+ *
6
+ * @module
7
+ */
8
+ import type { MllpSocket } from "../types";
9
+ export interface WorkersSocketOptions {
10
+ /** Host name or address of the remote system. */
11
+ readonly host: string;
12
+ /** TCP port of the remote system. */
13
+ readonly port: number;
14
+ /**
15
+ * TLS for the connection. `true`: verify the remote system's certificate
16
+ * against the public CAs Workers trusts, for `host`. `false`: plain TCP.
17
+ *
18
+ * Workers takes no CA, client certificate, or server name. A remote system
19
+ * MUST present a publicly trusted certificate for `host`.
20
+ *
21
+ * @default false
22
+ */
23
+ readonly tls?: boolean;
24
+ }
25
+ /**
26
+ * A TCP socket to one remote system, over `cloudflare:sockets`.
27
+ *
28
+ * Each `connect()` opens a fresh socket.
29
+ *
30
+ * @throws {MllpInvalidOptionError} `tls` is not a boolean.
31
+ */
32
+ export declare function workersSocket(opts: WorkersSocketOptions): MllpSocket;
33
+ //# sourceMappingURL=workers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../src/runtime/workers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAIxD,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,UAAU,CAoDpE"}
@@ -0,0 +1,70 @@
1
+ import { t as MllpInvalidOptionError } from "../chunks/mllp-invalid-option-error.js";
2
+ import { connect } from "cloudflare:sockets";
3
+ //#region src/runtime/workers.ts
4
+ /**
5
+ * Cloudflare Workers runtime adapter for `MllpClient`: one socket over
6
+ * `cloudflare:sockets`. Framing and stream ownership belong to the layer
7
+ * above.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * A TCP socket to one remote system, over `cloudflare:sockets`.
13
+ *
14
+ * Each `connect()` opens a fresh socket.
15
+ *
16
+ * @throws {MllpInvalidOptionError} `tls` is not a boolean.
17
+ */
18
+ function workersSocket(opts) {
19
+ const { tls = false } = opts;
20
+ if (typeof tls !== "boolean") throw new MllpInvalidOptionError("tls must be true or false on Workers, which applies no CA, client certificate, or server name.");
21
+ let open;
22
+ return {
23
+ async close() {
24
+ const socket = open;
25
+ open = void 0;
26
+ await socket?.close();
27
+ },
28
+ async connect(signal) {
29
+ signal.throwIfAborted();
30
+ const socket = connect({
31
+ hostname: opts.host,
32
+ port: opts.port
33
+ }, {
34
+ allowHalfOpen: false,
35
+ secureTransport: tls ? "on" : "off"
36
+ });
37
+ const listener = new AbortController();
38
+ try {
39
+ await Promise.race([socket.opened, aborted(signal, listener.signal)]);
40
+ } catch (error) {
41
+ if (!signal.aborted) throw error;
42
+ socket.close().catch(() => {});
43
+ throw signal.reason;
44
+ } finally {
45
+ listener.abort();
46
+ }
47
+ open = socket;
48
+ return {
49
+ readable: socket.readable,
50
+ writable: socket.writable
51
+ };
52
+ }
53
+ };
54
+ }
55
+ /**
56
+ * Rejects with `signal.reason` once `signal` aborts, unless `until` aborts
57
+ * first.
58
+ */
59
+ function aborted(signal, until) {
60
+ return new Promise((_resolve, reject) => {
61
+ signal.addEventListener("abort", () => reject(signal.reason), {
62
+ once: true,
63
+ signal: until
64
+ });
65
+ });
66
+ }
67
+ //#endregion
68
+ export { workersSocket };
69
+
70
+ //# sourceMappingURL=workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.js","names":[],"sources":["../../src/runtime/workers.ts"],"sourcesContent":["/**\n * Cloudflare Workers runtime adapter for `MllpClient`: one socket over\n * `cloudflare:sockets`. Framing and stream ownership belong to the layer\n * above.\n *\n * @module\n */\n\nimport { connect } from \"cloudflare:sockets\";\n\nimport { MllpInvalidOptionError } from \"../errors/mllp-invalid-option-error\";\nimport type { MllpSocket, MllpStreams } from \"../types\";\n\ntype Socket = ReturnType<typeof connect>;\n\nexport interface WorkersSocketOptions {\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 * TLS for the connection. `true`: verify the remote system's certificate\n * against the public CAs Workers trusts, for `host`. `false`: plain TCP.\n *\n * Workers takes no CA, client certificate, or server name. A remote system\n * MUST present a publicly trusted certificate for `host`.\n *\n * @default false\n */\n readonly tls?: boolean;\n}\n\n/**\n * A TCP socket to one remote system, over `cloudflare:sockets`.\n *\n * Each `connect()` opens a fresh socket.\n *\n * @throws {MllpInvalidOptionError} `tls` is not a boolean.\n */\nexport function workersSocket(opts: WorkersSocketOptions): MllpSocket {\n const { tls = false } = opts;\n if (typeof tls !== \"boolean\") {\n throw new MllpInvalidOptionError(\n \"tls must be true or false on Workers, which applies no CA, client certificate, or server name.\"\n );\n }\n let open: Socket | undefined;\n\n return {\n async close(): Promise<void> {\n const socket = open;\n open = undefined;\n await socket?.close();\n },\n\n async connect(signal: AbortSignal): Promise<MllpStreams> {\n signal.throwIfAborted();\n // `connect()` returns at once with the dial in progress; `opened`\n // settles when the handshake does.\n const socket = connect(\n { hostname: opts.host, port: opts.port },\n { allowHalfOpen: false, secureTransport: tls ? \"on\" : \"off\" }\n );\n // Raced, not awaited: `opened` alone would hold a cancelled attempt\n // until the dial times out. `listener` is the abort listener's\n // lifetime: aborting it removes the listener once the race is decided,\n // so nothing stays subscribed to `signal`.\n const listener = new AbortController();\n try {\n await Promise.race([socket.opened, aborted(signal, listener.signal)]);\n } catch (error) {\n // Two ways here: the dial failed (`opened` rejected), or the caller\n // cancelled. A failed dial is reported as it is. A cancelled one is\n // reported as the cancellation; its socket is closed without waiting,\n // since workerd settles that close only once the dial does, and then\n // with the dial's own failure, which nobody is left to act on.\n if (!signal.aborted) {\n throw error;\n }\n // oxlint-disable-next-line promise/prefer-await-to-then -- awaiting would wait out the dial\n socket.close().catch(() => {});\n throw signal.reason;\n } finally {\n listener.abort();\n }\n // Only an opened socket is held, so `close()` has something to end\n // exactly when `connect()` fulfilled.\n open = socket;\n return { readable: socket.readable, writable: socket.writable };\n },\n };\n}\n\n/**\n * Rejects with `signal.reason` once `signal` aborts, unless `until` aborts\n * first.\n */\nfunction aborted(signal: AbortSignal, until: AbortSignal): Promise<never> {\n // oxlint-disable-next-line promise/avoid-new -- wrapping an abort event\n return new Promise((_resolve, reject) => {\n signal.addEventListener(\"abort\", () => reject(signal.reason), {\n once: true,\n signal: until,\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAgB,cAAc,MAAwC;CACpE,MAAM,EAAE,MAAM,UAAU;AACxB,KAAI,OAAO,QAAQ,UACjB,OAAM,IAAI,uBACR,iGACD;CAEH,IAAI;AAEJ,QAAO;EACL,MAAM,QAAuB;GAC3B,MAAM,SAAS;AACf,UAAO,KAAA;AACP,SAAM,QAAQ,OAAO;;EAGvB,MAAM,QAAQ,QAA2C;AACvD,UAAO,gBAAgB;GAGvB,MAAM,SAAS,QACb;IAAE,UAAU,KAAK;IAAM,MAAM,KAAK;IAAM,EACxC;IAAE,eAAe;IAAO,iBAAiB,MAAM,OAAO;IAAO,CAC9D;GAKD,MAAM,WAAW,IAAI,iBAAiB;AACtC,OAAI;AACF,UAAM,QAAQ,KAAK,CAAC,OAAO,QAAQ,QAAQ,QAAQ,SAAS,OAAO,CAAC,CAAC;YAC9D,OAAO;AAMd,QAAI,CAAC,OAAO,QACV,OAAM;AAGR,WAAO,OAAO,CAAC,YAAY,GAAG;AAC9B,UAAM,OAAO;aACL;AACR,aAAS,OAAO;;AAIlB,UAAO;AACP,UAAO;IAAE,UAAU,OAAO;IAAU,UAAU,OAAO;IAAU;;EAElE;;;;;;AAOH,SAAS,QAAQ,QAAqB,OAAoC;AAExE,QAAO,IAAI,SAAS,UAAU,WAAW;AACvC,SAAO,iBAAiB,eAAe,OAAO,OAAO,OAAO,EAAE;GAC5D,MAAM;GACN,QAAQ;GACT,CAAC;GACF"}
@@ -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"}