@cosyte/mllp 0.0.4 → 0.0.5
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/CHANGELOG.md +46 -0
- package/dist/ack-from-hl7/index.cjs.map +1 -1
- package/dist/ack-from-hl7/index.mjs.map +1 -1
- package/dist/index.cjs +53 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +132 -9
- package/dist/index.d.ts +132 -9
- package/dist/index.mjs +53 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -46,7 +46,21 @@ type WarningCode = "MLLP_MISSING_LEADING_VT" | "MLLP_FS_WITHOUT_CR" | "MLLP_LF_A
|
|
|
46
46
|
*/
|
|
47
47
|
interface MllpWarning {
|
|
48
48
|
readonly code: WarningCode;
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Stable human-readable description. It carries structural facts (a byte
|
|
51
|
+
* offset, an accumulated size) and **never a run of payload content**.
|
|
52
|
+
*
|
|
53
|
+
* Two codes carry exactly one input byte, and it is worth knowing which:
|
|
54
|
+
* `MLLP_MISSING_LEADING_VT` and `MLLP_FS_WITHOUT_CR` render the hex of the
|
|
55
|
+
* single byte found where a framing byte was expected, and on a stream that
|
|
56
|
+
* omits its leading `VT` that byte is the first byte of the unframed content.
|
|
57
|
+
* One byte, never a run, the same bound `MllpFramingError.snippet` carries. If
|
|
58
|
+
* that is more than your threat model allows, log `code` and `byteOffset`
|
|
59
|
+
* rather than `message`.
|
|
60
|
+
*
|
|
61
|
+
* The ACK-correlation codes are stricter: their text comes from a frozen
|
|
62
|
+
* registry and does not vary with the wire at all.
|
|
63
|
+
*/
|
|
50
64
|
readonly message: string;
|
|
51
65
|
/** Absolute stream byte offset where the anomaly was detected. */
|
|
52
66
|
readonly byteOffset: number;
|
|
@@ -2039,6 +2053,69 @@ declare function createServer(opts?: ServerOptions): MllpServer;
|
|
|
2039
2053
|
*/
|
|
2040
2054
|
declare function createStarterServer(opts: StarterServerOptions): Promise<MllpServer>;
|
|
2041
2055
|
|
|
2056
|
+
/**
|
|
2057
|
+
* The frozen diagnostic registry for ACK correlation.
|
|
2058
|
+
*
|
|
2059
|
+
* Every message an ACK-correlation diagnostic can carry lives here as a
|
|
2060
|
+
* literal, and the lookup takes **only a code**. There is no value parameter
|
|
2061
|
+
* anywhere in this module, which is the whole point: a factory that cannot be
|
|
2062
|
+
* handed a value cannot interpolate one, regardless of what arrives on the
|
|
2063
|
+
* wire.
|
|
2064
|
+
*
|
|
2065
|
+
* ## What this replaces, and why the shape matters more than the wording
|
|
2066
|
+
*
|
|
2067
|
+
* The `correlateByControlId` path used to build its diagnostics by
|
|
2068
|
+
* interpolation, and both ends of it were consumer-controlled and unbounded.
|
|
2069
|
+
* The unmatched-ACK error read the **peer's** MSA-2 straight out of an inbound
|
|
2070
|
+
* frame and put it on an `Error.message`; measured against a peer sending a
|
|
2071
|
+
* one-megabyte MSA-2, that produced a 1,000,026-byte `message` and an
|
|
2072
|
+
* equally large field on the frozen error payload. The late-ACK warning did
|
|
2073
|
+
* the same with the control id of the send being reported on. Both go to a
|
|
2074
|
+
* log, and in this domain a log is a place a patient identifier must not
|
|
2075
|
+
* reach. That is not a hypothetical about control ids either: this package has
|
|
2076
|
+
* already shipped one scanner that ran past a segment terminator and returned
|
|
2077
|
+
* PID-3, a patient's medical record number, as the "control ID" of a truncated
|
|
2078
|
+
* MSH. Withholding the bytes is the defence that survives the next such bug.
|
|
2079
|
+
*
|
|
2080
|
+
* ## What a correlation diagnostic says instead
|
|
2081
|
+
*
|
|
2082
|
+
* That a control id did not match, plus **numbers**: a byte length, a stream
|
|
2083
|
+
* offset, an elapsed time. A number is a shape, not content. A truncated id is
|
|
2084
|
+
* not an acceptable middle ground, and neither is a hex or base64 rendering:
|
|
2085
|
+
* both still grow the diagnostic with the input and both still disclose the
|
|
2086
|
+
* bytes. Nothing is lost by withholding them, because the caller already holds
|
|
2087
|
+
* them: the outbound payload is the caller's own argument to `send()`, and the
|
|
2088
|
+
* inbound frame is on the `'message'` event.
|
|
2089
|
+
*
|
|
2090
|
+
* Mirrors the same answer this package already reached for the ACK adapter's
|
|
2091
|
+
* verbatim-echo warning, which reports byte lengths and withholds MSH-10 and
|
|
2092
|
+
* MSA-2 themselves.
|
|
2093
|
+
*
|
|
2094
|
+
* @packageDocumentation
|
|
2095
|
+
*/
|
|
2096
|
+
/**
|
|
2097
|
+
* The two warning codes emitted by ACK correlation rather than by framing.
|
|
2098
|
+
*
|
|
2099
|
+
* A subset of the framing `WarningCode` union; named separately so the
|
|
2100
|
+
* registry below is exhaustive over exactly the codes it owns and a new
|
|
2101
|
+
* correlation code cannot be added without a message for it.
|
|
2102
|
+
*/
|
|
2103
|
+
type AckCorrelationCode = "MLLP_ACK_UNMATCHED_CONTROL_ID" | "MLLP_ACK_AFTER_TIMEOUT";
|
|
2104
|
+
/**
|
|
2105
|
+
* Look up the frozen diagnostic text for an ACK-correlation code.
|
|
2106
|
+
*
|
|
2107
|
+
* A pure table read. It takes no value, so no caller can widen it into an
|
|
2108
|
+
* interpolation site.
|
|
2109
|
+
*
|
|
2110
|
+
* @param code - The correlation code being reported.
|
|
2111
|
+
* @returns The registered message for that code, byte-for-byte.
|
|
2112
|
+
* @example
|
|
2113
|
+
* ```typescript
|
|
2114
|
+
* const message = ackDiagnosticMessage('MLLP_ACK_AFTER_TIMEOUT');
|
|
2115
|
+
* ```
|
|
2116
|
+
*/
|
|
2117
|
+
declare function ackDiagnosticMessage(code: AckCorrelationCode): string;
|
|
2118
|
+
|
|
2042
2119
|
/**
|
|
2043
2120
|
* MLLP Client, `createClient()` factory and `MllpClient` class.
|
|
2044
2121
|
*
|
|
@@ -2063,6 +2140,37 @@ declare function createStarterServer(opts: StarterServerOptions): Promise<MllpSe
|
|
|
2063
2140
|
* @packageDocumentation
|
|
2064
2141
|
*/
|
|
2065
2142
|
|
|
2143
|
+
/**
|
|
2144
|
+
* The `'warning'` payload the client emits for an ACK-correlation deviation
|
|
2145
|
+
* (`MLLP_ACK_UNMATCHED_CONTROL_ID`, `MLLP_ACK_AFTER_TIMEOUT`).
|
|
2146
|
+
*
|
|
2147
|
+
* A `MllpWarning` with two extra numeric fields. `message` is a **frozen
|
|
2148
|
+
* registry entry**, byte-for-byte identical for a given code no matter what
|
|
2149
|
+
* arrived on the wire, and everything input-derived is a number: the control
|
|
2150
|
+
* ID's byte length and the elapsed time. A warning is a log line, so it carries
|
|
2151
|
+
* no field content.
|
|
2152
|
+
*
|
|
2153
|
+
* @example
|
|
2154
|
+
* ```typescript
|
|
2155
|
+
* client.on('warning', (w: AckCorrelationWarning) => {
|
|
2156
|
+
* logger.warn({ code: w.code, idBytes: w.controlIdBytes, elapsedMs: w.elapsedSinceSendMs });
|
|
2157
|
+
* });
|
|
2158
|
+
* ```
|
|
2159
|
+
*/
|
|
2160
|
+
interface AckCorrelationWarning extends MllpWarning {
|
|
2161
|
+
/** The correlation code this warning reports. */
|
|
2162
|
+
readonly code: AckCorrelationCode;
|
|
2163
|
+
/**
|
|
2164
|
+
* Byte length of the control ID involved, or `null` when there was none to
|
|
2165
|
+
* read. Control IDs are decoded `latin1`, so a code-unit count is a byte count.
|
|
2166
|
+
*/
|
|
2167
|
+
readonly controlIdBytes: number | null;
|
|
2168
|
+
/**
|
|
2169
|
+
* Milliseconds between the send's write-flush (or its timeout, for a late
|
|
2170
|
+
* ACK) and this warning.
|
|
2171
|
+
*/
|
|
2172
|
+
readonly elapsedSinceSendMs: number;
|
|
2173
|
+
}
|
|
2066
2174
|
/**
|
|
2067
2175
|
* Context passed to a custom `retryStrategy` hook on each reconnect attempt.
|
|
2068
2176
|
*
|
|
@@ -2315,7 +2423,10 @@ interface ClientStats {
|
|
|
2315
2423
|
* - `'reconnecting'`, `{ connectionId, attempt?, delayMs? }`
|
|
2316
2424
|
* - `'close'`, `{ connectionId }` once the FSM enters terminal `CLOSED`
|
|
2317
2425
|
* - `'message'`, `{ payload, connectionId, byteOffset, warnings }` for every inbound frame
|
|
2318
|
-
* - `'warning'`, `MllpWarning
|
|
2426
|
+
* - `'warning'`, `MllpWarning | AckCorrelationWarning`. A framing deviation arrives as a
|
|
2427
|
+
* `MllpWarning` enriched with `connectionId` from the Connection layer; the two
|
|
2428
|
+
* ACK-correlation codes arrive as {@link AckCorrelationWarning}, which adds
|
|
2429
|
+
* `controlIdBytes` and `elapsedSinceSendMs`. Narrow on `code` before reading either.
|
|
2319
2430
|
* - `'securityWarning'`, `SecurityWarning`. Emitted on every successful
|
|
2320
2431
|
* `secureConnect` (initial + every reconnect) when `tls.allowUnverified` is `true`
|
|
2321
2432
|
* with code `MLLP_TLS_VERIFY_DISABLED`. Also mirrored to `process.emitWarning`.
|
|
@@ -2870,15 +2981,27 @@ declare function createStarterClient(opts: StarterClientOptions): Promise<MllpCl
|
|
|
2870
2981
|
* await client.send(payload);
|
|
2871
2982
|
* } catch (err) {
|
|
2872
2983
|
* if (err instanceof MllpTimeoutError) {
|
|
2873
|
-
* logger.warn({ elapsedMs: err.elapsedMs,
|
|
2984
|
+
* logger.warn({ elapsedMs: err.elapsedMs, idBytes: err.messageControlIdBytes });
|
|
2874
2985
|
* }
|
|
2875
2986
|
* }
|
|
2876
2987
|
* ```
|
|
2877
2988
|
*/
|
|
2878
2989
|
declare class MllpTimeoutError extends Error {
|
|
2879
2990
|
readonly name: "MllpTimeoutError";
|
|
2880
|
-
/**
|
|
2881
|
-
|
|
2991
|
+
/**
|
|
2992
|
+
* Byte length of the timed-out send's MSH-10 control ID, or `undefined` when
|
|
2993
|
+
* there was none to read (FIFO mode, or a payload with no MSH-10).
|
|
2994
|
+
*
|
|
2995
|
+
* **The control ID itself is deliberately not here.** An `Error` is a
|
|
2996
|
+
* diagnostic surface: it is logged, and its `stack` is what an error reporter
|
|
2997
|
+
* ships off the box. MSH-10 is payload content, and a scanner that returns
|
|
2998
|
+
* the wrong field returns payload content of some other kind, which is how a
|
|
2999
|
+
* patient identifier reaches a log line. Nothing is lost by withholding it,
|
|
3000
|
+
* because this error rejects the very `send()` whose payload the caller
|
|
3001
|
+
* passed in, so the caller already holds the bytes. Control IDs are decoded
|
|
3002
|
+
* `latin1`, a 1:1 byte to code-unit map, so this count is a byte count.
|
|
3003
|
+
*/
|
|
3004
|
+
readonly messageControlIdBytes: number | undefined;
|
|
2882
3005
|
/** Milliseconds elapsed between write-flush and timeout fire. */
|
|
2883
3006
|
readonly elapsedMs: number;
|
|
2884
3007
|
/** Epoch ms timestamp recorded at write-flush callback. */
|
|
@@ -2886,11 +3009,11 @@ declare class MllpTimeoutError extends Error {
|
|
|
2886
3009
|
/**
|
|
2887
3010
|
* Construct an MLLP timeout error.
|
|
2888
3011
|
*
|
|
2889
|
-
* @param message - Human-readable error message.
|
|
2890
|
-
* @param opts - Timeout context (
|
|
3012
|
+
* @param message - Human-readable error message. Structural facts only, never field content.
|
|
3013
|
+
* @param opts - Timeout context (control-id byte length, elapsed time, flush timestamp).
|
|
2891
3014
|
*/
|
|
2892
3015
|
constructor(message: string, opts: {
|
|
2893
|
-
|
|
3016
|
+
messageControlIdBytes: number | undefined;
|
|
2894
3017
|
elapsedMs: number;
|
|
2895
3018
|
sentAt: number;
|
|
2896
3019
|
});
|
|
@@ -3091,4 +3214,4 @@ declare class MllpBackpressureError extends Error {
|
|
|
3091
3214
|
*/
|
|
3092
3215
|
declare const VERSION: string;
|
|
3093
3216
|
|
|
3094
|
-
export { type AckCode, type ClientAuth, type ClientOptions, type ClientStats, Connection, type ConnectionErrorCause, type ConnectionErrorPhase, type ConnectionOptions, type ConnectionState, type ConnectionStats, type EncoderOptions, FrameReader, type FrameReaderOptions, MLLP_BIND_ALL_INTERFACES, MLLP_TLS_VERIFY_DISABLED, type MessageMeta, MllpAckError, MllpBackpressureError, MllpClient, MllpConnectionError, MllpFramingError, MllpServer, MllpTimeoutError, type MllpWarning, type NackEvent, type NackReason, type NegativeAckCode, NetTransport, type PemInput, type ReconnectingEvent, type RetryContext, type RetryStrategy, type SecurityWarning, type SecurityWarningCode, type ServerOptions, type ServerStats, type ServerTlsOptions, type StarterClientOptions, type StarterServerOptions, type StateChangeEvent, type TlsOptions, TlsTransport, Transport, VERSION, type WarningCode, buildRawAck, createClient, createServer, createStarterClient, createStarterServer, createWarning, encodeFrame, isTlsProtocolError, isTlsVerificationErrorCode, isTransientConnectionError, rawAckUncorrelatable, resolveNackCode };
|
|
3217
|
+
export { type AckCode, type AckCorrelationCode, type AckCorrelationWarning, type ClientAuth, type ClientOptions, type ClientStats, Connection, type ConnectionErrorCause, type ConnectionErrorPhase, type ConnectionOptions, type ConnectionState, type ConnectionStats, type EncoderOptions, FrameReader, type FrameReaderOptions, MLLP_BIND_ALL_INTERFACES, MLLP_TLS_VERIFY_DISABLED, type MessageMeta, MllpAckError, MllpBackpressureError, MllpClient, MllpConnectionError, MllpFramingError, MllpServer, MllpTimeoutError, type MllpWarning, type NackEvent, type NackReason, type NegativeAckCode, NetTransport, type PemInput, type ReconnectingEvent, type RetryContext, type RetryStrategy, type SecurityWarning, type SecurityWarningCode, type ServerOptions, type ServerStats, type ServerTlsOptions, type StarterClientOptions, type StarterServerOptions, type StateChangeEvent, type TlsOptions, TlsTransport, Transport, VERSION, type WarningCode, ackDiagnosticMessage, buildRawAck, createClient, createServer, createStarterClient, createStarterServer, createWarning, encodeFrame, isTlsProtocolError, isTlsVerificationErrorCode, isTransientConnectionError, rawAckUncorrelatable, resolveNackCode };
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,21 @@ type WarningCode = "MLLP_MISSING_LEADING_VT" | "MLLP_FS_WITHOUT_CR" | "MLLP_LF_A
|
|
|
46
46
|
*/
|
|
47
47
|
interface MllpWarning {
|
|
48
48
|
readonly code: WarningCode;
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Stable human-readable description. It carries structural facts (a byte
|
|
51
|
+
* offset, an accumulated size) and **never a run of payload content**.
|
|
52
|
+
*
|
|
53
|
+
* Two codes carry exactly one input byte, and it is worth knowing which:
|
|
54
|
+
* `MLLP_MISSING_LEADING_VT` and `MLLP_FS_WITHOUT_CR` render the hex of the
|
|
55
|
+
* single byte found where a framing byte was expected, and on a stream that
|
|
56
|
+
* omits its leading `VT` that byte is the first byte of the unframed content.
|
|
57
|
+
* One byte, never a run, the same bound `MllpFramingError.snippet` carries. If
|
|
58
|
+
* that is more than your threat model allows, log `code` and `byteOffset`
|
|
59
|
+
* rather than `message`.
|
|
60
|
+
*
|
|
61
|
+
* The ACK-correlation codes are stricter: their text comes from a frozen
|
|
62
|
+
* registry and does not vary with the wire at all.
|
|
63
|
+
*/
|
|
50
64
|
readonly message: string;
|
|
51
65
|
/** Absolute stream byte offset where the anomaly was detected. */
|
|
52
66
|
readonly byteOffset: number;
|
|
@@ -2039,6 +2053,69 @@ declare function createServer(opts?: ServerOptions): MllpServer;
|
|
|
2039
2053
|
*/
|
|
2040
2054
|
declare function createStarterServer(opts: StarterServerOptions): Promise<MllpServer>;
|
|
2041
2055
|
|
|
2056
|
+
/**
|
|
2057
|
+
* The frozen diagnostic registry for ACK correlation.
|
|
2058
|
+
*
|
|
2059
|
+
* Every message an ACK-correlation diagnostic can carry lives here as a
|
|
2060
|
+
* literal, and the lookup takes **only a code**. There is no value parameter
|
|
2061
|
+
* anywhere in this module, which is the whole point: a factory that cannot be
|
|
2062
|
+
* handed a value cannot interpolate one, regardless of what arrives on the
|
|
2063
|
+
* wire.
|
|
2064
|
+
*
|
|
2065
|
+
* ## What this replaces, and why the shape matters more than the wording
|
|
2066
|
+
*
|
|
2067
|
+
* The `correlateByControlId` path used to build its diagnostics by
|
|
2068
|
+
* interpolation, and both ends of it were consumer-controlled and unbounded.
|
|
2069
|
+
* The unmatched-ACK error read the **peer's** MSA-2 straight out of an inbound
|
|
2070
|
+
* frame and put it on an `Error.message`; measured against a peer sending a
|
|
2071
|
+
* one-megabyte MSA-2, that produced a 1,000,026-byte `message` and an
|
|
2072
|
+
* equally large field on the frozen error payload. The late-ACK warning did
|
|
2073
|
+
* the same with the control id of the send being reported on. Both go to a
|
|
2074
|
+
* log, and in this domain a log is a place a patient identifier must not
|
|
2075
|
+
* reach. That is not a hypothetical about control ids either: this package has
|
|
2076
|
+
* already shipped one scanner that ran past a segment terminator and returned
|
|
2077
|
+
* PID-3, a patient's medical record number, as the "control ID" of a truncated
|
|
2078
|
+
* MSH. Withholding the bytes is the defence that survives the next such bug.
|
|
2079
|
+
*
|
|
2080
|
+
* ## What a correlation diagnostic says instead
|
|
2081
|
+
*
|
|
2082
|
+
* That a control id did not match, plus **numbers**: a byte length, a stream
|
|
2083
|
+
* offset, an elapsed time. A number is a shape, not content. A truncated id is
|
|
2084
|
+
* not an acceptable middle ground, and neither is a hex or base64 rendering:
|
|
2085
|
+
* both still grow the diagnostic with the input and both still disclose the
|
|
2086
|
+
* bytes. Nothing is lost by withholding them, because the caller already holds
|
|
2087
|
+
* them: the outbound payload is the caller's own argument to `send()`, and the
|
|
2088
|
+
* inbound frame is on the `'message'` event.
|
|
2089
|
+
*
|
|
2090
|
+
* Mirrors the same answer this package already reached for the ACK adapter's
|
|
2091
|
+
* verbatim-echo warning, which reports byte lengths and withholds MSH-10 and
|
|
2092
|
+
* MSA-2 themselves.
|
|
2093
|
+
*
|
|
2094
|
+
* @packageDocumentation
|
|
2095
|
+
*/
|
|
2096
|
+
/**
|
|
2097
|
+
* The two warning codes emitted by ACK correlation rather than by framing.
|
|
2098
|
+
*
|
|
2099
|
+
* A subset of the framing `WarningCode` union; named separately so the
|
|
2100
|
+
* registry below is exhaustive over exactly the codes it owns and a new
|
|
2101
|
+
* correlation code cannot be added without a message for it.
|
|
2102
|
+
*/
|
|
2103
|
+
type AckCorrelationCode = "MLLP_ACK_UNMATCHED_CONTROL_ID" | "MLLP_ACK_AFTER_TIMEOUT";
|
|
2104
|
+
/**
|
|
2105
|
+
* Look up the frozen diagnostic text for an ACK-correlation code.
|
|
2106
|
+
*
|
|
2107
|
+
* A pure table read. It takes no value, so no caller can widen it into an
|
|
2108
|
+
* interpolation site.
|
|
2109
|
+
*
|
|
2110
|
+
* @param code - The correlation code being reported.
|
|
2111
|
+
* @returns The registered message for that code, byte-for-byte.
|
|
2112
|
+
* @example
|
|
2113
|
+
* ```typescript
|
|
2114
|
+
* const message = ackDiagnosticMessage('MLLP_ACK_AFTER_TIMEOUT');
|
|
2115
|
+
* ```
|
|
2116
|
+
*/
|
|
2117
|
+
declare function ackDiagnosticMessage(code: AckCorrelationCode): string;
|
|
2118
|
+
|
|
2042
2119
|
/**
|
|
2043
2120
|
* MLLP Client, `createClient()` factory and `MllpClient` class.
|
|
2044
2121
|
*
|
|
@@ -2063,6 +2140,37 @@ declare function createStarterServer(opts: StarterServerOptions): Promise<MllpSe
|
|
|
2063
2140
|
* @packageDocumentation
|
|
2064
2141
|
*/
|
|
2065
2142
|
|
|
2143
|
+
/**
|
|
2144
|
+
* The `'warning'` payload the client emits for an ACK-correlation deviation
|
|
2145
|
+
* (`MLLP_ACK_UNMATCHED_CONTROL_ID`, `MLLP_ACK_AFTER_TIMEOUT`).
|
|
2146
|
+
*
|
|
2147
|
+
* A `MllpWarning` with two extra numeric fields. `message` is a **frozen
|
|
2148
|
+
* registry entry**, byte-for-byte identical for a given code no matter what
|
|
2149
|
+
* arrived on the wire, and everything input-derived is a number: the control
|
|
2150
|
+
* ID's byte length and the elapsed time. A warning is a log line, so it carries
|
|
2151
|
+
* no field content.
|
|
2152
|
+
*
|
|
2153
|
+
* @example
|
|
2154
|
+
* ```typescript
|
|
2155
|
+
* client.on('warning', (w: AckCorrelationWarning) => {
|
|
2156
|
+
* logger.warn({ code: w.code, idBytes: w.controlIdBytes, elapsedMs: w.elapsedSinceSendMs });
|
|
2157
|
+
* });
|
|
2158
|
+
* ```
|
|
2159
|
+
*/
|
|
2160
|
+
interface AckCorrelationWarning extends MllpWarning {
|
|
2161
|
+
/** The correlation code this warning reports. */
|
|
2162
|
+
readonly code: AckCorrelationCode;
|
|
2163
|
+
/**
|
|
2164
|
+
* Byte length of the control ID involved, or `null` when there was none to
|
|
2165
|
+
* read. Control IDs are decoded `latin1`, so a code-unit count is a byte count.
|
|
2166
|
+
*/
|
|
2167
|
+
readonly controlIdBytes: number | null;
|
|
2168
|
+
/**
|
|
2169
|
+
* Milliseconds between the send's write-flush (or its timeout, for a late
|
|
2170
|
+
* ACK) and this warning.
|
|
2171
|
+
*/
|
|
2172
|
+
readonly elapsedSinceSendMs: number;
|
|
2173
|
+
}
|
|
2066
2174
|
/**
|
|
2067
2175
|
* Context passed to a custom `retryStrategy` hook on each reconnect attempt.
|
|
2068
2176
|
*
|
|
@@ -2315,7 +2423,10 @@ interface ClientStats {
|
|
|
2315
2423
|
* - `'reconnecting'`, `{ connectionId, attempt?, delayMs? }`
|
|
2316
2424
|
* - `'close'`, `{ connectionId }` once the FSM enters terminal `CLOSED`
|
|
2317
2425
|
* - `'message'`, `{ payload, connectionId, byteOffset, warnings }` for every inbound frame
|
|
2318
|
-
* - `'warning'`, `MllpWarning
|
|
2426
|
+
* - `'warning'`, `MllpWarning | AckCorrelationWarning`. A framing deviation arrives as a
|
|
2427
|
+
* `MllpWarning` enriched with `connectionId` from the Connection layer; the two
|
|
2428
|
+
* ACK-correlation codes arrive as {@link AckCorrelationWarning}, which adds
|
|
2429
|
+
* `controlIdBytes` and `elapsedSinceSendMs`. Narrow on `code` before reading either.
|
|
2319
2430
|
* - `'securityWarning'`, `SecurityWarning`. Emitted on every successful
|
|
2320
2431
|
* `secureConnect` (initial + every reconnect) when `tls.allowUnverified` is `true`
|
|
2321
2432
|
* with code `MLLP_TLS_VERIFY_DISABLED`. Also mirrored to `process.emitWarning`.
|
|
@@ -2870,15 +2981,27 @@ declare function createStarterClient(opts: StarterClientOptions): Promise<MllpCl
|
|
|
2870
2981
|
* await client.send(payload);
|
|
2871
2982
|
* } catch (err) {
|
|
2872
2983
|
* if (err instanceof MllpTimeoutError) {
|
|
2873
|
-
* logger.warn({ elapsedMs: err.elapsedMs,
|
|
2984
|
+
* logger.warn({ elapsedMs: err.elapsedMs, idBytes: err.messageControlIdBytes });
|
|
2874
2985
|
* }
|
|
2875
2986
|
* }
|
|
2876
2987
|
* ```
|
|
2877
2988
|
*/
|
|
2878
2989
|
declare class MllpTimeoutError extends Error {
|
|
2879
2990
|
readonly name: "MllpTimeoutError";
|
|
2880
|
-
/**
|
|
2881
|
-
|
|
2991
|
+
/**
|
|
2992
|
+
* Byte length of the timed-out send's MSH-10 control ID, or `undefined` when
|
|
2993
|
+
* there was none to read (FIFO mode, or a payload with no MSH-10).
|
|
2994
|
+
*
|
|
2995
|
+
* **The control ID itself is deliberately not here.** An `Error` is a
|
|
2996
|
+
* diagnostic surface: it is logged, and its `stack` is what an error reporter
|
|
2997
|
+
* ships off the box. MSH-10 is payload content, and a scanner that returns
|
|
2998
|
+
* the wrong field returns payload content of some other kind, which is how a
|
|
2999
|
+
* patient identifier reaches a log line. Nothing is lost by withholding it,
|
|
3000
|
+
* because this error rejects the very `send()` whose payload the caller
|
|
3001
|
+
* passed in, so the caller already holds the bytes. Control IDs are decoded
|
|
3002
|
+
* `latin1`, a 1:1 byte to code-unit map, so this count is a byte count.
|
|
3003
|
+
*/
|
|
3004
|
+
readonly messageControlIdBytes: number | undefined;
|
|
2882
3005
|
/** Milliseconds elapsed between write-flush and timeout fire. */
|
|
2883
3006
|
readonly elapsedMs: number;
|
|
2884
3007
|
/** Epoch ms timestamp recorded at write-flush callback. */
|
|
@@ -2886,11 +3009,11 @@ declare class MllpTimeoutError extends Error {
|
|
|
2886
3009
|
/**
|
|
2887
3010
|
* Construct an MLLP timeout error.
|
|
2888
3011
|
*
|
|
2889
|
-
* @param message - Human-readable error message.
|
|
2890
|
-
* @param opts - Timeout context (
|
|
3012
|
+
* @param message - Human-readable error message. Structural facts only, never field content.
|
|
3013
|
+
* @param opts - Timeout context (control-id byte length, elapsed time, flush timestamp).
|
|
2891
3014
|
*/
|
|
2892
3015
|
constructor(message: string, opts: {
|
|
2893
|
-
|
|
3016
|
+
messageControlIdBytes: number | undefined;
|
|
2894
3017
|
elapsedMs: number;
|
|
2895
3018
|
sentAt: number;
|
|
2896
3019
|
});
|
|
@@ -3091,4 +3214,4 @@ declare class MllpBackpressureError extends Error {
|
|
|
3091
3214
|
*/
|
|
3092
3215
|
declare const VERSION: string;
|
|
3093
3216
|
|
|
3094
|
-
export { type AckCode, type ClientAuth, type ClientOptions, type ClientStats, Connection, type ConnectionErrorCause, type ConnectionErrorPhase, type ConnectionOptions, type ConnectionState, type ConnectionStats, type EncoderOptions, FrameReader, type FrameReaderOptions, MLLP_BIND_ALL_INTERFACES, MLLP_TLS_VERIFY_DISABLED, type MessageMeta, MllpAckError, MllpBackpressureError, MllpClient, MllpConnectionError, MllpFramingError, MllpServer, MllpTimeoutError, type MllpWarning, type NackEvent, type NackReason, type NegativeAckCode, NetTransport, type PemInput, type ReconnectingEvent, type RetryContext, type RetryStrategy, type SecurityWarning, type SecurityWarningCode, type ServerOptions, type ServerStats, type ServerTlsOptions, type StarterClientOptions, type StarterServerOptions, type StateChangeEvent, type TlsOptions, TlsTransport, Transport, VERSION, type WarningCode, buildRawAck, createClient, createServer, createStarterClient, createStarterServer, createWarning, encodeFrame, isTlsProtocolError, isTlsVerificationErrorCode, isTransientConnectionError, rawAckUncorrelatable, resolveNackCode };
|
|
3217
|
+
export { type AckCode, type AckCorrelationCode, type AckCorrelationWarning, type ClientAuth, type ClientOptions, type ClientStats, Connection, type ConnectionErrorCause, type ConnectionErrorPhase, type ConnectionOptions, type ConnectionState, type ConnectionStats, type EncoderOptions, FrameReader, type FrameReaderOptions, MLLP_BIND_ALL_INTERFACES, MLLP_TLS_VERIFY_DISABLED, type MessageMeta, MllpAckError, MllpBackpressureError, MllpClient, MllpConnectionError, MllpFramingError, MllpServer, MllpTimeoutError, type MllpWarning, type NackEvent, type NackReason, type NegativeAckCode, NetTransport, type PemInput, type ReconnectingEvent, type RetryContext, type RetryStrategy, type SecurityWarning, type SecurityWarningCode, type ServerOptions, type ServerStats, type ServerTlsOptions, type StarterClientOptions, type StarterServerOptions, type StateChangeEvent, type TlsOptions, TlsTransport, Transport, VERSION, type WarningCode, ackDiagnosticMessage, buildRawAck, createClient, createServer, createStarterClient, createStarterServer, createWarning, encodeFrame, isTlsProtocolError, isTlsVerificationErrorCode, isTransientConnectionError, rawAckUncorrelatable, resolveNackCode };
|
package/dist/index.mjs
CHANGED
|
@@ -2005,8 +2005,8 @@ var Correlator = class {
|
|
|
2005
2005
|
* removed from live store. Caller calls `entry.resolve(ackPayload)`.
|
|
2006
2006
|
* - controlId: keyed lookup by `controlIdFromAck`. Live-store hit returns
|
|
2007
2007
|
* the entry; graveyard hit fires `MLLP_ACK_AFTER_TIMEOUT` warning
|
|
2008
|
-
* and returns `null`; otherwise fires
|
|
2009
|
-
*
|
|
2008
|
+
* and returns `null`; otherwise fires `onUnmatchedAck` with the id's
|
|
2009
|
+
* **byte length** and returns `null`.
|
|
2010
2010
|
*
|
|
2011
2011
|
* Triggers lazy graveyard eviction.
|
|
2012
2012
|
*
|
|
@@ -2029,7 +2029,7 @@ var Correlator = class {
|
|
|
2029
2029
|
}
|
|
2030
2030
|
if (controlIdFromAck === null) {
|
|
2031
2031
|
if (this._opts.onUnmatchedAck !== void 0) {
|
|
2032
|
-
this._opts.onUnmatchedAck(
|
|
2032
|
+
this._opts.onUnmatchedAck(null);
|
|
2033
2033
|
}
|
|
2034
2034
|
return null;
|
|
2035
2035
|
}
|
|
@@ -2044,7 +2044,9 @@ var Correlator = class {
|
|
|
2044
2044
|
if (grave !== void 0) {
|
|
2045
2045
|
const elapsedSinceSendMs = this._opts.now() - grave.timedOutAt;
|
|
2046
2046
|
this._opts.onWarning("MLLP_ACK_AFTER_TIMEOUT", {
|
|
2047
|
-
|
|
2047
|
+
// The LENGTH, never the id. Control IDs are decoded `latin1`, a 1:1
|
|
2048
|
+
// byte to code-unit map, so `.length` is a byte count.
|
|
2049
|
+
controlIdBytes: grave.controlId === null ? null : grave.controlId.length,
|
|
2048
2050
|
elapsedSinceSendMs,
|
|
2049
2051
|
byteOffset: byteOffsetFromAck
|
|
2050
2052
|
});
|
|
@@ -2052,7 +2054,7 @@ var Correlator = class {
|
|
|
2052
2054
|
return null;
|
|
2053
2055
|
}
|
|
2054
2056
|
if (this._opts.onUnmatchedAck !== void 0) {
|
|
2055
|
-
this._opts.onUnmatchedAck(controlIdFromAck);
|
|
2057
|
+
this._opts.onUnmatchedAck(controlIdFromAck.length);
|
|
2056
2058
|
}
|
|
2057
2059
|
return null;
|
|
2058
2060
|
}
|
|
@@ -2127,11 +2129,32 @@ var Correlator = class {
|
|
|
2127
2129
|
}
|
|
2128
2130
|
};
|
|
2129
2131
|
|
|
2132
|
+
// src/client/ack-diagnostics.ts
|
|
2133
|
+
var ACK_DIAGNOSTIC_MESSAGES = Object.freeze({
|
|
2134
|
+
MLLP_ACK_UNMATCHED_CONTROL_ID: "Inbound ACK control ID matched no in-flight send and no timed-out send; the ACK was dropped. Field values are withheld: MSA-2 is inbound payload content and this diagnostic goes to a log. The inbound bytes are on the 'message' event; controlIdBytes gives the length.",
|
|
2135
|
+
MLLP_ACK_AFTER_TIMEOUT: "ACK arrived after its send had already timed out; the ACK was dropped. Field values are withheld: a control ID is payload content and this diagnostic goes to a log. The outbound bytes are the payload you passed to send(); controlIdBytes gives the length."
|
|
2136
|
+
});
|
|
2137
|
+
function ackDiagnosticMessage(code) {
|
|
2138
|
+
return ACK_DIAGNOSTIC_MESSAGES[code];
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2130
2141
|
// src/client/error.ts
|
|
2131
2142
|
var MllpTimeoutError = class _MllpTimeoutError extends Error {
|
|
2132
2143
|
name = "MllpTimeoutError";
|
|
2133
|
-
/**
|
|
2134
|
-
|
|
2144
|
+
/**
|
|
2145
|
+
* Byte length of the timed-out send's MSH-10 control ID, or `undefined` when
|
|
2146
|
+
* there was none to read (FIFO mode, or a payload with no MSH-10).
|
|
2147
|
+
*
|
|
2148
|
+
* **The control ID itself is deliberately not here.** An `Error` is a
|
|
2149
|
+
* diagnostic surface: it is logged, and its `stack` is what an error reporter
|
|
2150
|
+
* ships off the box. MSH-10 is payload content, and a scanner that returns
|
|
2151
|
+
* the wrong field returns payload content of some other kind, which is how a
|
|
2152
|
+
* patient identifier reaches a log line. Nothing is lost by withholding it,
|
|
2153
|
+
* because this error rejects the very `send()` whose payload the caller
|
|
2154
|
+
* passed in, so the caller already holds the bytes. Control IDs are decoded
|
|
2155
|
+
* `latin1`, a 1:1 byte to code-unit map, so this count is a byte count.
|
|
2156
|
+
*/
|
|
2157
|
+
messageControlIdBytes;
|
|
2135
2158
|
/** Milliseconds elapsed between write-flush and timeout fire. */
|
|
2136
2159
|
elapsedMs;
|
|
2137
2160
|
/** Epoch ms timestamp recorded at write-flush callback. */
|
|
@@ -2139,12 +2162,12 @@ var MllpTimeoutError = class _MllpTimeoutError extends Error {
|
|
|
2139
2162
|
/**
|
|
2140
2163
|
* Construct an MLLP timeout error.
|
|
2141
2164
|
*
|
|
2142
|
-
* @param message - Human-readable error message.
|
|
2143
|
-
* @param opts - Timeout context (
|
|
2165
|
+
* @param message - Human-readable error message. Structural facts only, never field content.
|
|
2166
|
+
* @param opts - Timeout context (control-id byte length, elapsed time, flush timestamp).
|
|
2144
2167
|
*/
|
|
2145
2168
|
constructor(message, opts) {
|
|
2146
2169
|
super(message);
|
|
2147
|
-
this.
|
|
2170
|
+
this.messageControlIdBytes = opts.messageControlIdBytes;
|
|
2148
2171
|
this.elapsedMs = opts.elapsedMs;
|
|
2149
2172
|
this.sentAt = opts.sentAt;
|
|
2150
2173
|
if (Error.captureStackTrace) {
|
|
@@ -2587,31 +2610,34 @@ var MllpClient = class extends EventEmitter {
|
|
|
2587
2610
|
maxInFlight: this._pipeline ? Number.POSITIVE_INFINITY : 1,
|
|
2588
2611
|
onWarning: (code, ctx) => {
|
|
2589
2612
|
this._aggregatedWarningsByCode[code] = (this._aggregatedWarningsByCode[code] ?? 0) + 1;
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
);
|
|
2613
|
+
const warning = Object.freeze({
|
|
2614
|
+
code,
|
|
2615
|
+
byteOffset: ctx.byteOffset,
|
|
2616
|
+
message: ackDiagnosticMessage(code),
|
|
2617
|
+
controlIdBytes: ctx.controlIdBytes,
|
|
2618
|
+
elapsedSinceSendMs: ctx.elapsedSinceSendMs,
|
|
2619
|
+
connectionId: this._connection?.connectionId ?? conn.connectionId,
|
|
2620
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
2621
|
+
});
|
|
2622
|
+
this._emitContained("warning", warning);
|
|
2600
2623
|
},
|
|
2601
|
-
onUnmatchedAck: (
|
|
2624
|
+
onUnmatchedAck: (controlIdBytes) => {
|
|
2602
2625
|
if (this.listenerCount("error") === 0) return;
|
|
2603
2626
|
const err = new MllpFramingError(
|
|
2604
2627
|
"MLLP_ACK_UNMATCHED_CONTROL_ID",
|
|
2605
2628
|
0,
|
|
2606
2629
|
Buffer.alloc(0),
|
|
2607
|
-
|
|
2630
|
+
ackDiagnosticMessage("MLLP_ACK_UNMATCHED_CONTROL_ID")
|
|
2608
2631
|
);
|
|
2609
2632
|
safeEmitError(
|
|
2610
2633
|
this,
|
|
2611
2634
|
Object.freeze({
|
|
2612
2635
|
connectionId: this._connection?.connectionId ?? conn.connectionId,
|
|
2613
2636
|
error: err,
|
|
2614
|
-
|
|
2637
|
+
// The peer's MSA-2 byte LENGTH. The bytes themselves used to be
|
|
2638
|
+
// here and on the message above; a peer sending a one-megabyte
|
|
2639
|
+
// MSA-2 produced a one-megabyte Error.message, straight into a log.
|
|
2640
|
+
controlIdBytes
|
|
2615
2641
|
})
|
|
2616
2642
|
);
|
|
2617
2643
|
},
|
|
@@ -2619,7 +2645,7 @@ var MllpClient = class extends EventEmitter {
|
|
|
2619
2645
|
this._timedOutTotal += 1;
|
|
2620
2646
|
entry.reject(
|
|
2621
2647
|
new MllpTimeoutError(`ACK timeout after ${elapsedMs}ms`, {
|
|
2622
|
-
|
|
2648
|
+
messageControlIdBytes: entry.controlId?.length,
|
|
2623
2649
|
elapsedMs,
|
|
2624
2650
|
sentAt: entry.sentAt ?? 0
|
|
2625
2651
|
})
|
|
@@ -3172,7 +3198,7 @@ var MllpClient = class extends EventEmitter {
|
|
|
3172
3198
|
cleanup();
|
|
3173
3199
|
reject(
|
|
3174
3200
|
new MllpTimeoutError(`waiting for drain timed out after ${ackTimeoutMs}ms`, {
|
|
3175
|
-
|
|
3201
|
+
messageControlIdBytes: void 0,
|
|
3176
3202
|
elapsedMs: ackTimeoutMs,
|
|
3177
3203
|
sentAt: Date.now()
|
|
3178
3204
|
})
|
|
@@ -3410,8 +3436,8 @@ async function createStarterClient(opts) {
|
|
|
3410
3436
|
}
|
|
3411
3437
|
|
|
3412
3438
|
// src/index.ts
|
|
3413
|
-
var VERSION = "0.0.
|
|
3439
|
+
var VERSION = "0.0.5";
|
|
3414
3440
|
|
|
3415
|
-
export { Connection, FrameReader, MLLP_BIND_ALL_INTERFACES, MLLP_TLS_VERIFY_DISABLED, MllpAckError, MllpBackpressureError, MllpClient, MllpConnectionError, MllpFramingError, MllpServer, MllpTimeoutError, NetTransport, TlsTransport, VERSION, buildRawAck, createClient, createServer, createStarterClient, createStarterServer, createWarning, encodeFrame, isTlsProtocolError, isTlsVerificationErrorCode, isTransientConnectionError, rawAckUncorrelatable, resolveNackCode };
|
|
3441
|
+
export { Connection, FrameReader, MLLP_BIND_ALL_INTERFACES, MLLP_TLS_VERIFY_DISABLED, MllpAckError, MllpBackpressureError, MllpClient, MllpConnectionError, MllpFramingError, MllpServer, MllpTimeoutError, NetTransport, TlsTransport, VERSION, ackDiagnosticMessage, buildRawAck, createClient, createServer, createStarterClient, createStarterServer, createWarning, encodeFrame, isTlsProtocolError, isTlsVerificationErrorCode, isTransientConnectionError, rawAckUncorrelatable, resolveNackCode };
|
|
3416
3442
|
//# sourceMappingURL=index.mjs.map
|
|
3417
3443
|
//# sourceMappingURL=index.mjs.map
|