@glion/mllp 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +486 -0
  3. package/dist/errors.d.ts +22 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/index.d.ts +9 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +38 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/mllp.js +791 -0
  10. package/dist/mllp.js.map +1 -0
  11. package/dist/node/adapter.d.ts +75 -0
  12. package/dist/node/adapter.d.ts.map +1 -0
  13. package/dist/node/index.d.ts +15 -0
  14. package/dist/node/index.d.ts.map +1 -0
  15. package/dist/node/serve.d.ts +150 -0
  16. package/dist/node/serve.d.ts.map +1 -0
  17. package/dist/node.js +324 -0
  18. package/dist/node.js.map +1 -0
  19. package/dist/server/adapter.d.ts +64 -0
  20. package/dist/server/adapter.d.ts.map +1 -0
  21. package/dist/server/compose.d.ts +15 -0
  22. package/dist/server/compose.d.ts.map +1 -0
  23. package/dist/server/context.d.ts +33 -0
  24. package/dist/server/context.d.ts.map +1 -0
  25. package/dist/server/mllp.d.ts +102 -0
  26. package/dist/server/mllp.d.ts.map +1 -0
  27. package/dist/server/router.d.ts +49 -0
  28. package/dist/server/router.d.ts.map +1 -0
  29. package/dist/server/types.d.ts +139 -0
  30. package/dist/server/types.d.ts.map +1 -0
  31. package/dist/transport/constants.d.ts +24 -0
  32. package/dist/transport/constants.d.ts.map +1 -0
  33. package/dist/transport/decoder-stream.d.ts +49 -0
  34. package/dist/transport/decoder-stream.d.ts.map +1 -0
  35. package/dist/transport/decoder.d.ts +18 -0
  36. package/dist/transport/decoder.d.ts.map +1 -0
  37. package/dist/transport/dynamic-buffer.d.ts +28 -0
  38. package/dist/transport/dynamic-buffer.d.ts.map +1 -0
  39. package/dist/transport/encoder.d.ts +14 -0
  40. package/dist/transport/encoder.d.ts.map +1 -0
  41. package/dist/transport/errors.d.ts +13 -0
  42. package/dist/transport/errors.d.ts.map +1 -0
  43. package/dist/transport/index.d.ts +8 -0
  44. package/dist/transport/index.d.ts.map +1 -0
  45. package/dist/transport/types.d.ts +49 -0
  46. package/dist/transport/types.d.ts.map +1 -0
  47. package/package.json +68 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mllp.js","names":["queryValue","#routes","#globalMiddleware","#scopedMiddleware","#router","#processor","#errorHandler","#handleError"],"sources":["../src/transport/constants.ts","../src/errors.ts","../src/transport/errors.ts","../src/transport/types.ts","../src/transport/dynamic-buffer.ts","../src/transport/decoder-stream.ts","../src/transport/encoder.ts","../src/server/compose.ts","../src/server/context.ts","../src/server/types.ts","../src/server/router.ts","../src/server/mllp.ts"],"sourcesContent":["// -------------\n// MLLP Frame Bytes\n// -------------\n\n/**\n * MLLP Start Byte - Vertical Tab (VT)\n * Marks the beginning of an MLLP frame\n */\nexport const MLLP_START_BYTE = 0x0b;\n\n/**\n * MLLP End Byte 1 - File Separator (FS)\n * First byte of the end sequence\n */\nexport const MLLP_END_BYTE_1 = 0x1c;\n\n/**\n * MLLP End Byte 2 - Carriage Return (CR)\n * Second byte of the end sequence\n */\nexport const MLLP_END_BYTE_2 = 0x0d;\n\n// -------------\n// Pre-computed Uint8Arrays for efficiency\n// -------------\n\n/**\n * MLLP header as a Uint8Array containing the start byte\n */\nexport const MLLP_HEADER = new Uint8Array([MLLP_START_BYTE]);\n\n/**\n * MLLP trailer as a Uint8Array containing the end sequence\n */\nexport const MLLP_TRAILER = new Uint8Array([MLLP_END_BYTE_1, MLLP_END_BYTE_2]);\n","/**\n * Options for constructing an MllpError.\n */\nexport interface MllpErrorOptions extends ErrorOptions {\n /** Error condition string (e.g., for ERR segment in ACK) */\n errorCondition?: string;\n}\n\n/**\n * Base error class for all MLLP errors.\n *\n * All error types in the MLLP package extend this class.\n * Provides a common `code` field so that any MLLP error can be\n * programmatically identified and translated (e.g., into ACK/NAK responses).\n */\nexport class MllpError extends Error {\n /** Machine-readable error code identifying the error type */\n readonly code: string;\n /** Error condition string (e.g., for ERR segment in ACK) */\n readonly errorCondition?: string;\n\n constructor(code: string, message: string, options?: MllpErrorOptions) {\n super(message, options);\n this.name = \"MllpError\";\n this.code = code;\n this.errorCondition = options?.errorCondition;\n }\n}\n","import { MllpError } from \"../errors\";\nimport type { TransportErrorCode } from \"./types\";\n\n/**\n * Transport-level error for MLLP framing issues (invalid framing, incomplete\n * messages, etc.)\n */\nexport class TransportError extends MllpError {\n declare readonly code: TransportErrorCode;\n /** Position in the data where the error occurred (if applicable) */\n readonly position?: number;\n\n constructor(code: TransportErrorCode, message: string, position?: number) {\n super(code, message);\n this.name = \"TransportError\";\n this.position = position;\n }\n}\n","import type { TransportError } from \"./errors\";\n\n/**\n * Input types for encoding - supports both string and binary\n */\nexport type EncoderInput = string | Uint8Array;\n\n/**\n * Decoded message result from MLLP frame\n */\nexport interface DecodedMessage {\n /** The raw message payload (without MLLP framing) */\n data: Uint8Array;\n /** Message decoded as string (convenience) */\n text: string;\n /** Byte length of the original message */\n byteLength: number;\n}\n\n/**\n * Options for the MLLP encoder\n */\nexport interface EncoderOptions {\n /** Encoding for string input (default: 'utf-8') */\n encoding?: string;\n}\n\n/**\n * Options for the MLLP decoder\n */\nexport interface DecoderOptions {\n /** Maximum message size in bytes (optional, no default limit) */\n maxMessageSize?: number;\n /** Encoding for text conversion (default: 'utf-8') */\n encoding?: string;\n /** Error callback for logging/handling errors */\n onError?: (error: TransportError) => void;\n}\n\n/**\n * Error codes for MLLP frame-level errors\n */\nexport const TransportErrorCode = {\n /** Stream ended with incomplete MLLP message */\n INCOMPLETE_MESSAGE: \"INCOMPLETE_MESSAGE\",\n /** Frame does not end with MLLP end sequence (0x1C 0x0D) */\n INVALID_END_SEQUENCE: \"INVALID_END_SEQUENCE\",\n /** Frame does not start with MLLP start byte (0x0B) */\n INVALID_START_BYTE: \"INVALID_START_BYTE\",\n /** Message exceeds configured maximum size */\n MESSAGE_TOO_LARGE: \"MESSAGE_TOO_LARGE\",\n} as const;\n\nexport type TransportErrorCode =\n (typeof TransportErrorCode)[keyof typeof TransportErrorCode];\n","/**\n * A growable byte buffer with geometric capacity doubling.\n *\n * Designed for protocol decoders that accumulate bytes from arbitrarily\n * chunked TCP streams. Amortizes copy cost to O(n) total — the same\n * strategy used by pg-protocol, NATS.js, and Java's ArrayList.\n *\n * The buffer maintains a read offset so consumed bytes can be skipped\n * without shifting data. When the wasted prefix exceeds half the\n * capacity, the buffer auto-compacts by shifting remaining bytes to\n * the front.\n *\n * @internal\n */\nexport class DynamicBuffer {\n private _buffer: Uint8Array;\n private _length = 0;\n private _offset = 0;\n\n constructor(initialCapacity = 4096) {\n this._buffer = new Uint8Array(initialCapacity);\n }\n\n get capacity(): number {\n return this._buffer.length;\n }\n\n get available(): number {\n return this._length - this._offset;\n }\n\n append(chunk: Uint8Array): void {\n const needed = this._length + chunk.length;\n if (needed > this._buffer.length) {\n const newCapacity = Math.max(this._buffer.length * 2, needed);\n const newBuffer = new Uint8Array(newCapacity);\n newBuffer.set(this._buffer.subarray(0, this._length));\n this._buffer = newBuffer;\n }\n this._buffer.set(chunk, this._length);\n this._length += chunk.length;\n }\n\n view(): Uint8Array {\n return this._buffer.subarray(this._offset, this._length);\n }\n\n consume(n: number): void {\n this._offset += n;\n if (this._offset >= this._length) {\n this._offset = 0;\n this._length = 0;\n return;\n }\n if (this._offset > this._buffer.length / 2) {\n this._compact();\n }\n }\n\n reset(): void {\n this._offset = 0;\n this._length = 0;\n }\n\n private _compact(): void {\n const remaining = this._length - this._offset;\n this._buffer.copyWithin(0, this._offset, this._length);\n this._offset = 0;\n this._length = remaining;\n }\n}\n","import { MLLP_END_BYTE_1, MLLP_END_BYTE_2, MLLP_START_BYTE } from \"./constants\";\nimport { DynamicBuffer } from \"./dynamic-buffer\";\nimport { TransportError } from \"./errors\";\nimport type { DecodedMessage, DecoderOptions } from \"./types\";\nimport { TransportErrorCode } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Internal state of the two-state frame decoder.\n *\n * - `WAITING_START` — scanning for VT (0x0B) start byte; any non-start bytes are\n * reported via `onError` and discarded.\n * - `IN_MESSAGE` — accumulating payload bytes until the FS CR (0x1C 0x0D) end\n * sequence is found.\n */\ntype DecoderState = \"WAITING_START\" | \"IN_MESSAGE\";\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nfunction defaultOnError(error: TransportError): void {\n console.warn(`MLLP decode error: [${error.code}] ${error.message}`);\n}\n\n/**\n * Default decoder options, merged with user-provided values via spread.\n *\n * - `encoding` — `\"utf8\"`, the standard encoding for HL7v2.\n * - `onError` — logs to `console.warn`; the stream continues processing.\n */\nconst defaultDecoderOptions: Required<\n Pick<DecoderOptions, \"encoding\" | \"onError\">\n> = {\n encoding: \"utf8\",\n onError: defaultOnError,\n};\n\n// ---------------------------------------------------------------------------\n// Transformer factory\n// ---------------------------------------------------------------------------\n\n/**\n * Build the {@link Transformer} that powers both {@link createDecoderStream}\n * and {@link MLLPDecoderStream}.\n *\n * ## How MLLP framing works\n *\n * Every HL7v2 message on the wire is wrapped in a minimal envelope:\n *\n * <VT> message-payload <FS><CR>\n * 0x0B 0x1C 0x0D\n *\n * Multiple frames can arrive back-to-back within the same TCP stream.\n * The decoder's job is to find each `<VT>…<FS><CR>` envelope, strip\n * the framing bytes, and emit the inner payload as a {@link DecodedMessage}.\n *\n * ## State machine\n *\n * The decoder alternates between two states:\n *\n * ┌──────────────┐ VT found ┌────────────┐\n * │ WAITING_START │──────────────▶│ IN_MESSAGE │\n * └──────┬───────┘ └─────┬──────┘\n * ▲ garbage → onError │\n * │ │ FS CR found → emit message\n * └─────────────────────────────┘\n *\n * 1. **WAITING_START** — Scans byte-by-byte for a VT start byte. Any bytes\n * encountered before the VT are garbage (e.g. noise on the line or a partial\n * connection reset); they are reported via `onError` and discarded.\n * 2. **IN_MESSAGE** — Scans for the two-byte FS CR end sequence. Once found, the\n * bytes between VT and FS CR are the message payload. The decoder emits the\n * message and transitions back to WAITING_START.\n *\n * ## Chunk boundaries\n *\n * TCP delivers data in arbitrarily-sized chunks that rarely align with\n * MLLP frame boundaries. A single chunk may contain:\n *\n * - A fraction of one frame (the rest arrives in the next chunk)\n * - Exactly one frame\n * - Multiple complete frames\n * - A mix of complete frames and a trailing partial frame\n *\n * The transformer handles all of these by appending every incoming\n * chunk to an internal `buffer`, then running `processBuffer()` to\n * extract as many complete frames as possible. Any leftover bytes\n * (a partial frame) stay in the buffer until the next chunk arrives.\n *\n * ## Error recovery\n *\n * The stream is **resilient** — it never throws. All framing errors\n * are reported via the `onError` callback, and the decoder continues\n * scanning for the next valid frame. Error scenarios:\n *\n * - **Garbage before VT** — bytes are skipped, `INVALID_START_BYTE` reported.\n * - **Oversized message** — frame is abandoned, `MESSAGE_TOO_LARGE` reported,\n * scanning resumes.\n * - **Incomplete frame at stream end** — `INCOMPLETE_MESSAGE` reported during\n * `flush()`. This is the only error that can't be recovered from, since there\n * is no more data coming.\n *\n * ## Size enforcement\n *\n * When `maxMessageSize` is set, the decoder checks the payload length\n * in two places to bound memory usage:\n *\n * 1. **Mid-accumulation** — while waiting for the end sequence, if the buffered\n * payload already exceeds the limit, the frame is abandoned immediately.\n * This prevents a missing end sequence from causing unbounded buffer\n * growth.\n * 2. **On completion** — once the end sequence is found, the final payload length\n * is checked before the message is enqueued.\n */\nfunction createDecoderTransformer(options?: DecoderOptions) {\n const opts = { ...defaultDecoderOptions, ...options };\n const textDecoder = new TextDecoder(opts.encoding);\n\n // -------------------------------------------------------------------------\n // Mutable decoder state (scoped to this transformer instance)\n // -------------------------------------------------------------------------\n\n /**\n * Accumulation buffer — grows as chunks arrive, shrinks as frames are\n * consumed.\n */\n const dynBuffer = new DynamicBuffer();\n\n /** Current state machine position. */\n let state: DecoderState = \"WAITING_START\";\n\n /**\n * Byte offset within the buffer view where the current frame's VT start byte\n * lives. Only meaningful when `state === \"IN_MESSAGE\"`.\n */\n let messageStartPos = 0;\n\n // -------------------------------------------------------------------------\n // Buffer scanning helpers\n // -------------------------------------------------------------------------\n\n /**\n * Scan `view` for the next VT (0x0B) at or after `startPos`.\n *\n * @returns The index of the VT byte, or -1 if not found.\n */\n function findStartByte(view: Uint8Array, startPos: number): number {\n for (let i = startPos; i < view.length; i++) {\n if (view[i] === MLLP_START_BYTE) {\n return i;\n }\n }\n return -1;\n }\n\n /**\n * Scan `view` for the two-byte FS CR (0x1C 0x0D) end sequence\n * at or after `startPos`.\n *\n * @returns The index of the FS byte (first byte of the pair), or -1 if not\n * found.\n */\n function findEndSequence(view: Uint8Array, startPos: number): number {\n for (let i = startPos; i < view.length - 1; i++) {\n if (view[i] === MLLP_END_BYTE_1 && view[i + 1] === MLLP_END_BYTE_2) {\n return i;\n }\n }\n return -1;\n }\n\n // -------------------------------------------------------------------------\n // Core processing loop\n // -------------------------------------------------------------------------\n\n /**\n * Drain as many complete frames from the buffer as possible, enqueuing\n * each decoded message into `controller`.\n *\n * Returns when the buffer contains only an incomplete frame (or is\n * empty). The incomplete bytes are kept for the next `transform()` call.\n *\n * The loop uses a local `position` cursor that always resets to 0\n * after a `consume()` call, since consumption shifts the view.\n */\n function processBuffer(\n controller: TransformStreamDefaultController<DecodedMessage>\n ): void {\n let position = 0;\n\n while (true) {\n const view = dynBuffer.view();\n if (position >= view.length) {\n break;\n }\n\n // ── WAITING_START ──────────────────────────────────────────────\n // Look for a VT byte that begins a new MLLP frame.\n if (state === \"WAITING_START\") {\n const startPos = findStartByte(view, position);\n\n if (startPos === -1) {\n // No VT anywhere in the remaining buffer — all garbage.\n if (view.length > 0) {\n opts.onError(\n new TransportError(\n TransportErrorCode.INVALID_START_BYTE,\n `Skipped ${view.length - position} bytes before finding start byte`,\n position\n )\n );\n }\n dynBuffer.reset();\n return;\n }\n\n // Report any garbage bytes that appeared before the VT.\n if (startPos > position) {\n opts.onError(\n new TransportError(\n TransportErrorCode.INVALID_START_BYTE,\n `Skipped ${startPos - position} bytes before start byte`,\n position\n )\n );\n }\n\n // Transition: we found VT, now accumulate the payload.\n state = \"IN_MESSAGE\";\n messageStartPos = startPos;\n position = startPos + 1; // advance past VT\n }\n\n // ── IN_MESSAGE ─────────────────────────────────────────────────\n // We have a VT; now look for the FS CR end sequence.\n if (state === \"IN_MESSAGE\") {\n const endPos = findEndSequence(view, position);\n\n if (endPos === -1) {\n // End sequence not yet in buffer — need more data from the wire.\n //\n // Before returning, check if the partial payload already exceeds\n // maxMessageSize. If so, abandon this frame early to avoid\n // unbounded buffer growth (e.g. a missing end sequence on a\n // very large or malicious stream).\n const currentSize = view.length - messageStartPos - 1;\n if (\n opts.maxMessageSize !== undefined &&\n currentSize > opts.maxMessageSize\n ) {\n opts.onError(\n new TransportError(\n TransportErrorCode.MESSAGE_TOO_LARGE,\n `Message size ${currentSize} exceeds maximum ${opts.maxMessageSize}`,\n messageStartPos\n )\n );\n\n // Abandon this frame. Resume scanning for a new VT starting\n // one byte after the current frame's VT (it might have been\n // a false start).\n state = \"WAITING_START\";\n position = messageStartPos + 1;\n dynBuffer.consume(position);\n position = 0;\n continue;\n }\n\n // Compact: shift the partial frame to the start of the buffer\n // so we don't re-scan already-consumed bytes on the next chunk.\n if (messageStartPos > 0) {\n dynBuffer.consume(messageStartPos);\n messageStartPos = 0;\n }\n return; // wait for more data\n }\n\n // ── Complete frame found ───────────────────────────────────\n // Extract the payload: everything between VT and FS CR.\n //\n // view: ... [VT] [payload bytes...] [FS] [CR] ...\n // ^ ^\n // messageStartPos endPos\n //\n // payload = view[messageStartPos+1 .. endPos)\n //\n const messageData = view.slice(messageStartPos + 1, endPos);\n const messageLength = messageData.length;\n\n // Final size check on the complete payload.\n if (\n opts.maxMessageSize !== undefined &&\n messageLength > opts.maxMessageSize\n ) {\n opts.onError(\n new TransportError(\n TransportErrorCode.MESSAGE_TOO_LARGE,\n `Message size ${messageLength} exceeds maximum ${opts.maxMessageSize}`,\n messageStartPos\n )\n );\n } else {\n // Emit the decoded message downstream.\n controller.enqueue({\n byteLength: messageLength,\n data: messageData,\n text: textDecoder.decode(messageData),\n });\n }\n\n // Advance past the FS CR end sequence (2 bytes) and compact\n // the buffer so the next iteration starts fresh.\n position = endPos + 2;\n state = \"WAITING_START\";\n dynBuffer.consume(position);\n position = 0;\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Transformer interface returned to TransformStream\n // -------------------------------------------------------------------------\n\n return {\n /**\n * Called when the writable side of the stream closes (TCP connection\n * ended). If we're mid-frame, it means the connection dropped before\n * a complete MLLP envelope was received — report it via `onError`.\n */\n flush(_controller: TransformStreamDefaultController<DecodedMessage>): void {\n if (state === \"IN_MESSAGE\" && dynBuffer.available > 0) {\n opts.onError(\n new TransportError(\n TransportErrorCode.INCOMPLETE_MESSAGE,\n \"Stream ended with incomplete MLLP message\",\n messageStartPos\n )\n );\n }\n },\n\n /**\n * Called for each chunk of bytes received from the TCP socket.\n * Appends the chunk to the internal buffer and extracts any\n * complete MLLP frames that are now available.\n */\n transform(\n chunk: Uint8Array,\n controller: TransformStreamDefaultController<DecodedMessage>\n ): void {\n dynBuffer.append(chunk);\n processBuffer(controller);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a `TransformStream` that extracts MLLP messages from a raw\n * byte stream.\n *\n * Pipe a TCP socket's `ReadableStream<Uint8Array>` through this stream\n * to receive one {@link DecodedMessage} per MLLP frame, regardless of\n * how the bytes are chunked on the wire.\n *\n * @example\n * ```typescript\n * import { createDecoderStream } from \"@glion/mllp\";\n *\n * const decoder = createDecoderStream({\n * maxMessageSize: 1024 * 1024, // 1 MB limit\n * onError: (err) => log.warn(err), // custom error handler\n * });\n *\n * const messages = socket.readable.pipeThrough(decoder).getReader();\n *\n * while (true) {\n * const { done, value } = await messages.read();\n * if (done) break;\n * console.log(value.text); // HL7v2 message string\n * }\n * ```;\n *\n * @param options - Optional decoder settings (max size, encoding, error\n * handler).\n * @returns A `TransformStream<Uint8Array, DecodedMessage>`.\n */\nexport function createDecoderStream(\n options?: DecoderOptions\n): TransformStream<Uint8Array, DecodedMessage> {\n return new TransformStream(createDecoderTransformer(options));\n}\n\n/**\n * Class-based variant of {@link createDecoderStream} for cases where\n * a reusable instance is preferred over the factory function.\n *\n * Functionally identical — both delegate to the same internal\n * transformer.\n *\n * @example\n * ```typescript\n * const decoder = new MLLPDecoderStream({ maxMessageSize: 512_000 });\n * socket.readable.pipeThrough(decoder);\n * ```;\n */\nexport class MLLPDecoderStream extends TransformStream<\n Uint8Array,\n DecodedMessage\n> {\n constructor(options?: DecoderOptions) {\n super(createDecoderTransformer(options));\n }\n}\n","import { MLLP_END_BYTE_1, MLLP_END_BYTE_2, MLLP_START_BYTE } from \"./constants\";\nimport type { EncoderInput, EncoderOptions } from \"./types\";\n\n/** Module-level singleton — avoids allocation per encode() call. */\nconst textEncoder = new TextEncoder();\n\n/**\n * Encode a single HL7v2 message into an MLLP frame.\n *\n * Wraps the message with MLLP framing:\n * - Start byte: 0x0B (VT)\n * - End sequence: 0x1C 0x0D (FS + CR)\n */\nexport function encode(\n message: EncoderInput,\n _options?: EncoderOptions\n): Uint8Array {\n const messageBytes =\n typeof message === \"string\" ? textEncoder.encode(message) : message;\n\n // Direct allocation: header (1) + message + trailer (2)\n const frame = new Uint8Array(messageBytes.length + 3);\n frame[0] = MLLP_START_BYTE;\n frame.set(messageBytes, 1);\n frame[frame.length - 2] = MLLP_END_BYTE_1;\n frame[frame.length - 1] = MLLP_END_BYTE_2;\n return frame;\n}\n\n/**\n * Encode multiple HL7v2 messages into concatenated MLLP frames.\n */\nexport function encodeMultiple(\n messages: EncoderInput[],\n options?: EncoderOptions\n): Uint8Array {\n const frames = messages.map((msg) => encode(msg, options));\n let totalLength = 0;\n for (const frame of frames) {\n totalLength += frame.length;\n }\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const frame of frames) {\n result.set(frame, offset);\n offset += frame.length;\n }\n return result;\n}\n","import type { Context, Middleware } from \"./types\";\n\n/**\n * Compose an array of middleware into a single function that executes\n * them in onion-model order (like Hono/Koa).\n *\n * Each middleware receives (ctx, next). Calling next() proceeds to the\n * next middleware. If a middleware returns a Response without\n * calling next(), it short-circuits the chain.\n *\n * Response handling follows Hono's pattern: return values are written\n * to ctx.res, and middleware can also set ctx.res directly.\n * Last write wins.\n */\nexport function compose(\n middlewares: Middleware[]\n): (ctx: Context) => Promise<void> {\n return async (ctx: Context): Promise<void> => {\n let index = -1;\n\n async function dispatch(i: number): Promise<void> {\n if (i <= index) {\n throw new Error(\"next() called multiple times\");\n }\n index = i;\n\n if (i >= middlewares.length) {\n return;\n }\n\n const middleware = middlewares[i] as Middleware;\n const result = await middleware(ctx, () => dispatch(i + 1));\n\n // If middleware returned a response, set ctx.res (last write wins)\n if (\n result &&\n typeof result === \"object\" &&\n \"raw\" in result &&\n typeof result.raw === \"string\"\n ) {\n ctx.res = result;\n }\n }\n\n await dispatch(0);\n };\n}\n","import type { Root } from \"@glion/ast\";\nimport { value as queryValue } from \"@glion/util-query\";\nimport { VFile } from \"vfile\";\n\nimport type {\n ConnectionInfo,\n Context,\n Hl7v2Processor,\n Response,\n} from \"./types\";\n\n/**\n * Options for creating a Context instance.\n */\nexport interface CreateContextOptions {\n /** Raw HL7 message string */\n raw: string;\n /** Raw bytes from the MLLP frame payload */\n bytes: Uint8Array;\n /** Connection metadata */\n connection: ConnectionInfo;\n /** HL7v2 unified processor */\n processor: Hl7v2Processor;\n}\n\n/**\n * Create a new Context for an incoming message.\n *\n * Only the **parse** step runs eagerly (sync, fast). Routing fields are\n * extracted as strings from the parsed tree immediately — these cached\n * strings always reflect the original wire values (see ADR-0013).\n *\n * The expensive pipeline stages are lazy:\n *\n * - `ctx.tree()` — triggers `run()` (transformers: annotations, escape decoding,\n * lint). Cached after first call.\n * - `ctx.result()` — triggers `run()` + `stringify()` (compilation, e.g., JSON\n * via hl7v2Jsonify). Cached after first call.\n *\n * If no handler matches or middleware short-circuits before accessing\n * the tree, the transform and compile steps never execute.\n */\nexport function createContext(options: CreateContextOptions): Context {\n const { raw, bytes, connection, processor } = options;\n const variables = new Map<string, unknown>();\n let varSnapshot: Readonly<Record<string, unknown>> | undefined;\n\n // ── Eager: parse (sync, fast) ──────────────────────────────────────\n // VFile carries the input through the pipeline and collects diagnostics.\n const file = new VFile(raw);\n const parsed = processor.parse(file);\n\n // Extract routing fields as strings from the parsed tree.\n // These are cached immediately and always reflect the original\n // wire values, even after transformers enrich the tree in-place.\n const controlId = queryValue(parsed, \"MSH-10\")?.value ?? \"\";\n const messageType = queryValue(parsed, \"MSH-9.1\")?.value ?? \"\";\n const triggerEvent = queryValue(parsed, \"MSH-9.2\")?.value ?? \"\";\n const messageStructure = queryValue(parsed, \"MSH-9.3\")?.value ?? \"\";\n const version = queryValue(parsed, \"MSH-12.1\")?.value ?? \"\";\n\n // ── Lazy: transform (async, cached) ────────────────────────────────\n // run() applies all transformers: annotations, escape decoding, lint.\n // Only executes on first call to ctx.tree() or ctx.result().\n let transformedTree: Root | undefined;\n let transformPromise: Promise<Root> | undefined;\n\n function getTree(): Promise<Root> {\n if (transformedTree) {\n return Promise.resolve(transformedTree);\n }\n if (!transformPromise) {\n transformPromise = (async () => {\n const result = await processor.run(parsed, file);\n transformedTree = result;\n return result;\n })();\n }\n return transformPromise;\n }\n\n // ── Lazy: compile (async, cached) ──────────────────────────────────\n // stringify() compiles the tree into the final format (e.g., JSON).\n // Only executes on first call to ctx.result(). Requires tree first.\n // Promise is cached (including rejections) to match getTree() behavior.\n let resultPromise: Promise<unknown | undefined> | undefined;\n\n function getResult(): Promise<unknown | undefined> {\n if (!resultPromise) {\n resultPromise = (async () => {\n const tree = await getTree();\n return processor.compiler ? processor.stringify(tree, file) : undefined;\n })();\n }\n return resultPromise;\n }\n\n // ── Build context ──────────────────────────────────────────────────\n const ctx: Context = {\n ast: parsed,\n connection: Object.freeze(connection),\n controlId,\n file,\n get<K extends string>(key: K): unknown {\n return variables.get(key);\n },\n messageStructure,\n messageType,\n req: Object.freeze({ bytes, raw }),\n res: undefined as Response | undefined,\n result: getResult,\n set<K extends string>(key: K, value: unknown): void {\n variables.set(key, value);\n varSnapshot = undefined;\n },\n tree: getTree,\n triggerEvent,\n get var(): Readonly<Record<string, unknown>> {\n if (!varSnapshot) {\n varSnapshot = Object.freeze(Object.fromEntries(variables));\n }\n return varSnapshot;\n },\n version,\n };\n\n return ctx;\n}\n","import type { Root } from \"@glion/ast\";\nimport type { Processor } from \"unified\";\nimport type { VFile } from \"vfile\";\n\n/**\n * Connection metadata for an MLLP socket.\n */\nexport interface ConnectionInfo {\n readonly id: number;\n readonly remoteAddress: string;\n readonly remotePort: number;\n readonly localPort: number;\n readonly secure: boolean;\n readonly state: Map<string, unknown>;\n}\n\n/**\n * MLLP response — a raw HL7v2 string that the server encodes\n * with MLLP framing before sending back over TCP.\n *\n * Acknowledgment helpers (ack/nak) are not built in — register\n * an acknowledgment middleware via `.use()` to generate ACK/NAK responses.\n */\nexport interface Response {\n readonly raw: string;\n}\n\n/**\n * The MLLP context object.\n *\n * Routing fields (messageType, triggerEvent, etc.) are extracted from\n * the **pre-transform** parsed tree and are always available synchronously.\n *\n * The transformed tree, VFile, and compiled result are lazy — they\n * trigger pipeline stages on first access. See ADR-0013.\n */\nexport interface Context {\n /** Incoming message data */\n readonly req: {\n /** Original HL7 message string (without MLLP framing) */\n readonly raw: string;\n /** Raw bytes from MLLP frame payload */\n readonly bytes: Uint8Array;\n };\n\n /** TCP connection metadata */\n readonly connection: ConnectionInfo;\n\n /**\n * Parsed AST — always available synchronously. Use this for reading\n * MSH fields, building ACKs, routing, or any operation that doesn't\n * need to wait for transforms to complete.\n *\n * Note: this is the same object reference as the tree returned by\n * `tree()`. After `tree()` resolves, transformers may have enriched\n * it in-place (e.g., decoded escapes, resolved message structure).\n * The routing fields (messageType, triggerEvent, etc.) are cached\n * as separate string properties and always reflect the original\n * wire values regardless of transforms.\n */\n readonly ast: Root;\n\n /**\n * Transformed AST. Triggers `run()` (transformers) on first call.\n * Cached after first access — subsequent calls return the same tree.\n */\n tree(): Promise<Root>;\n\n /**\n * VFile with diagnostics and lint messages. Created during context\n * creation. Diagnostics accumulate after `tree()` triggers transformers.\n */\n file: VFile;\n\n /**\n * Compiled result from the processor's compiler (e.g., JSON from\n * hl7v2Jsonify). Triggers both `run()` and `stringify()` on first call.\n * Undefined when the processor has no compiler.\n * Cached after first access.\n */\n result(): Promise<unknown | undefined>;\n\n /** Response to send back. Set by middleware or handler. */\n res: Response | undefined;\n\n /** MSH-9.1 message type (e.g., \"ADT\") — from pre-transform parse */\n messageType: string;\n /** MSH-9.2 trigger event (e.g., \"A01\") — from pre-transform parse */\n triggerEvent: string;\n /** MSH-9.3 message structure (e.g., \"ADT_A01\") — from pre-transform parse */\n messageStructure: string;\n /** MSH-12 version (e.g., \"2.5.1\") — from pre-transform parse */\n version: string;\n /** MSH-10 message control ID — from pre-transform parse */\n controlId: string;\n\n /** Store a variable */\n set<K extends string>(key: K, value: unknown): void;\n /** Retrieve a variable */\n get<K extends string>(key: K): unknown;\n /** Read-only snapshot of all variables */\n readonly var: Readonly<Record<string, unknown>>;\n}\n\n/**\n * A unified `Processor` that parses HL7v2 messages into `Root` trees.\n *\n * Only `ParseTree` is constrained — the remaining type params are left\n * loose so that any processor built with `unified().use(hl7v2Parser)`\n * is assignable regardless of which transformer/compiler plugins are added.\n */\nexport type Hl7v2Processor = Processor<Root, Root, Root>;\n\n/**\n * Middleware function signature (Hono/Koa onion model).\n */\nexport type MiddlewareReturn =\n // oxlint-disable-next-line typescript/no-invalid-void-type\n | Promise<Response | undefined | void>\n | Promise<void>\n | Response\n | undefined\n // oxlint-disable-next-line typescript/no-invalid-void-type\n | void;\n\nexport type Middleware = (\n ctx: Context,\n next: () => Promise<void>\n) => MiddlewareReturn;\n\n/**\n * Terminal route handler — may return a response or void (no response).\n */\nexport type Handler = (\n ctx: Context\n // oxlint-disable-next-line typescript/no-invalid-void-type\n) => Promise<Response | undefined | void> | Response | undefined | void;\n\n/**\n * Error handler — receives the error and context, returns a response.\n */\nexport type ErrorHandler = (\n err: Error,\n ctx: Context\n // oxlint-disable-next-line typescript/no-invalid-void-type\n) => Promise<Response | undefined | void> | Response | undefined | void;\n\n/**\n * A filter function for route or middleware matching.\n * Receives the full context and returns true if the route/middleware applies.\n */\nexport type RouteFilter = (ctx: Context) => boolean;\n\n/**\n * Parsed route pattern for matching against incoming messages.\n */\nexport interface RoutePattern {\n /** Original pattern string (e.g., \"ADT^A01\") */\n readonly source: string;\n /** Message type to match, or \"*\" for wildcard */\n readonly messageType: string;\n /** Trigger event to match, or \"*\" for wildcard */\n readonly triggerEvent: string;\n}\n\n/**\n * Parse a pattern string like \"ADT^A01\", \"ADT^*\", \"*^A01\", or \"*\"\n * into a structured RoutePattern.\n */\nexport function parsePattern(pattern: string): RoutePattern {\n if (pattern === \"*\") {\n return { messageType: \"*\", source: pattern, triggerEvent: \"*\" };\n }\n\n const caretIndex = pattern.indexOf(\"^\");\n if (caretIndex === -1) {\n return { messageType: pattern, source: pattern, triggerEvent: \"*\" };\n }\n\n return {\n messageType: pattern.slice(0, caretIndex) || \"*\",\n source: pattern,\n triggerEvent: pattern.slice(caretIndex + 1) || \"*\",\n };\n}\n\n/**\n * Test whether a route pattern matches a given message type and trigger event.\n */\nexport function matchPattern(\n pattern: RoutePattern,\n messageType: string,\n triggerEvent: string\n): boolean {\n const typeMatch =\n pattern.messageType === \"*\" || pattern.messageType === messageType;\n const eventMatch =\n pattern.triggerEvent === \"*\" || pattern.triggerEvent === triggerEvent;\n return typeMatch && eventMatch;\n}\n","import { matchPattern, parsePattern } from \"./types\";\nimport type { Context, Handler, Middleware, RouteFilter } from \"./types\";\n\n/**\n * A route entry: matcher + handler.\n */\ninterface Route {\n filter: (ctx: Context) => boolean;\n handler: Handler;\n}\n\n/**\n * A scoped middleware entry: matcher + middleware.\n */\ninterface ScopedMiddleware {\n filter: (ctx: Context) => boolean;\n middleware: Middleware;\n}\n\n/**\n * Result of matching a message against registered routes and middleware.\n */\nexport interface MatchResult {\n /** The matched handler, or undefined if no route matches */\n handler: Handler | undefined;\n /** Middleware that applies to this message (global + scoped matches) */\n middlewares: Middleware[];\n}\n\n/**\n * Create a matcher function from a string pattern.\n */\nfunction patternMatcher(pattern: string): (ctx: Context) => boolean {\n const parsed = parsePattern(pattern);\n return (ctx) => matchPattern(parsed, ctx.messageType, ctx.triggerEvent);\n}\n\n/**\n * Pattern-matching router for HL7v2 message types.\n *\n * Routes are matched in registration order (first match wins).\n * Supports string patterns with wildcards or filter functions.\n *\n * String patterns:\n *\n * - `\"ADT^A01\"` — exact match\n * - `\"ADT^*\"` — any ADT message\n * - `\"*\"` — catch-all\n *\n * Filter functions:\n *\n * - `(ctx) => ctx.messageType === \"ADT\" && ctx.version === \"2.5.1\"`\n */\nexport class Router {\n readonly #routes: Route[] = [];\n readonly #globalMiddleware: Middleware[] = [];\n readonly #scopedMiddleware: ScopedMiddleware[] = [];\n\n /**\n * Register a route handler with a string pattern or filter function.\n */\n add(patternOrFilter: string | RouteFilter, handler: Handler): void {\n const filter =\n typeof patternOrFilter === \"string\"\n ? patternMatcher(patternOrFilter)\n : patternOrFilter;\n\n this.#routes.push({ filter, handler });\n }\n\n /**\n * Register global middleware (runs for all messages).\n * When `prepend` is true, the middleware is inserted at the front\n * of the chain so it executes outermost (first in, last out).\n */\n addMiddleware(middleware: Middleware, prepend?: boolean): void;\n /**\n * Register scoped middleware with a string pattern or filter function.\n */\n addMiddleware(\n patternOrFilter: string | RouteFilter,\n middleware: Middleware\n ): void;\n addMiddleware(\n patternOrFilterOrMiddleware: string | RouteFilter | Middleware,\n middlewareOrPrepend?: Middleware | boolean\n ): void {\n // Global middleware: addMiddleware(fn) or addMiddleware(fn, true)\n if (\n typeof patternOrFilterOrMiddleware === \"function\" &&\n (middlewareOrPrepend === undefined ||\n typeof middlewareOrPrepend === \"boolean\")\n ) {\n if (middlewareOrPrepend === true) {\n this.#globalMiddleware.unshift(\n patternOrFilterOrMiddleware as Middleware\n );\n } else {\n this.#globalMiddleware.push(patternOrFilterOrMiddleware as Middleware);\n }\n return;\n }\n\n // Scoped middleware: addMiddleware(pattern/filter, fn)\n if (typeof middlewareOrPrepend === \"function\") {\n const middleware = middlewareOrPrepend;\n const filter =\n typeof patternOrFilterOrMiddleware === \"string\"\n ? patternMatcher(patternOrFilterOrMiddleware)\n : (patternOrFilterOrMiddleware as RouteFilter);\n\n this.#scopedMiddleware.push({ filter, middleware });\n }\n }\n\n /**\n * Match a context against registered routes and middleware.\n * Returns the first matching handler and all applicable middleware.\n */\n match(ctx: Context): MatchResult {\n // Find first matching route (registration order)\n let handler: Handler | undefined;\n for (const route of this.#routes) {\n if (route.filter(ctx)) {\n handler = route.handler;\n break;\n }\n }\n\n // Collect applicable middleware: global + matching scoped\n const middlewares: Middleware[] = [...this.#globalMiddleware];\n for (const scoped of this.#scopedMiddleware) {\n if (scoped.filter(ctx)) {\n middlewares.push(scoped.middleware);\n }\n }\n\n return { handler, middlewares };\n }\n}\n","import { MllpError } from \"../errors\";\nimport { compose } from \"./compose\";\nimport { createContext } from \"./context\";\nimport { Router } from \"./router\";\nimport type {\n ConnectionInfo,\n Context,\n ErrorHandler,\n Handler,\n Hl7v2Processor,\n Middleware,\n Response,\n RouteFilter,\n} from \"./types\";\n\n/**\n * Routing fields from the message that caused an error.\n * Attached to errors thrown by `Mllp.handle()` so that callers\n * (e.g., `serve()`) can include message context in error callbacks.\n */\nexport interface MessageInfo {\n readonly messageType: string;\n readonly triggerEvent: string;\n readonly messageStructure: string;\n readonly version: string;\n readonly controlId: string;\n}\n\n/**\n * Internal store for associating message routing info with errors.\n * Uses a WeakMap so error objects can be garbage-collected normally.\n */\nconst errorMessageInfo = new WeakMap<Error, MessageInfo>();\n\n/**\n * Retrieve the {@link MessageInfo} associated with an error thrown\n * by `Mllp.handle()`. Returns `undefined` for errors that did not\n * originate from message processing (e.g., lifecycle callback errors).\n */\nexport function getMessageInfo(error: Error): MessageInfo | undefined {\n return errorMessageInfo.get(error);\n}\n\n/**\n * MLLP application for HL7v2 messaging.\n *\n * A pure routing and middleware engine with no TCP/server concerns.\n * Use `serve()` to bind this to a TCP server.\n *\n * @example\n * ```typescript\n * import { Mllp } from \"@glion/mllp\";\n * import { parseHL7v2 } from \"@glion/hl7v2\";\n *\n * const app = new Mllp().parser(parseHL7v2).on(\"ADT^A01\", async (ctx) => {\n * const tree = await ctx.tree();\n * return { raw: \"...\" };\n * });\n * ```;\n */\nexport class Mllp {\n readonly #router = new Router();\n #processor: Hl7v2Processor | undefined;\n #errorHandler: ErrorHandler | undefined;\n\n /**\n * Register an HL7v2 processor for incoming messages.\n *\n * Accepts a unified `Processor` — either a pre-built one like\n * `parseHL7v2` from `@glion/hl7v2`, or a custom composition\n * via `unified().use(hl7v2Parser).use(...)`.\n *\n * Must be called before `handle()`. Calling multiple times replaces\n * the previous processor (last-write-wins).\n */\n parser(processor: Hl7v2Processor): this {\n this.#processor = processor;\n return this;\n }\n\n /**\n * Register middleware.\n *\n * Accepts:\n *\n * - A middleware function: `(ctx, next) => { ... }`\n * - A scoped middleware: `app.use('ADT^*', middleware)` or `app.use(filter,\n * middleware)`\n */\n use(middleware: Middleware, options?: { prepend?: boolean }): this;\n use(patternOrFilter: string | RouteFilter, middleware: Middleware): this;\n use(\n first: string | RouteFilter | Middleware,\n second?: Middleware | { prepend?: boolean }\n ): this {\n // Scoped middleware: use('ADT^*', middleware) or use(filter, middleware)\n if (\n (typeof first === \"string\" || typeof first === \"function\") &&\n typeof second === \"function\"\n ) {\n this.#router.addMiddleware(first as string | RouteFilter, second);\n return this;\n }\n\n // Standard middleware: use((ctx, next) => { ... })\n if (typeof first === \"function\") {\n const prepend =\n second && typeof second === \"object\" ? second.prepend : false;\n this.#router.addMiddleware(first as Middleware, prepend);\n return this;\n }\n\n return this;\n }\n\n /**\n * Register a route handler for a message type pattern or filter function.\n *\n * String patterns:\n *\n * - `\"ADT^A01\"` — exact match\n * - `\"ADT^*\"` — any ADT message\n * - `\"*^A01\"` — any type with A01 trigger\n * - `\"*\"` — catch-all\n *\n * Filter functions:\n *\n * - `(ctx) => ctx.messageType === \"ADT\" && ctx.version === \"2.5.1\"`\n */\n on(patternOrFilter: string | RouteFilter, handler: Handler): this {\n this.#router.add(patternOrFilter, handler);\n return this;\n }\n\n /**\n * Register a global error handler.\n *\n * Called when middleware or a handler throws. Without an error handler,\n * the error is re-thrown to the caller (e.g. `serve()`).\n *\n * If the error handler returns a response, it is used as the reply.\n * If the error handler itself throws, the new error is re-thrown.\n */\n onError(handler: ErrorHandler): this {\n this.#errorHandler = handler;\n return this;\n }\n\n /**\n * Process a raw HL7v2 message through the middleware chain and router.\n * This is the integration point — analogous to Hono's `fetch()`.\n *\n * The pipeline is lazy (see ADR-0013):\n * 1. Parse (sync, fast) — always runs, extracts routing fields\n * 2. Route match — uses pre-transform routing fields\n * 3. If no match → return undefined (no transform/compile cost)\n * 4. Transform/compile — only when handlers access ctx.tree()/ctx.result()\n *\n * Throws `MllpError` if no processor has been registered via `app.parser()`.\n */\n async handle(\n raw: string,\n bytes: Uint8Array,\n connection: ConnectionInfo\n // oxlint-disable-next-line typescript/no-invalid-void-type\n ): Promise<Response | undefined | void> {\n if (!this.#processor) {\n throw new MllpError(\n \"ERR_NO_PARSER\",\n \"No parser registered. Call app.parser() before handling messages.\"\n );\n }\n\n // Context creation is sync — only parse() runs here.\n // Transform and compile are deferred to ctx.tree() / ctx.result().\n const ctx = createContext({\n bytes,\n connection,\n processor: this.#processor,\n raw,\n });\n\n try {\n // Match route and collect middleware\n const match = this.#router.match(ctx);\n const middlewares = [...match.middlewares];\n\n // Add the handler as the terminal middleware\n if (match.handler) {\n const handler = match.handler;\n middlewares.push((handlerCtx: Context) => handler(handlerCtx));\n }\n\n // Compose and execute\n await compose(middlewares)(ctx);\n\n return ctx.res;\n } catch (error) {\n return this.#handleError(\n error instanceof Error ? error : new Error(String(error)),\n ctx\n );\n }\n }\n\n /**\n * Handle an error during message processing.\n *\n * If a custom error handler is registered, delegates to it.\n * If the error handler itself throws, re-throws the error handler's error.\n * If no error handler is registered, re-throws the original error.\n *\n * In both re-throw cases, message routing fields are attached to the\n * error via {@link getMessageInfo} so callers can include message\n * context in error reporting.\n *\n * Callers (e.g. `serve()`) are responsible for catching these errors\n * and deciding how to handle them at the transport level.\n */\n async #handleError(\n err: Error,\n ctx: Context\n // oxlint-disable-next-line typescript/no-invalid-void-type\n ): Promise<Response | undefined | void> {\n const info: MessageInfo = {\n controlId: ctx.controlId,\n messageStructure: ctx.messageStructure,\n messageType: ctx.messageType,\n triggerEvent: ctx.triggerEvent,\n version: ctx.version,\n };\n\n if (this.#errorHandler) {\n try {\n return await this.#errorHandler(err, ctx);\n } catch (handlerError) {\n const e =\n handlerError instanceof Error\n ? handlerError\n : new Error(String(handlerError));\n errorMessageInfo.set(e, info);\n throw e;\n }\n }\n\n errorMessageInfo.set(err, info);\n throw err;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Node.js TCP/TLS adapter for the MLLP server.
3
+ *
4
+ * Implements the {@link TcpAdapter} interface using Node.js built-in
5
+ * `net` and `tls` modules. Incoming `net.Socket` instances are wrapped
6
+ * into Web Streams-based {@link AdapterSocket} objects so that the rest
7
+ * of the MLLP stack can work with a platform-agnostic streaming API.
8
+ *
9
+ * This module is internal to the `@glion/mllp` package and
10
+ * is consumed by {@link serve} in `./serve.ts`.
11
+ *
12
+ * @module
13
+ */
14
+ import type { TcpAdapter } from "../server/adapter";
15
+ /**
16
+ * Options specific to the Node.js adapter that control per-socket behaviour.
17
+ *
18
+ * @internal
19
+ */
20
+ export interface NodeAdapterOptions {
21
+ /**
22
+ * Socket inactivity timeout in milliseconds.
23
+ *
24
+ * When set to a value greater than `0`, sockets that remain idle for
25
+ * longer than this duration are automatically destroyed. Set to `0`
26
+ * (the default) to disable idle timeouts entirely.
27
+ *
28
+ * @default 0
29
+ */
30
+ socketTimeout?: number;
31
+ /**
32
+ * Whether to enable TCP keep-alive probes on accepted sockets.
33
+ *
34
+ * When `true` the operating system will periodically send keep-alive
35
+ * packets to detect dead peers and prevent intermediate network
36
+ * equipment from dropping idle connections.
37
+ *
38
+ * @default true
39
+ */
40
+ keepAlive?: boolean;
41
+ /**
42
+ * The initial delay in milliseconds before the first TCP keep-alive
43
+ * probe is sent on an idle socket. Only meaningful when
44
+ * {@link keepAlive} is `true`.
45
+ *
46
+ * @default 60000
47
+ */
48
+ keepAliveInitialDelay?: number;
49
+ }
50
+ /**
51
+ * Create a Node.js {@link TcpAdapter} that can listen for TCP or TLS
52
+ * connections using the built-in `net` and `tls` modules.
53
+ *
54
+ * When the returned adapter's `listen()` method is called with a `tls`
55
+ * option, a TLS server (`tls.createServer`) is used; otherwise a plain
56
+ * TCP server (`net.createServer`) is created.
57
+ *
58
+ * Each accepted socket is configured via {@link configureSocket} (timeouts,
59
+ * keep-alive, Nagle disabled) and then wrapped into an {@link AdapterSocket}
60
+ * before being handed to the provided {@link ConnectionHandler}.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const adapter = nodeAdapter({ socketTimeout: 30_000 });
65
+ * const handle = adapter.listen({ port: 2575 }, (socket) => {
66
+ * // handle connection
67
+ * });
68
+ * ```;
69
+ *
70
+ * @param options - Optional per-socket configuration (timeouts, keep-alive).
71
+ * @returns A {@link TcpAdapter} ready to bind to a port.
72
+ * @internal
73
+ */
74
+ export declare function nodeAdapter(options?: NodeAdapterOptions): TcpAdapter;
75
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/node/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,OAAO,KAAK,EAIV,UAAU,EAEX,MAAM,mBAAmB,CAAC;AAE3B;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAsED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,UAAU,CAiGpE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Node.js runtime adapter for `@glion/mllp`.
3
+ *
4
+ * Import from `@glion/mllp/node` to start an MLLP
5
+ * server on Node.js.
6
+ *
7
+ * @module
8
+ * @example
9
+ * ```typescript
10
+ * import { serve } from "@glion/mllp/node";
11
+ * ```;
12
+ */
13
+ export { serve } from "./serve";
14
+ export type { ConnectionCallback, ErrorCallback, Server, ServeOptions, } from "./serve";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EACV,kBAAkB,EAClB,aAAa,EACb,MAAM,EACN,YAAY,GACb,MAAM,SAAS,CAAC"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Node.js MLLP server entry point.
3
+ *
4
+ * Binds an {@link Mllp} application to a TCP (or TLS) socket using the
5
+ * Node.js `net` / `tls` modules. Incoming bytes are decoded from the MLLP
6
+ * framing protocol, dispatched through the application's middleware stack,
7
+ * and the resulting acknowledgements are encoded and written back to the
8
+ * client.
9
+ *
10
+ * @module
11
+ */
12
+ import type { MessageInfo, Mllp } from "../server/mllp";
13
+ import type { ConnectionInfo } from "../server/types";
14
+ /**
15
+ * Lifecycle callback invoked for connection events.
16
+ */
17
+ export type ConnectionCallback = (connection: ConnectionInfo) => void | Promise<void>;
18
+ /**
19
+ * Error callback invoked when a message handler error is unhandled.
20
+ * Receives the error and the connection it occurred on.
21
+ *
22
+ * For handler errors, `messageInfo` contains the routing fields
23
+ * (messageType, triggerEvent, controlId, version) of the message that
24
+ * caused the error — useful for audit logging in healthcare contexts.
25
+ * For lifecycle callback errors (onConnect/onDisconnect), `messageInfo`
26
+ * is `undefined`.
27
+ *
28
+ * This fires only for application-level errors (handler/middleware throws
29
+ * with no app-level error handler, or app-level error handler itself throws).
30
+ * Transport-level errors (connection reset, decode failures) do not trigger
31
+ * this callback.
32
+ */
33
+ export type ErrorCallback = (error: Error, connection: ConnectionInfo, messageInfo: MessageInfo | undefined) => void | Promise<void>;
34
+ /**
35
+ * Options for starting an MLLP server with {@link serve}.
36
+ */
37
+ export interface ServeOptions {
38
+ /**
39
+ * The hostname or IP address to bind the server to.
40
+ * When omitted the OS will bind to all available interfaces (`0.0.0.0`).
41
+ */
42
+ hostname?: string;
43
+ /**
44
+ * Whether to enable TCP keep-alive on accepted sockets.
45
+ * Defaults to the adapter default (`true`).
46
+ */
47
+ keepAlive?: boolean;
48
+ /**
49
+ * The initial delay in milliseconds before the first TCP keep-alive probe
50
+ * is sent on an idle socket. Only meaningful when `keepAlive` is `true`.
51
+ * Defaults to the adapter default (`60 000`).
52
+ */
53
+ keepAliveInitialDelay?: number;
54
+ /**
55
+ * Called when a new TCP connection is accepted. Receives the connection
56
+ * metadata including the unique connection ID and mutable state map.
57
+ *
58
+ * If this callback throws, the connection is torn down and
59
+ * `onDisconnect` is still called.
60
+ */
61
+ onConnect?: ConnectionCallback;
62
+ /**
63
+ * Called when a TCP connection is closed (including force-close).
64
+ * Always fires, even if `onConnect` threw.
65
+ *
66
+ * If this callback throws, the error is routed to `onError`
67
+ * (or `console.error` as last resort).
68
+ */
69
+ onDisconnect?: ConnectionCallback;
70
+ /**
71
+ * Called when a message handler error is unhandled — either no app-level
72
+ * error handler is registered, or the app-level error handler itself
73
+ * threw. Also called when lifecycle callbacks (`onConnect`,
74
+ * `onDisconnect`) throw.
75
+ *
76
+ * Only logs `error.message` and connection ID to avoid leaking PHI
77
+ * in the `console.error` fallback. Production deployments should
78
+ * always provide this callback.
79
+ */
80
+ onError?: ErrorCallback;
81
+ /**
82
+ * The TCP port number to listen on.
83
+ */
84
+ port: number;
85
+ /**
86
+ * Socket inactivity timeout in milliseconds. A socket that has been idle
87
+ * for longer than this value will be destroyed. Set to `0` (the default)
88
+ * to disable the timeout.
89
+ */
90
+ socketTimeout?: number;
91
+ /**
92
+ * TLS configuration. When provided the server will create a TLS socket
93
+ * instead of a plain TCP socket.
94
+ */
95
+ tls?: {
96
+ /** Optional CA certificate(s) for client certificate verification. */
97
+ ca?: string | Buffer;
98
+ /** The server certificate. */
99
+ cert: string | Buffer;
100
+ /** The private key for the server certificate. */
101
+ key: string | Buffer;
102
+ /** Optional passphrase for the private key. */
103
+ passphrase?: string;
104
+ };
105
+ }
106
+ /**
107
+ * A running MLLP server handle returned by {@link serve}.
108
+ */
109
+ export interface Server {
110
+ /** The port the server is currently listening on. */
111
+ readonly port: number;
112
+ /**
113
+ * Resolves when the underlying TCP server has bound and started listening.
114
+ * Await this before attempting to connect to ensure the port is ready.
115
+ */
116
+ readonly listening: Promise<void>;
117
+ /**
118
+ * Gracefully close the server. No new connections will be accepted and the
119
+ * returned promise resolves once all underlying resources are released.
120
+ */
121
+ close(): Promise<void>;
122
+ }
123
+ /**
124
+ * Start an MLLP server bound to a TCP (or TLS) port.
125
+ *
126
+ * The server accepts incoming connections, decodes MLLP-framed HL7v2
127
+ * messages, passes them through the provided {@link Mllp} application, and
128
+ * writes back any response (typically an ACK/NAK) using MLLP framing.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { Mllp } from "@glion/mllp";
133
+ * import { parseHL7v2 } from "@glion/hl7v2";
134
+ * import { serve } from "@glion/mllp/node";
135
+ *
136
+ * const app = new Mllp()
137
+ * .parser(parseHL7v2)
138
+ * .on("ADT^A01", async (ctx) => ({ raw: "..." }));
139
+ *
140
+ * const server = serve(app, { port: 2575 });
141
+ * console.log(`Listening on port ${server.port}`);
142
+ * ```
143
+ *
144
+ * @param app - The {@link Mllp} application that will handle each message.
145
+ * @param options - Server configuration (port, TLS, timeouts, etc.).
146
+ * @returns A {@link Server} handle that exposes the bound port and a `close()`
147
+ * method for graceful shutdown.
148
+ */
149
+ export declare function serve(app: Mllp, options: ServeOptions): Server;
150
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/node/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAStD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,UAAU,EAAE,cAAc,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,cAAc,EAC1B,WAAW,EAAE,WAAW,GAAG,SAAS,KACjC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAE/B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAElC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,GAAG,CAAC,EAAE;QACJ,sEAAsE;QACtE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACrB,8BAA8B;QAC9B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QACtB,kDAAkD;QAClD,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;QACrB,+CAA+C;QAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CA+B9D"}