@glion/mllp 0.15.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -59
- package/dist/errors.d.ts +37 -17
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -38
- package/dist/mllp.js +35 -406
- package/dist/mllp.js.map +1 -1
- package/dist/node/adapter.d.ts.map +1 -1
- package/dist/node/serve.d.ts +6 -4
- package/dist/node/serve.d.ts.map +1 -1
- package/dist/node.js +36 -11
- package/dist/node.js.map +1 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/mllp.d.ts +4 -2
- package/dist/server/mllp.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -9
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +17 -17
- package/dist/index.js.map +0 -1
- package/dist/transport/constants.d.ts +0 -24
- package/dist/transport/constants.d.ts.map +0 -1
- package/dist/transport/decoder-stream.d.ts +0 -49
- package/dist/transport/decoder-stream.d.ts.map +0 -1
- package/dist/transport/decoder.d.ts +0 -18
- package/dist/transport/decoder.d.ts.map +0 -1
- package/dist/transport/dynamic-buffer.d.ts +0 -28
- package/dist/transport/dynamic-buffer.d.ts.map +0 -1
- package/dist/transport/encoder.d.ts +0 -14
- package/dist/transport/encoder.d.ts.map +0 -1
- package/dist/transport/errors.d.ts +0 -13
- package/dist/transport/errors.d.ts.map +0 -1
- package/dist/transport/index.d.ts +0 -8
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/types.d.ts +0 -49
- package/dist/transport/types.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -57,33 +57,46 @@ The server class. Built with a fluent chainable API:
|
|
|
57
57
|
|
|
58
58
|
Start a Node.js or Bun TCP server that dispatches incoming MLLP frames through the `Mllp` instance.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
|
|
60
|
+
| Option | Type | Description |
|
|
61
|
+
| ----------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `port` | `number` | TCP port to listen on. |
|
|
63
|
+
| `hostname` | `string`, optional | Interface to bind. Defaults to all interfaces. |
|
|
64
|
+
| `tls` | `{ cert, key, ca?, passphrase? }`, optional | Enable MLLP over TLS. |
|
|
65
|
+
| `keepAlive` | `boolean`, optional | Enable TCP keep-alive on accepted sockets. Defaults to `true`. |
|
|
66
|
+
| `keepAliveInitialDelay` | `number`, optional | Delay in ms before the first keep-alive probe on an idle socket. Defaults to `60000`. |
|
|
67
|
+
| `socketTimeout` | `number`, optional | Socket inactivity timeout in ms — an idle socket past it is destroyed. `0` (default) disables. |
|
|
68
|
+
| `onConnect` | `ConnectionCallback`, optional | Called when a new TCP connection is accepted. |
|
|
69
|
+
| `onDisconnect` | `ConnectionCallback`, optional | Called when a connection closes — always fires, even if `onConnect` threw. |
|
|
70
|
+
| `onError` | `ErrorCallback`, optional | Called when a handler error goes unhandled or a lifecycle callback throws. |
|
|
71
|
+
|
|
72
|
+
### Wire framing
|
|
73
|
+
|
|
74
|
+
MLLP framing lives in [`@glion/mllp-codec`](https://github.com/rethinkhealth/glion/tree/main/packages/mllp-codec) — `frame()` wraps outbound messages, `unframe()` streams inbound bytes back into them. `@glion/mllp` consumes it internally and re-exports nothing from it; import it directly when you need the codec.
|
|
72
75
|
|
|
73
76
|
### Types
|
|
74
77
|
|
|
75
|
-
| Type | Description
|
|
76
|
-
| ------------------ |
|
|
77
|
-
| `Context` | Request context with message data and routing fields.
|
|
78
|
-
| `Response` | Response object `{ raw: string }`.
|
|
79
|
-
| `Hl7v2Processor` | Unified `Processor` type for HL7v2 (`Processor<Root, Root, Root>`). |
|
|
80
|
-
| `Middleware` | Middleware function `(ctx, next) => ...`.
|
|
81
|
-
| `Handler` | Terminal route handler `(ctx) => Response`.
|
|
82
|
-
| `ErrorHandler` | Error handler `(err, ctx) => Response`.
|
|
83
|
-
| `RouteFilter` | Filter function `(ctx) => boolean` used for routing.
|
|
84
|
-
| `MiddlewareReturn` | Return type of middleware functions.
|
|
85
|
-
| `ConnectionInfo` | Connection metadata (remote/local address, TLS flag).
|
|
86
|
-
| `RoutePattern` | Parsed route pattern.
|
|
78
|
+
| Type | Description |
|
|
79
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `Context` | Request context with message data and routing fields. |
|
|
81
|
+
| `Response` | Response object `{ raw: string }`. |
|
|
82
|
+
| `Hl7v2Processor` | Unified `Processor` type for HL7v2 (`Processor<Root, Root \| undefined, Root \| undefined>` — head/tail admit `undefined` so the bare `unified().use(hl7v2Parser).freeze()` is assignable). |
|
|
83
|
+
| `Middleware` | Middleware function `(ctx, next) => ...`. |
|
|
84
|
+
| `Handler` | Terminal route handler `(ctx) => Response`. |
|
|
85
|
+
| `ErrorHandler` | Error handler `(err, ctx) => Response`. |
|
|
86
|
+
| `RouteFilter` | Filter function `(ctx) => boolean` used for routing. |
|
|
87
|
+
| `MiddlewareReturn` | Return type of middleware functions. |
|
|
88
|
+
| `ConnectionInfo` | Connection metadata (remote/local address, TLS flag). |
|
|
89
|
+
| `RoutePattern` | Parsed route pattern. |
|
|
90
|
+
|
|
91
|
+
### Errors
|
|
92
|
+
|
|
93
|
+
Server-side failures are raised (or passed to `onError`) as `MllpServerError` — branch on its stable `code`; the underlying codec or charset error rides on `cause`:
|
|
94
|
+
|
|
95
|
+
| Code | Meaning |
|
|
96
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `NO_PARSER` | `app.handle()` ran before a parser was registered via `app.parser()`. |
|
|
98
|
+
| `INCOMPATIBLE_CHARSET` | An inbound message could not be decoded as UTF-8 (the `CharsetError` is on `cause`). |
|
|
99
|
+
| `PROTOCOL_VIOLATION` | The MLLP byte stream was violated and the connection closed (the `MllpCodecError` is on `cause`). |
|
|
87
100
|
|
|
88
101
|
## Routing
|
|
89
102
|
|
|
@@ -256,39 +269,6 @@ const server = serve(app, {
|
|
|
256
269
|
});
|
|
257
270
|
```
|
|
258
271
|
|
|
259
|
-
## Primitives
|
|
260
|
-
|
|
261
|
-
### Simple API
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
import { encode, decode, encodeMultiple } from "@glion/mllp";
|
|
265
|
-
|
|
266
|
-
const mllpFrame = encode(hl7Message);
|
|
267
|
-
const decoded = decode(mllpFrame);
|
|
268
|
-
console.log(decoded.text);
|
|
269
|
-
|
|
270
|
-
const frames = encodeMultiple(["MSH|1", "MSH|2"]);
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Streaming API
|
|
274
|
-
|
|
275
|
-
```ts
|
|
276
|
-
import { createDecoderStream } from "@glion/mllp";
|
|
277
|
-
|
|
278
|
-
const decoder = createDecoderStream({
|
|
279
|
-
maxMessageSize: 1024 * 1024,
|
|
280
|
-
onError: (error) => console.warn(`[${error.code}] ${error.message}`),
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
tcpSocket.readable.pipeThrough(decoder).pipeTo(
|
|
284
|
-
new WritableStream({
|
|
285
|
-
write(message) {
|
|
286
|
-
console.log("Received:", message.text);
|
|
287
|
-
},
|
|
288
|
-
})
|
|
289
|
-
);
|
|
290
|
-
```
|
|
291
|
-
|
|
292
272
|
## Design notes
|
|
293
273
|
|
|
294
274
|
### Why no default error response?
|
|
@@ -299,14 +279,13 @@ Instead, the `Mllp` engine is middleware-first:
|
|
|
299
279
|
|
|
300
280
|
- **Default behaviour** — no response is sent; the sending system times out and retries, which is valid MLLP behaviour.
|
|
301
281
|
- **Logging** — add a logger middleware to make errors observable.
|
|
302
|
-
- **ACK/NAK** —
|
|
282
|
+
- **ACK/NAK** — register your own acknowledgment middleware to translate outcomes into ACK/NAK responses. Built-in acknowledgment translation at the framework's error boundary is landing next (see ADR-0019).
|
|
303
283
|
- **Custom error responses** — use `app.onError()` for application-specific handling.
|
|
304
284
|
|
|
305
285
|
```ts
|
|
306
286
|
const app = new Mllp().parser(parseHL7v2);
|
|
307
287
|
|
|
308
288
|
app.use(logger()); // observability — provided by middleware
|
|
309
|
-
app.use(ackMiddleware()); // error → NAK translation — @glion/mllp-ack
|
|
310
289
|
|
|
311
290
|
app.on("ADT^A01", handler);
|
|
312
291
|
```
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Base error class for all MLLP errors.
|
|
2
|
+
* Errors raised by the `@glion/mllp` server itself.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0018: one base error class discriminated by a stable `code` string.
|
|
5
|
+
* A NAK the server *builds* in response to a message is an `@glion/ack`
|
|
6
|
+
* `AckException` — a different concern from a server-lifecycle/config failure,
|
|
7
|
+
* which is what `MllpServerError` represents.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
* Provides a common `code` field so that any MLLP error can be
|
|
13
|
-
* programmatically identified and translated (e.g., into ACK/NAK responses).
|
|
9
|
+
* @module
|
|
14
10
|
*/
|
|
15
|
-
export declare
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
export declare const MllpServerErrorCode: {
|
|
12
|
+
/**
|
|
13
|
+
* An inbound message could not be decoded as UTF-8 — the only character set
|
|
14
|
+
* supported for now. The underlying `@glion/util-charset` `CharsetError` is
|
|
15
|
+
* kept on `cause`; callers branch on this code without importing that
|
|
16
|
+
* package.
|
|
17
|
+
*/
|
|
18
|
+
readonly INCOMPATIBLE_CHARSET: "INCOMPATIBLE_CHARSET";
|
|
19
|
+
/**
|
|
20
|
+
* `app.handle()` was called before a parser was registered via
|
|
21
|
+
* `app.parser()`.
|
|
22
|
+
*/
|
|
23
|
+
readonly NO_PARSER: "NO_PARSER";
|
|
24
|
+
/**
|
|
25
|
+
* The MLLP byte stream itself was violated and the connection closed:
|
|
26
|
+
* the remote system sent bytes outside a message, glued or unterminated
|
|
27
|
+
* messages, an over-cap message — or a handler's response contained a
|
|
28
|
+
* reserved VT/FS byte and could not be framed. The underlying
|
|
29
|
+
* `@glion/mllp-codec` `MllpCodecError` is kept on `cause`; branch on this
|
|
30
|
+
* code without importing that package.
|
|
31
|
+
*/
|
|
32
|
+
readonly PROTOCOL_VIOLATION: "PROTOCOL_VIOLATION";
|
|
33
|
+
};
|
|
34
|
+
export type MllpServerErrorCode = (typeof MllpServerErrorCode)[keyof typeof MllpServerErrorCode];
|
|
35
|
+
/** The one error class the `@glion/mllp` server raises. Branch on `code`. */
|
|
36
|
+
export declare class MllpServerError extends Error {
|
|
37
|
+
readonly code: MllpServerErrorCode;
|
|
38
|
+
constructor(code: MllpServerErrorCode, message: string, opts?: {
|
|
39
|
+
cause?: unknown;
|
|
40
|
+
});
|
|
21
41
|
}
|
|
22
42
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,mBAAmB;IAC9B;;;;;OAKG;;IAEH;;;OAGG;;IAEH;;;;;;;OAOG;;CAEK,CAAC;AACX,MAAM,MAAM,mBAAmB,GAC7B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEjE,6EAA6E;AAC7E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;gBAEjC,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAM7B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type { DecodedMessage, DecoderOptions, EncoderInput, EncoderOptions, } from "./transport";
|
|
3
|
-
export { MllpError } from "./errors";
|
|
4
|
-
export type { MllpErrorOptions } from "./errors";
|
|
1
|
+
export { MllpServerError, MllpServerErrorCode } from "./errors";
|
|
5
2
|
export { getMessageInfo, Mllp } from "./server/mllp";
|
|
6
3
|
export type { MessageInfo } from "./server/mllp";
|
|
7
4
|
export type { ConnectionInfo, Context, ErrorHandler, Handler, Hl7v2Processor, Middleware, MiddlewareReturn, Response, RouteFilter, RoutePattern, } from "./server/types";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EACV,cAAc,EACd,OAAO,EACP,YAAY,EACZ,OAAO,EACP,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,38 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Decode a single MLLP frame (VT ... FS CR) into a message.
|
|
5
|
-
*
|
|
6
|
-
* Validates framing, strips the envelope, and returns the payload as
|
|
7
|
-
* both raw bytes and decoded text. Throws {@link TransportError} on
|
|
8
|
-
* any structural issue.
|
|
9
|
-
*
|
|
10
|
-
* For streaming / chunked TCP data, use {@link createDecoderStream} instead.
|
|
11
|
-
*
|
|
12
|
-
* @throws TransportError with `INVALID_START_BYTE` if byte 0 is not 0x0B
|
|
13
|
-
* @throws TransportError with `INVALID_END_SEQUENCE` if frame doesn't end with
|
|
14
|
-
* 0x1C 0x0D
|
|
15
|
-
* @throws TransportError with `MESSAGE_TOO_LARGE` if payload exceeds
|
|
16
|
-
* `maxMessageSize`
|
|
17
|
-
*/
|
|
18
|
-
function decode(frame, options) {
|
|
19
|
-
if (frame.length < 3) throw new TransportError(TransportErrorCode.INVALID_END_SEQUENCE, "Frame too short to contain valid MLLP structure", 0);
|
|
20
|
-
const startByte = frame[0];
|
|
21
|
-
if (startByte !== 11) throw new TransportError(TransportErrorCode.INVALID_START_BYTE, `Invalid start byte: expected 0x0B, got 0x${(startByte ?? 0).toString(16).padStart(2, "0")}`, 0);
|
|
22
|
-
const len = frame.length;
|
|
23
|
-
const endByte1 = frame[len - 2];
|
|
24
|
-
const endByte2 = frame[len - 1];
|
|
25
|
-
if (endByte1 !== 28 || endByte2 !== 13) throw new TransportError(TransportErrorCode.INVALID_END_SEQUENCE, `Invalid end sequence: expected 0x1C 0x0D, got 0x${(endByte1 ?? 0).toString(16).padStart(2, "0")} 0x${(endByte2 ?? 0).toString(16).padStart(2, "0")}`, len - 2);
|
|
26
|
-
const messageLength = len - 3;
|
|
27
|
-
if (options?.maxMessageSize !== void 0 && messageLength > options.maxMessageSize) throw new TransportError(TransportErrorCode.MESSAGE_TOO_LARGE, `Message size ${messageLength} exceeds maximum ${options.maxMessageSize}`, 0);
|
|
28
|
-
const data = frame.subarray(1, len - 2);
|
|
29
|
-
return {
|
|
30
|
-
byteLength: messageLength,
|
|
31
|
-
data,
|
|
32
|
-
text: new TextDecoder(options?.encoding ?? "utf8").decode(data)
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
export { TransportErrorCode as ErrorCode, MLLPDecoderStream, MLLP_END_BYTE_1, MLLP_END_BYTE_2, MLLP_HEADER, MLLP_START_BYTE, MLLP_TRAILER, Mllp, MllpError, TransportError, createDecoderStream, decode, encode, encodeMultiple, getMessageInfo, matchPattern, parsePattern };
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import { a as MllpServerError, i as parsePattern, n as getMessageInfo, o as MllpServerErrorCode, r as matchPattern, t as Mllp } from "./mllp.js";
|
|
2
|
+
export { Mllp, MllpServerError, MllpServerErrorCode, getMessageInfo, matchPattern, parsePattern };
|