@glion/mllp 0.26.0 → 0.27.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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Rethink Health
3
+ Copyright (c) 2023 Rethink Health, SUARL
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/mllp.js CHANGED
@@ -96,11 +96,11 @@ function createContext(options) {
96
96
  let varSnapshot;
97
97
  const file = new VFile(raw);
98
98
  const parsed = processor.parse(file);
99
- const controlId = value(parsed, "MSH-10")?.value ?? "";
100
- const messageType = value(parsed, "MSH-9.1")?.value ?? "";
101
- const triggerEvent = value(parsed, "MSH-9.2")?.value ?? "";
102
- const messageStructure = value(parsed, "MSH-9.3")?.value ?? "";
103
- const version = value(parsed, "MSH-12.1")?.value ?? "";
99
+ const controlId = value(parsed, "MSH.10")?.value ?? "";
100
+ const messageType = value(parsed, "MSH.9.1")?.value ?? "";
101
+ const triggerEvent = value(parsed, "MSH.9.2")?.value ?? "";
102
+ const messageStructure = value(parsed, "MSH.9.3")?.value ?? "";
103
+ const version = value(parsed, "MSH.12.1")?.value ?? "";
104
104
  let transformedTree;
105
105
  let transformPromise;
106
106
  function getTree() {
package/dist/mllp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mllp.js","names":["queryValue","#routes","#globalMiddleware","#scopedMiddleware","#router","#processor","#errorHandler","#handleError"],"sources":["../src/errors.ts","../src/server/compose.ts","../src/server/context.ts","../src/server/types.ts","../src/server/router.ts","../src/server/mllp.ts"],"sourcesContent":["/**\n * Errors raised by the `@glion/mllp` server itself.\n *\n * Per ADR 0018: one base error class discriminated by a stable `code` string.\n * A NAK the server *builds* in response to a message is an `@glion/ack`\n * `AckException` — a different concern from a server-lifecycle/config failure,\n * which is what `MllpServerError` represents.\n *\n * @module\n */\n\nexport const MllpServerErrorCode = {\n /**\n * An inbound message could not be decoded as UTF-8 — the only character set\n * supported for now. The underlying `@glion/util-charset` `CharsetError` is\n * kept on `cause`; callers branch on this code without importing that\n * package.\n */\n INCOMPATIBLE_CHARSET: \"INCOMPATIBLE_CHARSET\",\n /**\n * `app.handle()` was called before a parser was registered via\n * `app.parser()`.\n */\n NO_PARSER: \"NO_PARSER\",\n /**\n * The MLLP byte stream itself was violated and the connection closed:\n * the remote system sent bytes outside a message, glued or unterminated\n * messages, an over-cap message — or a handler's response contained a\n * reserved VT/FS byte and could not be framed. The underlying\n * `@glion/mllp-codec` `MllpCodecError` is kept on `cause`; branch on this\n * code without importing that package.\n */\n PROTOCOL_VIOLATION: \"PROTOCOL_VIOLATION\",\n} as const;\nexport type MllpServerErrorCode =\n (typeof MllpServerErrorCode)[keyof typeof MllpServerErrorCode];\n\n/** The one error class the `@glion/mllp` server raises. Branch on `code`. */\nexport class MllpServerError extends Error {\n readonly code: MllpServerErrorCode;\n constructor(\n code: MllpServerErrorCode,\n message: string,\n opts?: { cause?: unknown }\n ) {\n super(message, opts);\n this.name = \"MllpServerError\";\n this.code = code;\n }\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 // Hl7v2Processor's tail admits `undefined` (so the bare parser is\n // assignable), which makes unified type run()'s result as\n // `Root | Node`; an HL7v2 pipeline always yields the (possibly\n // transformed) Root.\n const result = (await processor.run(parsed, file)) as Root;\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 { 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// Re-exported from @glion/parser — the ecosystem home of \"a unified\n// processor over HL7v2 trees\"; the client's `parser` option shares it.\nexport type { Hl7v2Processor } from \"@glion/parser\";\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 { MllpServerError, MllpServerErrorCode } 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: unknown): MessageInfo | undefined {\n // A WeakMap lookup; a non-error key simply misses and yields undefined.\n return errorMessageInfo.get(error as 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 *\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 `MllpServerError` (`NO_PARSER`) if no processor has been registered\n * 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 MllpServerError(\n MllpServerErrorCode.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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"}
1
+ {"version":3,"file":"mllp.js","names":["queryValue","#routes","#globalMiddleware","#scopedMiddleware","#router","#processor","#errorHandler","#handleError"],"sources":["../src/errors.ts","../src/server/compose.ts","../src/server/context.ts","../src/server/types.ts","../src/server/router.ts","../src/server/mllp.ts"],"sourcesContent":["/**\n * Errors raised by the `@glion/mllp` server itself.\n *\n * Per ADR 0018: one base error class discriminated by a stable `code` string.\n * A NAK the server *builds* in response to a message is an `@glion/ack`\n * `AckException` — a different concern from a server-lifecycle/config failure,\n * which is what `MllpServerError` represents.\n *\n * @module\n */\n\nexport const MllpServerErrorCode = {\n /**\n * An inbound message could not be decoded as UTF-8 — the only character set\n * supported for now. The underlying `@glion/util-charset` `CharsetError` is\n * kept on `cause`; callers branch on this code without importing that\n * package.\n */\n INCOMPATIBLE_CHARSET: \"INCOMPATIBLE_CHARSET\",\n /**\n * `app.handle()` was called before a parser was registered via\n * `app.parser()`.\n */\n NO_PARSER: \"NO_PARSER\",\n /**\n * The MLLP byte stream itself was violated and the connection closed:\n * the remote system sent bytes outside a message, glued or unterminated\n * messages, an over-cap message — or a handler's response contained a\n * reserved VT/FS byte and could not be framed. The underlying\n * `@glion/mllp-codec` `MllpCodecError` is kept on `cause`; branch on this\n * code without importing that package.\n */\n PROTOCOL_VIOLATION: \"PROTOCOL_VIOLATION\",\n} as const;\nexport type MllpServerErrorCode =\n (typeof MllpServerErrorCode)[keyof typeof MllpServerErrorCode];\n\n/** The one error class the `@glion/mllp` server raises. Branch on `code`. */\nexport class MllpServerError extends Error {\n readonly code: MllpServerErrorCode;\n constructor(\n code: MllpServerErrorCode,\n message: string,\n opts?: { cause?: unknown }\n ) {\n super(message, opts);\n this.name = \"MllpServerError\";\n this.code = code;\n }\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 // Hl7v2Processor's tail admits `undefined` (so the bare parser is\n // assignable), which makes unified type run()'s result as\n // `Root | Node`; an HL7v2 pipeline always yields the (possibly\n // transformed) Root.\n const result = (await processor.run(parsed, file)) as Root;\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 { 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// Re-exported from @glion/parser — the ecosystem home of \"a unified\n// processor over HL7v2 trees\"; the client's `parser` option shares it.\nexport type { Hl7v2Processor } from \"@glion/parser\";\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 { MllpServerError, MllpServerErrorCode } 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: unknown): MessageInfo | undefined {\n // A WeakMap lookup; a non-error key simply misses and yields undefined.\n return errorMessageInfo.get(error as 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 *\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 `MllpServerError` (`NO_PARSER`) if no processor has been registered\n * 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 MllpServerError(\n MllpServerErrorCode.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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/mllp",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Transport-agnostic MLLP (Minimal Lower Layer Protocol) engine for HL7v2 messaging",
5
5
  "keywords": [
6
6
  "health",
@@ -44,11 +44,11 @@
44
44
  "dependencies": {
45
45
  "unified": "11.0.5",
46
46
  "vfile": "6.0.3",
47
- "@glion/ast": "0.26.0",
48
- "@glion/mllp-codec": "0.26.0",
49
- "@glion/parser": "0.26.0",
50
- "@glion/util-charset": "0.26.0",
51
- "@glion/util-query": "0.26.0"
47
+ "@glion/ast": "0.27.0",
48
+ "@glion/mllp-codec": "0.27.0",
49
+ "@glion/parser": "0.27.0",
50
+ "@glion/util-charset": "0.27.0",
51
+ "@glion/util-query": "0.27.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "25.6.0",
@@ -58,7 +58,7 @@
58
58
  "typescript": "6.0.3",
59
59
  "vitest": "4.1.11",
60
60
  "@glion/check-readme": "0.18.0",
61
- "@glion/hl7v2": "0.26.0",
61
+ "@glion/hl7v2": "0.27.0",
62
62
  "@glion/testing": "0.18.0",
63
63
  "@glion/tsconfig": "0.18.0"
64
64
  },