@cosyte/mllp 0.0.2 → 0.0.4
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 +80 -0
- package/README.md +21 -5
- package/dist/ack-from-hl7/index.cjs +1 -1
- package/dist/ack-from-hl7/index.cjs.map +1 -1
- package/dist/ack-from-hl7/index.d.cts +4 -5
- package/dist/ack-from-hl7/index.d.ts +4 -5
- package/dist/ack-from-hl7/index.mjs +1 -1
- package/dist/ack-from-hl7/index.mjs.map +1 -1
- package/dist/{index-DAZ4BvJE.d.cts → index-CD8V2Mtk.d.cts} +1 -1
- package/dist/{index-DAZ4BvJE.d.ts → index-CD8V2Mtk.d.ts} +1 -1
- package/dist/index.cjs +92 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +216 -234
- package/dist/index.d.ts +216 -234
- package/dist/index.mjs +92 -103
- package/dist/index.mjs.map +1 -1
- package/dist/testing/index.d.cts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as Transport } from './index-
|
|
1
|
+
import { T as Transport } from './index-CD8V2Mtk.js';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
3
|
import { Socket } from 'node:net';
|
|
4
4
|
import { TLSSocket } from 'node:tls';
|
|
@@ -32,8 +32,8 @@ type WarningCode = "MLLP_MISSING_LEADING_VT" | "MLLP_FS_WITHOUT_CR" | "MLLP_LF_A
|
|
|
32
32
|
/**
|
|
33
33
|
* A frozen warning object emitted when the decoder tolerates a framing deviation.
|
|
34
34
|
*
|
|
35
|
-
* `connectionId` is `undefined` when emitted by a standalone `FrameReader
|
|
36
|
-
*
|
|
35
|
+
* `connectionId` is `undefined` when emitted by a standalone `FrameReader`.
|
|
36
|
+
* `Connection` enriches it to the real UUIDv4 before forwarding upstream.
|
|
37
37
|
*
|
|
38
38
|
* @example
|
|
39
39
|
* ```typescript
|
|
@@ -52,7 +52,7 @@ interface MllpWarning {
|
|
|
52
52
|
readonly byteOffset: number;
|
|
53
53
|
/**
|
|
54
54
|
* Connection identifier. `undefined` at the framing layer;
|
|
55
|
-
* enriched by
|
|
55
|
+
* enriched by `Connection` before emitting upstream.
|
|
56
56
|
*/
|
|
57
57
|
readonly connectionId: string | undefined;
|
|
58
58
|
/** Wall-clock time at point of emission. */
|
|
@@ -62,8 +62,8 @@ interface MllpWarning {
|
|
|
62
62
|
* Create a frozen `MllpWarning` object. The returned object is `Object.freeze()`'d
|
|
63
63
|
* so subscribers cannot mutate shared warning state.
|
|
64
64
|
*
|
|
65
|
-
* `connectionId` is always `undefined` here
|
|
66
|
-
* `{ ...w, connectionId: this.connectionId }` then re-freezes
|
|
65
|
+
* `connectionId` is always `undefined` here; `Connection` enriches it via
|
|
66
|
+
* `{ ...w, connectionId: this.connectionId }` then re-freezes.
|
|
67
67
|
*
|
|
68
68
|
* @example
|
|
69
69
|
* ```typescript
|
|
@@ -118,7 +118,7 @@ declare class MllpFramingError extends Error {
|
|
|
118
118
|
* This is a **copied** Buffer, isolated from the source buffer so it remains
|
|
119
119
|
* valid after the underlying buffer is reused or overwritten.
|
|
120
120
|
*
|
|
121
|
-
* **PHI contract
|
|
121
|
+
* **PHI contract:** the decoder only ever populates this with the single
|
|
122
122
|
* framing-boundary byte that violated the frame structure (whose hex value the
|
|
123
123
|
* `message` already discloses), never a run of payload content bytes. For anomalies
|
|
124
124
|
* whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE`, the accumulated size),
|
|
@@ -180,7 +180,7 @@ interface EncoderOptions {
|
|
|
180
180
|
allowDelimiterBytesInPayload?: boolean;
|
|
181
181
|
/**
|
|
182
182
|
* Called for each offending delimiter byte when `allowDelimiterBytesInPayload` is `true`.
|
|
183
|
-
* Invocation is wrapped in try/catch, a throwing handler does not interrupt encoding
|
|
183
|
+
* Invocation is wrapped in try/catch, a throwing handler does not interrupt encoding.
|
|
184
184
|
*
|
|
185
185
|
* @example
|
|
186
186
|
* ```typescript
|
|
@@ -254,7 +254,7 @@ declare function encodeFrame(payload: Buffer, opts?: EncoderOptions): Buffer;
|
|
|
254
254
|
*
|
|
255
255
|
* All tolerance opts default to `false`, every framing deviation throws unless
|
|
256
256
|
* explicitly enabled. Server-level defaults (allowFsOnly, allowLfAfterFs, allowLeadingWhitespace)
|
|
257
|
-
* are applied by
|
|
257
|
+
* are applied by the server when constructing readers.
|
|
258
258
|
*
|
|
259
259
|
* @example
|
|
260
260
|
* ```typescript
|
|
@@ -279,27 +279,27 @@ interface FrameReaderOptions {
|
|
|
279
279
|
*/
|
|
280
280
|
onFrame: (payload: Buffer, byteOffset: number, warnings: readonly MllpWarning[]) => void;
|
|
281
281
|
/**
|
|
282
|
-
* Called for each tolerated framing deviation. Wrapped in try/catch
|
|
282
|
+
* Called for each tolerated framing deviation. Wrapped in try/catch.
|
|
283
283
|
* A throwing handler will not corrupt FSM state.
|
|
284
284
|
*/
|
|
285
285
|
onWarning?: (w: MllpWarning) => void;
|
|
286
286
|
/**
|
|
287
287
|
* Maximum accumulated payload bytes before `MllpFramingError('MLLP_FRAME_TOO_LARGE')`.
|
|
288
|
-
* Default: 16 MiB (
|
|
288
|
+
* Default: 16 MiB (DoS prevention).
|
|
289
289
|
*/
|
|
290
290
|
maxFrameSizeBytes?: number;
|
|
291
|
-
/**
|
|
291
|
+
/** Tolerate FS without trailing CR; emits `MLLP_FS_WITHOUT_CR`. */
|
|
292
292
|
allowFsOnly?: boolean;
|
|
293
|
-
/**
|
|
293
|
+
/** Tolerate FS+LF instead of FS+CR; emits `MLLP_LF_AFTER_FS`. */
|
|
294
294
|
allowLfAfterFs?: boolean;
|
|
295
|
-
/**
|
|
295
|
+
/** Tolerate missing leading VT; emits `MLLP_MISSING_LEADING_VT`. */
|
|
296
296
|
allowMissingLeadingVt?: boolean;
|
|
297
|
-
/**
|
|
297
|
+
/** Tolerate SP/TAB/LF/CR before VT; emits `MLLP_LEADING_WHITESPACE`. */
|
|
298
298
|
allowLeadingWhitespace?: boolean;
|
|
299
299
|
/**
|
|
300
300
|
* When `true`, escalates the following tolerances to thrown `MllpFramingError`
|
|
301
301
|
* even if individual opt-ins (`allowFsOnly`, `allowLfAfterFs`, `allowMissingLeadingVt`,
|
|
302
|
-
* `allowLeadingWhitespace`) are enabled
|
|
302
|
+
* `allowLeadingWhitespace`) are enabled:
|
|
303
303
|
* - `MLLP_MISSING_LEADING_VT`
|
|
304
304
|
* - `MLLP_FS_WITHOUT_CR`
|
|
305
305
|
* - `MLLP_LF_AFTER_FS`
|
|
@@ -343,9 +343,9 @@ declare class FrameReader {
|
|
|
343
343
|
private _writePos;
|
|
344
344
|
/** Monotonic absolute stream byte offset across the entire connection lifetime. */
|
|
345
345
|
private _byteOffset;
|
|
346
|
-
/** Byte offset where current leading-whitespace run started
|
|
346
|
+
/** Byte offset where current leading-whitespace run started. */
|
|
347
347
|
private _wsStart;
|
|
348
|
-
/** Count of leading whitespace bytes accumulated in current run
|
|
348
|
+
/** Count of leading whitespace bytes accumulated in current run. */
|
|
349
349
|
private _wsCount;
|
|
350
350
|
/** Byte offset of the VT byte that started the current frame. */
|
|
351
351
|
private _frameStartOffset;
|
|
@@ -371,7 +371,7 @@ declare class FrameReader {
|
|
|
371
371
|
/**
|
|
372
372
|
* Clear internal accumulator state and reset byte offset to 0.
|
|
373
373
|
*
|
|
374
|
-
* Call on reconnect / connection reuse to start fresh without allocating a new reader
|
|
374
|
+
* Call on reconnect / connection reuse to start fresh without allocating a new reader.
|
|
375
375
|
* Pending partial frame state is discarded silently.
|
|
376
376
|
*
|
|
377
377
|
* @example
|
|
@@ -388,10 +388,10 @@ declare class FrameReader {
|
|
|
388
388
|
private _appendByte;
|
|
389
389
|
/**
|
|
390
390
|
* Copy accumulated payload bytes into a new Buffer and deliver via `onFrame`.
|
|
391
|
-
* The copy isolates callers from internal accumulator reuse
|
|
391
|
+
* The copy isolates callers from internal accumulator reuse.
|
|
392
392
|
*/
|
|
393
393
|
private _deliverFrame;
|
|
394
|
-
/** Emit a warning via the `onWarning` callback, swallowing any handler exceptions
|
|
394
|
+
/** Emit a warning via the `onWarning` callback, swallowing any handler exceptions. */
|
|
395
395
|
private _emitWarning;
|
|
396
396
|
}
|
|
397
397
|
|
|
@@ -455,7 +455,7 @@ declare class NetTransport implements Transport {
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
/**
|
|
458
|
-
* TlsTransport, `Transport` implementation wrapping a `tls.TLSSocket
|
|
458
|
+
* TlsTransport, `Transport` implementation wrapping a `tls.TLSSocket`.
|
|
459
459
|
*
|
|
460
460
|
* Identical wiring to `NetTransport` with one deliberate difference: `onConnect`
|
|
461
461
|
* maps to the socket's `'secureConnect'` event (fires once the TLS handshake
|
|
@@ -522,7 +522,7 @@ declare class TlsTransport implements Transport {
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
/**
|
|
525
|
-
* Security-warning codes and payload shape shared by the client and server
|
|
525
|
+
* Security-warning codes and payload shape shared by the client and server.
|
|
526
526
|
*
|
|
527
527
|
* These codes are a **public API**, they appear in `'securityWarning'` event
|
|
528
528
|
* handlers, log pipelines, and monitoring dashboards. Renaming or removing a
|
|
@@ -608,7 +608,7 @@ interface SecurityWarning {
|
|
|
608
608
|
}
|
|
609
609
|
|
|
610
610
|
/**
|
|
611
|
-
* TLS (MLLPS) option types for the client and server
|
|
611
|
+
* TLS (MLLPS) option types for the client and server.
|
|
612
612
|
*
|
|
613
613
|
* Spec anchor: IHE ATNA, ITI-19 Authenticate Node
|
|
614
614
|
* (https://profiles.ihe.net/ITI/TF/Volume2/ITI-19.html), the "STX: TLS 1.2
|
|
@@ -642,7 +642,7 @@ interface SecurityWarning {
|
|
|
642
642
|
*/
|
|
643
643
|
type PemInput = string | Buffer | Array<string | Buffer>;
|
|
644
644
|
/**
|
|
645
|
-
* Client-side TLS options (
|
|
645
|
+
* Client-side TLS options (`ClientOptions.tls`).
|
|
646
646
|
*
|
|
647
647
|
* Passing `true` for `ClientOptions.tls` is equivalent to `{}`, TLS enabled
|
|
648
648
|
* with all defaults, including certificate verification **on**.
|
|
@@ -728,7 +728,7 @@ interface TlsOptions {
|
|
|
728
728
|
*/
|
|
729
729
|
type ClientAuth = "NONE" | "WANT" | "MUST";
|
|
730
730
|
/**
|
|
731
|
-
* Server-side TLS options (
|
|
731
|
+
* Server-side TLS options (`ServerOptions.tls`).
|
|
732
732
|
*
|
|
733
733
|
* @example
|
|
734
734
|
* ```typescript
|
|
@@ -795,9 +795,9 @@ interface ServerTlsOptions {
|
|
|
795
795
|
*/
|
|
796
796
|
|
|
797
797
|
/**
|
|
798
|
-
* The 6 connection states
|
|
798
|
+
* The 6 connection states.
|
|
799
799
|
*
|
|
800
|
-
* Transitions are validated against the
|
|
800
|
+
* Transitions are validated against the legal-transition edge graph. Illegal transitions
|
|
801
801
|
* are silently ignored to preserve FSM integrity.
|
|
802
802
|
*/
|
|
803
803
|
type ConnectionState = "CONNECTING" | "CONNECTED" | "DRAINING" | "RECONNECTING" | "DISCONNECTED" | "CLOSED";
|
|
@@ -819,8 +819,8 @@ interface StateChangeEvent {
|
|
|
819
819
|
/**
|
|
820
820
|
* Payload for the 'reconnecting' event. Always `Object.freeze`'d.
|
|
821
821
|
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
822
|
+
* `attempt` and `delayMs` are populated by `MllpClient` when it schedules a
|
|
823
|
+
* reconnect. A `Connection` emitting on its own supplies `connectionId` only.
|
|
824
824
|
*
|
|
825
825
|
* @example
|
|
826
826
|
* ```typescript
|
|
@@ -835,7 +835,7 @@ interface ReconnectingEvent {
|
|
|
835
835
|
readonly delayMs?: number;
|
|
836
836
|
}
|
|
837
837
|
/**
|
|
838
|
-
* Return type of `connection.getStats()`, JSON-serializable
|
|
838
|
+
* Return type of `connection.getStats()`, JSON-serializable.
|
|
839
839
|
*
|
|
840
840
|
* All timestamps are `Date | null` (not epoch milliseconds). `JSON.stringify()`
|
|
841
841
|
* serialises them to ISO 8601 strings by default with no information loss.
|
|
@@ -877,9 +877,9 @@ interface ConnectionOptions {
|
|
|
877
877
|
transport: Transport;
|
|
878
878
|
/** Called for each decoded MLLP frame (raw payload bytes, framing stripped). */
|
|
879
879
|
onMessage?: (payload: Buffer) => void;
|
|
880
|
-
/** Per-connection warning subscriber
|
|
880
|
+
/** Per-connection warning subscriber. Replaces previous subscription. */
|
|
881
881
|
onWarning?: (w: MllpWarning) => void;
|
|
882
|
-
/** Drain timeout used by close()
|
|
882
|
+
/** Drain timeout used by close() (default: 30_000 ms). */
|
|
883
883
|
drainTimeoutMs?: number;
|
|
884
884
|
/** FrameReader options (tolerance, maxFrameSizeBytes). onFrame/onWarning are managed internally. */
|
|
885
885
|
framing?: Omit<FrameReaderOptions, "onFrame" | "onWarning">;
|
|
@@ -906,7 +906,7 @@ interface ConnectionOptions {
|
|
|
906
906
|
* ```
|
|
907
907
|
*/
|
|
908
908
|
declare class Connection extends EventEmitter {
|
|
909
|
-
/** Stable UUIDv4 identifier for this connection
|
|
909
|
+
/** Stable UUIDv4 identifier for this connection. */
|
|
910
910
|
readonly connectionId: string;
|
|
911
911
|
private _state;
|
|
912
912
|
private readonly _transport;
|
|
@@ -919,20 +919,19 @@ declare class Connection extends EventEmitter {
|
|
|
919
919
|
private _bytesOut;
|
|
920
920
|
private _lastByteInAt;
|
|
921
921
|
private _lastByteOutAt;
|
|
922
|
-
/** Ring buffer: last MAX_WARNINGS entries
|
|
922
|
+
/** Ring buffer: last MAX_WARNINGS entries. */
|
|
923
923
|
private _warningBuffer;
|
|
924
|
-
/** Accurate total counts per code, unaffected by ring buffer truncation
|
|
924
|
+
/** Accurate total counts per code, unaffected by ring buffer truncation. */
|
|
925
925
|
private readonly _warningsByCode;
|
|
926
|
-
/** Set to true once the ring buffer overflows
|
|
926
|
+
/** Set to true once the ring buffer overflows. */
|
|
927
927
|
private _warningsTruncated;
|
|
928
928
|
private _onWarningFn;
|
|
929
929
|
private _drainPromise;
|
|
930
930
|
/**
|
|
931
931
|
* beforeClose hook, no-op default that resolves immediately.
|
|
932
932
|
*
|
|
933
|
-
*
|
|
934
|
-
*
|
|
935
|
-
* Connection calls this during `close()`, racing against `drainTimeoutMs`.
|
|
933
|
+
* An owner may replace this instance property to run work before the socket
|
|
934
|
+
* closes. Connection calls it during `close()`, racing against `drainTimeoutMs`.
|
|
936
935
|
*/
|
|
937
936
|
beforeClose: (drainTimeoutMs: number) => Promise<void>;
|
|
938
937
|
/**
|
|
@@ -943,16 +942,16 @@ declare class Connection extends EventEmitter {
|
|
|
943
942
|
*/
|
|
944
943
|
constructor(opts: ConnectionOptions);
|
|
945
944
|
/**
|
|
946
|
-
* Current FSM state. One of the 6 `ConnectionState` values
|
|
945
|
+
* Current FSM state. One of the 6 `ConnectionState` values.
|
|
947
946
|
*
|
|
948
947
|
* Subscribe to `'stateChange'` events for reactive state monitoring.
|
|
949
948
|
*/
|
|
950
949
|
get state(): ConnectionState;
|
|
951
950
|
/**
|
|
952
|
-
* Register or replace the per-connection warning subscriber
|
|
951
|
+
* Register or replace the per-connection warning subscriber.
|
|
953
952
|
*
|
|
954
953
|
* Subsequent calls replace the previous handler (set-once semantics prevent leaks).
|
|
955
|
-
* The handler is called synchronously and any exception it throws is swallowed
|
|
954
|
+
* The handler is called synchronously and any exception it throws is swallowed.
|
|
956
955
|
*
|
|
957
956
|
* @example
|
|
958
957
|
* ```typescript
|
|
@@ -980,7 +979,7 @@ declare class Connection extends EventEmitter {
|
|
|
980
979
|
/**
|
|
981
980
|
* Write raw bytes to the transport (no framing applied).
|
|
982
981
|
*
|
|
983
|
-
*
|
|
982
|
+
* The server and the client wrap this with `encodeFrame()` to add MLLP framing.
|
|
984
983
|
* Returns `false` if the connection is CLOSED or DISCONNECTED (no bytes written).
|
|
985
984
|
*
|
|
986
985
|
* @returns `true` if bytes flushed immediately; `false` if buffered (backpressure) or not writable.
|
|
@@ -996,7 +995,7 @@ declare class Connection extends EventEmitter {
|
|
|
996
995
|
* Initiate graceful close of the connection.
|
|
997
996
|
*
|
|
998
997
|
* - If `CONNECTING` or `RECONNECTING`: cancels the pending attempt, transitions
|
|
999
|
-
* directly to `CLOSED`, and calls `transport.destroy()
|
|
998
|
+
* directly to `CLOSED`, and calls `transport.destroy()`.
|
|
1000
999
|
* - If `CONNECTED`: transitions to `DRAINING`, calls `beforeClose(drainTimeoutMs)`,
|
|
1001
1000
|
* then `DRAINING → DISCONNECTED` once the hook resolves, or `DRAINING → CLOSED`
|
|
1002
1001
|
* if the drain timeout elapses first.
|
|
@@ -1017,7 +1016,7 @@ declare class Connection extends EventEmitter {
|
|
|
1017
1016
|
* Race `beforeClose()` against the drain timeout.
|
|
1018
1017
|
*
|
|
1019
1018
|
* On normal completion: `DRAINING → DISCONNECTED` + `transport.close()`.
|
|
1020
|
-
* On timeout: `DRAINING → CLOSED` + `transport.destroy()
|
|
1019
|
+
* On timeout: `DRAINING → CLOSED` + `transport.destroy()`.
|
|
1021
1020
|
*/
|
|
1022
1021
|
private _drainWithTimeout;
|
|
1023
1022
|
/**
|
|
@@ -1036,7 +1035,7 @@ declare class Connection extends EventEmitter {
|
|
|
1036
1035
|
*/
|
|
1037
1036
|
destroy(reason?: Error): void;
|
|
1038
1037
|
/**
|
|
1039
|
-
* Return a JSON-serializable observability snapshot
|
|
1038
|
+
* Return a JSON-serializable observability snapshot.
|
|
1040
1039
|
*
|
|
1041
1040
|
* All timestamp fields are `Date | null` (not epoch milliseconds). `JSON.stringify()`
|
|
1042
1041
|
* serialises them to ISO 8601 strings with no information loss.
|
|
@@ -1133,9 +1132,9 @@ declare class Connection extends EventEmitter {
|
|
|
1133
1132
|
/**
|
|
1134
1133
|
* Connection lifecycle phase where the error occurred.
|
|
1135
1134
|
*
|
|
1136
|
-
* All 5 phases are defined
|
|
1137
|
-
* exercised
|
|
1138
|
-
* breaking type change later
|
|
1135
|
+
* All 5 phases are defined even though `'reconnect'` is only
|
|
1136
|
+
* exercised by the client. Locking the full union now prevents a
|
|
1137
|
+
* breaking type change later.
|
|
1139
1138
|
*/
|
|
1140
1139
|
type ConnectionErrorPhase = "connect" | "send" | "receive" | "close" | "reconnect";
|
|
1141
1140
|
/**
|
|
@@ -1146,18 +1145,18 @@ type ConnectionErrorPhase = "connect" | "send" | "receive" | "close" | "reconnec
|
|
|
1146
1145
|
* removing a member is a breaking change.
|
|
1147
1146
|
*
|
|
1148
1147
|
* - `'fifo-unsafe'`, A queued send was rejected during reconnect because
|
|
1149
|
-
* FIFO ordering cannot be safely resumed across sessions
|
|
1148
|
+
* FIFO ordering cannot be safely resumed across sessions.
|
|
1150
1149
|
* - `'in-flight-orphan'`, An in-flight send (already write-flushed,
|
|
1151
1150
|
* ACK timer started) was rejected during reconnect in FIFO mode because
|
|
1152
1151
|
* the at-most-once delivery contract cannot be preserved across a
|
|
1153
|
-
* socket drop (
|
|
1154
|
-
* - `'tls-verify'
|
|
1152
|
+
* socket drop (healthcare medication/orders semantics).
|
|
1153
|
+
* - `'tls-verify'`, The TLS handshake failed certificate
|
|
1155
1154
|
* verification (untrusted chain, expired/not-yet-valid cert, hostname
|
|
1156
1155
|
* mismatch, revocation, …). See {@link isTlsVerificationErrorCode} for the
|
|
1157
1156
|
* exact underlying error codes. Classified **permanent** by
|
|
1158
1157
|
* `isTransientConnectionError`, never auto-reconnect-looped into a
|
|
1159
1158
|
* misconfigured or MITM'd endpoint.
|
|
1160
|
-
* - `'tls-handshake'
|
|
1159
|
+
* - `'tls-handshake'`, A TLS-**protocol**-shaped handshake
|
|
1161
1160
|
* failure observed before `'secureConnect'`: `ERR_SSL_*` codes, `EPROTO`,
|
|
1162
1161
|
* or an OpenSSL alert-bearing error (protocol version mismatch, no shared
|
|
1163
1162
|
* cipher, a required mutual-TLS client certificate rejected by the
|
|
@@ -1352,8 +1351,8 @@ declare function resolveNackCode(err: unknown): NegativeAckCode;
|
|
|
1352
1351
|
* 3. **A batch or concatenated messages.** An `FHS`/`BHS`/`BTS`/`FTS` envelope (§2.10.3)
|
|
1353
1352
|
* or a second `MSH` in the same frame: a single MSA-2 can echo only ONE control ID, so
|
|
1354
1353
|
* a positive ACK naming the first silently drops the rest (see
|
|
1355
|
-
* {@link containsBatchOrExtraMessage}). Batch ACK is its own feature
|
|
1356
|
-
*
|
|
1354
|
+
* {@link containsBatchOrExtraMessage}). Batch ACK is its own feature; until it is
|
|
1355
|
+
* designed, a batch must stay a **loud non-positive** answer.
|
|
1357
1356
|
*
|
|
1358
1357
|
* This is a **refusal**, not a tolerance widening: it never makes an unreadable message
|
|
1359
1358
|
* readable, never re-bases on a located `MSH`, never parses a batch. It only recognizes
|
|
@@ -1477,7 +1476,7 @@ declare function buildRawAck(payload: Buffer, code: AckCode): Buffer;
|
|
|
1477
1476
|
*/
|
|
1478
1477
|
|
|
1479
1478
|
/**
|
|
1480
|
-
* Metadata attached to each decoded MLLP message
|
|
1479
|
+
* Metadata attached to each decoded MLLP message.
|
|
1481
1480
|
*
|
|
1482
1481
|
* All fields are `readonly`, the object is `Object.freeze()`'d before emission.
|
|
1483
1482
|
*
|
|
@@ -1541,10 +1540,10 @@ interface NackEvent {
|
|
|
1541
1540
|
readonly reason: NackReason;
|
|
1542
1541
|
}
|
|
1543
1542
|
/**
|
|
1544
|
-
* Observability snapshot returned by `server.getStats()
|
|
1543
|
+
* Observability snapshot returned by `server.getStats()`.
|
|
1545
1544
|
*
|
|
1546
1545
|
* All fields are JSON-serializable. `connections` and `activeConnections` both
|
|
1547
|
-
* reflect the current live connection count
|
|
1546
|
+
* reflect the current live connection count.
|
|
1548
1547
|
*
|
|
1549
1548
|
* @example
|
|
1550
1549
|
* ```typescript
|
|
@@ -1560,7 +1559,7 @@ interface ServerStats {
|
|
|
1560
1559
|
/** Bound host, or `null` before listen(). */
|
|
1561
1560
|
readonly host: string | null;
|
|
1562
1561
|
/**
|
|
1563
|
-
* Current live connection count
|
|
1562
|
+
* Current live connection count.
|
|
1564
1563
|
* Same value as `activeConnections`.
|
|
1565
1564
|
*/
|
|
1566
1565
|
readonly connections: number;
|
|
@@ -1574,9 +1573,9 @@ interface ServerStats {
|
|
|
1574
1573
|
readonly acceptedTotal: number;
|
|
1575
1574
|
/** Total connections closed since listen() (monotonically increasing). */
|
|
1576
1575
|
readonly closedTotal: number;
|
|
1577
|
-
/** Whether this server is configured for TLS
|
|
1576
|
+
/** Whether this server is configured for TLS. Mirrors `ServerOptions.tls` being set. */
|
|
1578
1577
|
readonly tls: boolean;
|
|
1579
|
-
/** Total `'tlsClientError'` events (failed TLS handshakes, incl. rejected client certs) since listen()
|
|
1578
|
+
/** Total `'tlsClientError'` events (failed TLS handshakes, incl. rejected client certs) since listen(). */
|
|
1580
1579
|
readonly tlsClientErrorsTotal: number;
|
|
1581
1580
|
}
|
|
1582
1581
|
/**
|
|
@@ -1616,7 +1615,7 @@ interface ServerOptions {
|
|
|
1616
1615
|
*/
|
|
1617
1616
|
onMessage?: (payload: Buffer, meta: MessageMeta, conn: Connection) => void | Promise<void>;
|
|
1618
1617
|
/**
|
|
1619
|
-
* FrameReader tolerance options applied to every accepted connection
|
|
1618
|
+
* FrameReader tolerance options applied to every accepted connection.
|
|
1620
1619
|
* Merged with SERVER_DEFAULT_FRAMING, caller-supplied values override defaults.
|
|
1621
1620
|
* `onFrame` and `onWarning` are managed internally and must not be supplied here.
|
|
1622
1621
|
*/
|
|
@@ -1641,31 +1640,31 @@ interface ServerOptions {
|
|
|
1641
1640
|
* - **`fn`**, `fn(payload, meta, conn)` builds the ACK bytes the server sends; the
|
|
1642
1641
|
* caller fully owns MSA-1 (e.g. to emit enhanced-mode `CA`/`CE`/`CR`).
|
|
1643
1642
|
*
|
|
1644
|
-
* The `'message'` event always fires BEFORE the ACK is sent
|
|
1643
|
+
* The `'message'` event always fires BEFORE the ACK is sent. Do NOT call
|
|
1645
1644
|
* `conn.send()` in `onMessage` when `autoAck` is set, this results in two ACKs.
|
|
1646
1645
|
*/
|
|
1647
1646
|
autoAck?: "AA" | ((payload: Buffer, meta: MessageMeta, conn: Connection) => Buffer | Promise<Buffer>);
|
|
1648
1647
|
/**
|
|
1649
|
-
* TCP keepalive probe interval in ms
|
|
1648
|
+
* TCP keepalive probe interval in ms.
|
|
1650
1649
|
* Calls `socket.setKeepAlive(true, ms)` on each accepted socket.
|
|
1651
1650
|
* Uses OS TCP stack to detect dead peers (half-open, network partitions).
|
|
1652
1651
|
* Distinct from `deadPeerTimeoutMs` (application-level idle close).
|
|
1653
1652
|
*/
|
|
1654
1653
|
keepaliveIntervalMs?: number;
|
|
1655
1654
|
/**
|
|
1656
|
-
* Application-level idle close timeout in ms
|
|
1655
|
+
* Application-level idle close timeout in ms.
|
|
1657
1656
|
* If no HL7 messages are received on a connection for this interval, the connection
|
|
1658
1657
|
* is destroyed via `conn.destroy(new Error('idle timeout'))`.
|
|
1659
1658
|
* Resets on every `'message'` event. Distinct from `keepaliveIntervalMs` (OS TCP probe).
|
|
1660
1659
|
*/
|
|
1661
1660
|
deadPeerTimeoutMs?: number;
|
|
1662
1661
|
/**
|
|
1663
|
-
* Graceful drain timeout passed to `conn.close()` during `server.close()
|
|
1662
|
+
* Graceful drain timeout passed to `conn.close()` during `server.close()`.
|
|
1664
1663
|
* Default: 30 000 ms.
|
|
1665
1664
|
*/
|
|
1666
1665
|
drainTimeoutMs?: number;
|
|
1667
1666
|
/**
|
|
1668
|
-
* Enable TLS (MLLPS) for this server
|
|
1667
|
+
* Enable TLS (MLLPS) for this server. When set, the server binds a
|
|
1669
1668
|
* `tls.Server` instead of a plain `net.Server`, consumes `'secureConnection'`
|
|
1670
1669
|
* (post-handshake sockets) instead of `'connection'`, and surfaces failed
|
|
1671
1670
|
* handshakes via the `'tlsClientError'` event rather than crashing.
|
|
@@ -1676,8 +1675,8 @@ interface ServerOptions {
|
|
|
1676
1675
|
*/
|
|
1677
1676
|
tls?: ServerTlsOptions;
|
|
1678
1677
|
/**
|
|
1679
|
-
* Opt-in required to bind a wildcard host, a bind-safety guardrail
|
|
1680
|
-
*
|
|
1678
|
+
* Opt-in required to bind a wildcard host, a bind-safety guardrail.
|
|
1679
|
+
* Without this flag, `listen()` rejects a wildcard host in
|
|
1681
1680
|
* two tiers: **literal spellings** (`'0.0.0.0'`, `'::'`, `''`, `'::0'`,
|
|
1682
1681
|
* `'0:0:0:0:0:0:0:0'`, `'::ffff:0.0.0.0'`, …) reject **pre-bind** (fast
|
|
1683
1682
|
* path, nothing is ever bound); **resolver-only shorthands** (`'0'`,
|
|
@@ -1694,7 +1693,7 @@ interface ServerOptions {
|
|
|
1694
1693
|
allowWildcardBind?: boolean;
|
|
1695
1694
|
}
|
|
1696
1695
|
/**
|
|
1697
|
-
* Options for `createStarterServer()`, the "three lines of code" factory
|
|
1696
|
+
* Options for `createStarterServer()`, the "three lines of code" factory.
|
|
1698
1697
|
*
|
|
1699
1698
|
* Extends `ServerOptions` with `port`, `host`, and `handleSignals`. Defaults:
|
|
1700
1699
|
* `autoAck: 'AA'`, `drainTimeoutMs: 30_000`, `Symbol.asyncDispose` wired.
|
|
@@ -1705,7 +1704,9 @@ interface ServerOptions {
|
|
|
1705
1704
|
*
|
|
1706
1705
|
* const server = await createStarterServer({
|
|
1707
1706
|
* port: 2575,
|
|
1708
|
-
* onMessage: (
|
|
1707
|
+
* onMessage: async (payload) => {
|
|
1708
|
+
* await db.commit(payload); // the durable-commit step; a throw answers a negative ACK
|
|
1709
|
+
* },
|
|
1709
1710
|
* });
|
|
1710
1711
|
* // server is listening, auto-ACK enabled, Symbol.asyncDispose wired
|
|
1711
1712
|
* await using _ = server; // closes on scope exit
|
|
@@ -1715,14 +1716,14 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1715
1716
|
/** Port to listen on. */
|
|
1716
1717
|
port: number;
|
|
1717
1718
|
/**
|
|
1718
|
-
* Host to bind to. Default `'127.0.0.1'` (
|
|
1719
|
+
* Host to bind to. Default `'127.0.0.1'` (bind-safety hardening,
|
|
1719
1720
|
* was `'0.0.0.0'`; binding all interfaces now requires
|
|
1720
1721
|
* `ServerOptions.allowWildcardBind: true`).
|
|
1721
1722
|
*/
|
|
1722
1723
|
host?: string;
|
|
1723
1724
|
/**
|
|
1724
1725
|
* Register `process.once('SIGTERM')` and `process.once('SIGINT')` handlers that
|
|
1725
|
-
* call `server.close()` then `process.exit(0)
|
|
1726
|
+
* call `server.close()` then `process.exit(0)`. Default: `false`.
|
|
1726
1727
|
*
|
|
1727
1728
|
* Handlers are automatically removed when `server.close()` is called, so
|
|
1728
1729
|
* `process.listenerCount('SIGTERM') === 0` after close() completes, preventing
|
|
@@ -1731,7 +1732,7 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1731
1732
|
handleSignals?: boolean;
|
|
1732
1733
|
}
|
|
1733
1734
|
/**
|
|
1734
|
-
* MLLP TCP server, wraps `net.Server` without extending it
|
|
1735
|
+
* MLLP TCP server, wraps `net.Server` without extending it.
|
|
1735
1736
|
*
|
|
1736
1737
|
* Each accepted socket is wrapped in a `NetTransport`, connected to a `Connection`
|
|
1737
1738
|
* with server-level framing options, and added to `_connections`. Messages are
|
|
@@ -1756,10 +1757,10 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1756
1757
|
* during the bind window changes the `listen()` rejection from the bind error to the typed
|
|
1757
1758
|
* close-during-listen `MllpConnectionError`, match on the `'error'` event payload, not the
|
|
1758
1759
|
* rejection, if you do that.
|
|
1759
|
-
*
|
|
1760
|
+
* TLS (MLLPS) adds two more: `'tlsClientError'` (a failed TLS handshake, the
|
|
1760
1761
|
* server logs it and keeps serving other connections) and `'securityWarning'` (loud,
|
|
1761
1762
|
* one-time notice when a wildcard host is bound via `allowWildcardBind: true`).
|
|
1762
|
-
* All event payloads are `Object.freeze()`'d before emission
|
|
1763
|
+
* All event payloads are `Object.freeze()`'d before emission.
|
|
1763
1764
|
*
|
|
1764
1765
|
* @example
|
|
1765
1766
|
* ```typescript
|
|
@@ -1781,10 +1782,10 @@ declare class MllpServer extends EventEmitter {
|
|
|
1781
1782
|
/** `true` when `ServerOptions.tls` is set, `_netServer` is actually a `tls.Server`. */
|
|
1782
1783
|
private readonly _isTls;
|
|
1783
1784
|
private _listening;
|
|
1784
|
-
/**
|
|
1785
|
+
/** Single-flight guard: `true` while a `listen()` call is settling. */
|
|
1785
1786
|
private _listenInFlight;
|
|
1786
1787
|
/**
|
|
1787
|
-
*
|
|
1788
|
+
* Settle hook for an in-flight `listen()`. Non-null only while a
|
|
1788
1789
|
* `listen()` is in flight; invoked by `close()` so a close racing startup
|
|
1789
1790
|
* rejects the pending `listen()` (typed) instead of leaving it hung with
|
|
1790
1791
|
* the single-flight guard stuck. Cleared on every settle path.
|
|
@@ -1794,7 +1795,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1794
1795
|
private _host;
|
|
1795
1796
|
private _acceptedTotal;
|
|
1796
1797
|
private _closedTotal;
|
|
1797
|
-
/**
|
|
1798
|
+
/** Count of `'tlsClientError'` events since listen(). */
|
|
1798
1799
|
private _tlsClientErrorsTotal;
|
|
1799
1800
|
/**
|
|
1800
1801
|
* Construct an MLLP server. Created idle; call `listen()` (or use
|
|
@@ -1825,7 +1826,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1825
1826
|
*
|
|
1826
1827
|
* @param port - TCP port to bind. Use `0` to let the OS assign an ephemeral port.
|
|
1827
1828
|
* @param hostOrOpts - Host string or object with `host` and optional `signal`.
|
|
1828
|
-
* Default host: `'127.0.0.1'` (
|
|
1829
|
+
* Default host: `'127.0.0.1'` (bind-safety hardening). Wildcard hosts
|
|
1829
1830
|
* are rejected unless `ServerOptions.allowWildcardBind: true`, literal
|
|
1830
1831
|
* spellings pre-bind, resolver-only shorthands post-bind via the
|
|
1831
1832
|
* OS-normalized bound address.
|
|
@@ -1844,7 +1845,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1844
1845
|
/**
|
|
1845
1846
|
* Stop accepting new connections and gracefully close all active connections.
|
|
1846
1847
|
*
|
|
1847
|
-
* Sequence
|
|
1848
|
+
* Sequence:
|
|
1848
1849
|
* 1. `net.Server.close()`, stops accepting new connections immediately
|
|
1849
1850
|
* 2. If `_connections` is empty: resolves immediately (no drain needed)
|
|
1850
1851
|
* 3. Calls `_drainAll(drainTimeoutMs)`, Promise.all + side-effect setTimeout
|
|
@@ -1896,10 +1897,9 @@ declare class MllpServer extends EventEmitter {
|
|
|
1896
1897
|
*/
|
|
1897
1898
|
[Symbol.asyncDispose](): Promise<void>;
|
|
1898
1899
|
/**
|
|
1899
|
-
* Return a JSON-serializable observability snapshot
|
|
1900
|
+
* Return a JSON-serializable observability snapshot.
|
|
1900
1901
|
*
|
|
1901
1902
|
* `totalBytesIn` and `totalBytesOut` aggregate from live connections at call time.
|
|
1902
|
-
* Plan 04 adds full aggregation; at Plan 01 scope they return 0.
|
|
1903
1903
|
*
|
|
1904
1904
|
* @example
|
|
1905
1905
|
* ```typescript
|
|
@@ -1931,8 +1931,8 @@ declare class MllpServer extends EventEmitter {
|
|
|
1931
1931
|
private _emitErrorIfListened;
|
|
1932
1932
|
private _onSocketAccepted;
|
|
1933
1933
|
/**
|
|
1934
|
-
* Extract a minimal, content-free peer-certificate summary (
|
|
1935
|
-
*
|
|
1934
|
+
* Extract a minimal, content-free peer-certificate summary (ATNA ITI-19
|
|
1935
|
+
* mutual authentication), CN strings, expiry, and the verification
|
|
1936
1936
|
* outcome (`authorized`) only; never the full certificate object. Returns
|
|
1937
1937
|
* `null` when no peer certificate was presented (e.g. `clientAuth: 'WANT'`
|
|
1938
1938
|
* with no client cert).
|
|
@@ -1983,20 +1983,20 @@ declare class MllpServer extends EventEmitter {
|
|
|
1983
1983
|
* Run `onMessage` for its side effects only (custom-ACK and manual modes), guarding
|
|
1984
1984
|
* against a synchronous throw or a rejected promise. In these modes the handler does
|
|
1985
1985
|
* **not** gate the ACK, so a failure is re-emitted as a PHI-safe `'error'` on the
|
|
1986
|
-
* connection (
|
|
1986
|
+
* connection (a handler error never crashes the server) rather than escaping as
|
|
1987
1987
|
* an unhandled rejection. The error text is never placed on the wire.
|
|
1988
1988
|
*/
|
|
1989
1989
|
private _observeMessage;
|
|
1990
1990
|
/**
|
|
1991
1991
|
* The custom-builder path for `autoAck: fn`. Awaits the builder and dispatches its
|
|
1992
1992
|
* Buffer as the ACK; the caller owns MSA-1. A builder error is re-emitted as `'error'`
|
|
1993
|
-
* on the connection, the server never crashes
|
|
1993
|
+
* on the connection, the server never crashes.
|
|
1994
1994
|
*/
|
|
1995
1995
|
private _sendCustomAck;
|
|
1996
1996
|
/**
|
|
1997
1997
|
* Frame an ACK payload and write it to the connection. `Connection.send()` returns
|
|
1998
1998
|
* `false` under backpressure (socket write buffer full); that drops the ACK and emits
|
|
1999
|
-
* a `MllpConnectionError({ phase: 'send' })` on the connection
|
|
1999
|
+
* a `MllpConnectionError({ phase: 'send' })` on the connection, the peer will
|
|
2000
2000
|
* time out waiting and may retry. The server never crashes.
|
|
2001
2001
|
*/
|
|
2002
2002
|
private _dispatchAck;
|
|
@@ -2025,15 +2025,15 @@ declare function createServer(opts?: ServerOptions): MllpServer;
|
|
|
2025
2025
|
* Provides the "three lines of code" north-star experience with sensible defaults:
|
|
2026
2026
|
* `autoAck: 'AA'`, `drainTimeoutMs: 30_000`, `Symbol.asyncDispose` wired.
|
|
2027
2027
|
*
|
|
2028
|
-
* NOTE: Stub at Plan 01 scope, Plan 04 fills in the full implementation.
|
|
2029
|
-
*
|
|
2030
2028
|
* @example
|
|
2031
2029
|
* ```typescript
|
|
2032
2030
|
* import { createStarterServer } from '@cosyte/mllp';
|
|
2033
2031
|
*
|
|
2034
2032
|
* const server = await createStarterServer({
|
|
2035
2033
|
* port: 2575,
|
|
2036
|
-
* onMessage: (payload) =>
|
|
2034
|
+
* onMessage: async (payload) => {
|
|
2035
|
+
* await db.commit(payload); // the durable-commit step; a throw answers a negative ACK
|
|
2036
|
+
* },
|
|
2037
2037
|
* });
|
|
2038
2038
|
* ```
|
|
2039
2039
|
*/
|
|
@@ -2047,11 +2047,8 @@ declare function createStarterServer(opts: StarterServerOptions): Promise<MllpSe
|
|
|
2047
2047
|
* events with frozen payloads. Supports `AbortSignal` cancellation on every
|
|
2048
2048
|
* awaitable and `Symbol.asyncDispose` for `await using` ergonomics.
|
|
2049
2049
|
*
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
2052
|
-
* The `correlateByControlId` option (MSH-10 → MSA-2 ACK matching) lights up
|
|
2053
|
-
* out-of-order ACK handling. Subsequent plans add auto-reconnect (Plan 04),
|
|
2054
|
-
* backpressure (Plan 05), and `createStarterClient` + `getStats()` (PLAN-06).
|
|
2050
|
+
* The `correlateByControlId` option (MSH-10 → MSA-2 ACK matching) enables
|
|
2051
|
+
* out-of-order ACK handling.
|
|
2055
2052
|
*
|
|
2056
2053
|
* @example
|
|
2057
2054
|
* ```typescript
|
|
@@ -2059,7 +2056,7 @@ declare function createStarterServer(opts: StarterServerOptions): Promise<MllpSe
|
|
|
2059
2056
|
*
|
|
2060
2057
|
* const client = createClient({ host: 'localhost', port: 2575 });
|
|
2061
2058
|
* await client.connect();
|
|
2062
|
-
*
|
|
2059
|
+
* const ack = await client.send(payloadBuffer);
|
|
2063
2060
|
* await client.close();
|
|
2064
2061
|
* ```
|
|
2065
2062
|
*
|
|
@@ -2067,11 +2064,9 @@ declare function createStarterServer(opts: StarterServerOptions): Promise<MllpSe
|
|
|
2067
2064
|
*/
|
|
2068
2065
|
|
|
2069
2066
|
/**
|
|
2070
|
-
* Context passed to a custom `retryStrategy` hook on each reconnect attempt
|
|
2071
|
-
* (CLIENT-12, D-15).
|
|
2067
|
+
* Context passed to a custom `retryStrategy` hook on each reconnect attempt.
|
|
2072
2068
|
*
|
|
2073
|
-
* Frozen via `Object.freeze` before invocation, handlers cannot mutate
|
|
2074
|
-
* (T-05-04-04 mitigation).
|
|
2069
|
+
* Frozen via `Object.freeze` before invocation, handlers cannot mutate.
|
|
2075
2070
|
*
|
|
2076
2071
|
* @example
|
|
2077
2072
|
* ```typescript
|
|
@@ -2093,22 +2088,22 @@ interface RetryContext {
|
|
|
2093
2088
|
readonly totalElapsedMs: number;
|
|
2094
2089
|
/** Ms since the last successful ACK. `Infinity` if no success seen. */
|
|
2095
2090
|
readonly sinceLastSuccessMs: number;
|
|
2096
|
-
/**
|
|
2091
|
+
/** How the failure that triggered this reconnect attempt was classified. */
|
|
2097
2092
|
readonly classifiedAs: "transient" | "permanent";
|
|
2098
2093
|
/**
|
|
2099
2094
|
* The same `AbortSignal` passed into `connect()`. If no signal was
|
|
2100
2095
|
* supplied, the module-level `NEVER_ABORTING_SIGNAL` sentinel is provided
|
|
2101
|
-
* so handlers always have a real `AbortSignal` to inspect.
|
|
2096
|
+
* so handlers always have a real `AbortSignal` to inspect.
|
|
2102
2097
|
*/
|
|
2103
2098
|
readonly signal: AbortSignal;
|
|
2104
2099
|
}
|
|
2105
2100
|
/**
|
|
2106
|
-
* Custom reconnect-backoff hook
|
|
2107
|
-
* reconnection
|
|
2101
|
+
* Custom reconnect-backoff hook. Return `null` to halt
|
|
2102
|
+
* reconnection, the FSM transitions to `CLOSED`.
|
|
2108
2103
|
*/
|
|
2109
2104
|
type RetryStrategy = (ctx: RetryContext) => number | null;
|
|
2110
2105
|
/**
|
|
2111
|
-
* Combined count + byte-based queue cap. Stricter-of-two wins
|
|
2106
|
+
* Combined count + byte-based queue cap. Stricter-of-two wins.
|
|
2112
2107
|
*
|
|
2113
2108
|
* - `number`, count cap only (default 64).
|
|
2114
2109
|
* - `{ bytes }`, byte cap only.
|
|
@@ -2129,8 +2124,6 @@ type HighWaterMark = number | {
|
|
|
2129
2124
|
/**
|
|
2130
2125
|
* Options for {@link createClient} and the {@link MllpClient} constructor.
|
|
2131
2126
|
*
|
|
2132
|
-
* Phase 5 plans extend this incrementally, new fields are additive and optional.
|
|
2133
|
-
*
|
|
2134
2127
|
* @example
|
|
2135
2128
|
* ```typescript
|
|
2136
2129
|
* const opts: ClientOptions = { host: 'localhost', port: 2575, drainTimeoutMs: 10_000 };
|
|
@@ -2146,54 +2139,54 @@ interface ClientOptions {
|
|
|
2146
2139
|
/** Drain timeout for {@link MllpClient.close} (default: `30_000` ms). */
|
|
2147
2140
|
readonly drainTimeoutMs?: number;
|
|
2148
2141
|
/**
|
|
2149
|
-
* Per-message ACK timeout in milliseconds
|
|
2142
|
+
* Per-message ACK timeout in milliseconds. The clock starts at
|
|
2150
2143
|
* the underlying `write()` flush callback, NOT at the `send()` call,
|
|
2151
2144
|
* pre-flush queue time is not charged to the peer. Default: `30_000`.
|
|
2152
2145
|
*/
|
|
2153
2146
|
readonly ackTimeoutMs?: number;
|
|
2154
2147
|
/**
|
|
2155
|
-
* If `true`, ACKs are matched against outgoing sends by MSH-10 → MSA-2
|
|
2156
|
-
*
|
|
2148
|
+
* If `true`, ACKs are matched against outgoing sends by MSH-10 → MSA-2.
|
|
2149
|
+
* Default `false` (FIFO mode).
|
|
2157
2150
|
*
|
|
2158
2151
|
* Out-of-order ACKs from the peer are supported in this mode. MSH-10 is
|
|
2159
2152
|
* extracted from the outbound payload before send; MSA-2 is extracted from
|
|
2160
2153
|
* the inbound ACK payload. An ACK whose MSA-2 matches no pending send
|
|
2161
2154
|
* (and is not in the late-ACK graveyard) emits a frozen
|
|
2162
|
-
* `MllpFramingError('MLLP_ACK_UNMATCHED_CONTROL_ID')` to the `'error'` event
|
|
2163
|
-
*
|
|
2164
|
-
* `MLLP_ACK_AFTER_TIMEOUT` warning
|
|
2155
|
+
* `MllpFramingError('MLLP_ACK_UNMATCHED_CONTROL_ID')` to the `'error'` event.
|
|
2156
|
+
* A late ACK whose MSA-2 matches a graveyard entry emits a
|
|
2157
|
+
* `MLLP_ACK_AFTER_TIMEOUT` warning and is dropped.
|
|
2165
2158
|
*
|
|
2166
2159
|
* @default false
|
|
2167
2160
|
*/
|
|
2168
2161
|
readonly correlateByControlId?: boolean;
|
|
2169
2162
|
/**
|
|
2170
|
-
* Auto-reconnect on transient disconnect
|
|
2163
|
+
* Auto-reconnect on transient disconnect. Default `false`.
|
|
2171
2164
|
*
|
|
2172
2165
|
* When `true`, dropped connections caused by transient errors (per
|
|
2173
2166
|
* {@link isTransientConnectionError}) trigger the FSM cycle
|
|
2174
2167
|
* `CONNECTED → DISCONNECTED → RECONNECTING → CONNECTING → CONNECTED`
|
|
2175
|
-
* with exponential backoff
|
|
2168
|
+
* with exponential backoff unless overridden by
|
|
2176
2169
|
* {@link ClientOptions.retryStrategy}. Permanent errors halt and
|
|
2177
|
-
* transition directly to `CLOSED
|
|
2170
|
+
* transition directly to `CLOSED`.
|
|
2178
2171
|
*/
|
|
2179
2172
|
readonly autoReconnect?: boolean;
|
|
2180
2173
|
/**
|
|
2181
|
-
* Custom reconnect-backoff hook
|
|
2182
|
-
* reconnection
|
|
2183
|
-
* to the exponential strategy
|
|
2174
|
+
* Custom reconnect-backoff hook. Return `null` to halt
|
|
2175
|
+
* reconnection. Receives a frozen {@link RetryContext}. Defaults
|
|
2176
|
+
* to the exponential strategy.
|
|
2184
2177
|
*/
|
|
2185
2178
|
readonly retryStrategy?: RetryStrategy;
|
|
2186
|
-
/** First delay (ms) on auto-reconnect; default 100.
|
|
2179
|
+
/** First delay (ms) on auto-reconnect; default 100. */
|
|
2187
2180
|
readonly initialDelayMs?: number;
|
|
2188
|
-
/** Maximum backoff cap (ms); default 30_000.
|
|
2181
|
+
/** Maximum backoff cap (ms); default 30_000. */
|
|
2189
2182
|
readonly maxDelayMs?: number;
|
|
2190
|
-
/** Backoff multiplier; default 2.
|
|
2183
|
+
/** Backoff multiplier; default 2. */
|
|
2191
2184
|
readonly multiplier?: number;
|
|
2192
|
-
/** Jitter fraction, e.g. 0.2 = ±20%; default 0.2.
|
|
2185
|
+
/** Jitter fraction, e.g. 0.2 = ±20%; default 0.2. */
|
|
2193
2186
|
readonly jitter?: number;
|
|
2194
2187
|
/**
|
|
2195
|
-
* Application-level high-water mark on the in-flight + queued send set
|
|
2196
|
-
*
|
|
2188
|
+
* Application-level high-water mark on the in-flight + queued send set.
|
|
2189
|
+
* `number` configures a count cap (default 64);
|
|
2197
2190
|
* `{ bytes }` configures a byte cap; `{ count, bytes }` configures
|
|
2198
2191
|
* both, with the stricter-of-two trigger winning.
|
|
2199
2192
|
*
|
|
@@ -2204,17 +2197,17 @@ interface ClientOptions {
|
|
|
2204
2197
|
*/
|
|
2205
2198
|
readonly highWaterMark?: HighWaterMark;
|
|
2206
2199
|
/**
|
|
2207
|
-
* Behavior when the high-water mark is exceeded
|
|
2200
|
+
* Behavior when the high-water mark is exceeded.
|
|
2208
2201
|
*
|
|
2209
2202
|
* - `'reject'` (default), `send()` rejects with `MllpBackpressureError`.
|
|
2210
2203
|
* - `'wait'`, `send()` awaits the `'drain'` event OR the per-message
|
|
2211
|
-
* `ackTimeoutMs` OR `signal` abort, whichever fires first
|
|
2204
|
+
* `ackTimeoutMs` OR `signal` abort, whichever fires first.
|
|
2212
2205
|
*
|
|
2213
2206
|
* @default 'reject'
|
|
2214
2207
|
*/
|
|
2215
2208
|
readonly onBackpressure?: "reject" | "wait";
|
|
2216
2209
|
/**
|
|
2217
|
-
* Strict serialization send → await-ACK → send
|
|
2210
|
+
* Strict serialization send → await-ACK → send.
|
|
2218
2211
|
*
|
|
2219
2212
|
* - `true` (default), concurrent in-flight sends up to
|
|
2220
2213
|
* {@link ClientOptions.highWaterMark}.
|
|
@@ -2229,23 +2222,23 @@ interface ClientOptions {
|
|
|
2229
2222
|
* TCP keepalive interval (ms). Sets `socket.setKeepAlive(true, ms)` on
|
|
2230
2223
|
* the underlying `net.Socket` BEFORE wrapping in `NetTransport`. OS-level
|
|
2231
2224
|
* half-open detection (network partitions, NAT-table eviction). Independent
|
|
2232
|
-
* of {@link ClientOptions.deadPeerTimeoutMs}
|
|
2225
|
+
* of {@link ClientOptions.deadPeerTimeoutMs}.
|
|
2233
2226
|
*
|
|
2234
2227
|
* @default undefined (off)
|
|
2235
2228
|
*/
|
|
2236
2229
|
readonly keepaliveIntervalMs?: number;
|
|
2237
2230
|
/**
|
|
2238
|
-
* Application-idle timeout (ms) keyed on last inbound bytes / ACK / warning
|
|
2239
|
-
*
|
|
2231
|
+
* Application-idle timeout (ms) keyed on last inbound bytes / ACK / warning.
|
|
2232
|
+
* On trip, calls `connection.destroy(new Error('dead
|
|
2240
2233
|
* peer timeout'))` which surfaces as `MllpConnectionError({ phase: 'receive' })`.
|
|
2241
|
-
* Trip honors {@link ClientOptions.autoReconnect}
|
|
2242
|
-
* {@link ClientOptions.keepaliveIntervalMs}
|
|
2234
|
+
* Trip honors {@link ClientOptions.autoReconnect}. Independent of
|
|
2235
|
+
* {@link ClientOptions.keepaliveIntervalMs}.
|
|
2243
2236
|
*
|
|
2244
2237
|
* @default undefined (off)
|
|
2245
2238
|
*/
|
|
2246
2239
|
readonly deadPeerTimeoutMs?: number;
|
|
2247
2240
|
/**
|
|
2248
|
-
* Enable TLS (MLLPS) for this connection
|
|
2241
|
+
* Enable TLS (MLLPS) for this connection. `true` enables TLS with
|
|
2249
2242
|
* all defaults, including certificate verification **on**. Pass a
|
|
2250
2243
|
* {@link TlsOptions} object to customize (`ca`/`cert`/`key`, minimum
|
|
2251
2244
|
* version, ciphers, `allowUnverified`, …).
|
|
@@ -2257,16 +2250,16 @@ interface ClientOptions {
|
|
|
2257
2250
|
readonly tls?: TlsOptions | true;
|
|
2258
2251
|
}
|
|
2259
2252
|
/**
|
|
2260
|
-
* Observability snapshot returned by {@link MllpClient.getStats}
|
|
2253
|
+
* Observability snapshot returned by {@link MllpClient.getStats}.
|
|
2261
2254
|
*
|
|
2262
|
-
* All fields are JSON-serializable
|
|
2255
|
+
* All fields are JSON-serializable, no Buffers, no class instances,
|
|
2263
2256
|
* no Maps, no circular references. `lastConnectedAt` and `lastAckAt` are
|
|
2264
2257
|
* **epoch milliseconds** (numbers), NOT `Date` instances, log-pipeline
|
|
2265
|
-
* friendly
|
|
2258
|
+
* friendly.
|
|
2266
2259
|
*
|
|
2267
2260
|
* `warningsByCode` keys are constrained to the public {@link WarningCode}
|
|
2268
2261
|
* union, adding/removing a code is a breaking change (CLAUDE.md
|
|
2269
|
-
* stable-codes guardrail enforced at the type boundary
|
|
2262
|
+
* stable-codes guardrail enforced at the type boundary).
|
|
2270
2263
|
*
|
|
2271
2264
|
* @example
|
|
2272
2265
|
* ```typescript
|
|
@@ -2288,7 +2281,7 @@ interface ClientStats {
|
|
|
2288
2281
|
readonly inFlight: number;
|
|
2289
2282
|
/**
|
|
2290
2283
|
* Aggregated warning counts. Keys are constrained to the public
|
|
2291
|
-
* {@link WarningCode} union
|
|
2284
|
+
* {@link WarningCode} union. Connection-level warnings + Correlator
|
|
2292
2285
|
* `MLLP_ACK_*` warnings are merged.
|
|
2293
2286
|
*/
|
|
2294
2287
|
readonly warningsByCode: Partial<Record<WarningCode, number>>;
|
|
@@ -2302,28 +2295,28 @@ interface ClientStats {
|
|
|
2302
2295
|
readonly ackedTotal: number;
|
|
2303
2296
|
/** Total ACK timeouts since construction. */
|
|
2304
2297
|
readonly timedOutTotal: number;
|
|
2305
|
-
/** Total reconnect attempts since construction
|
|
2298
|
+
/** Total reconnect attempts since construction. */
|
|
2306
2299
|
readonly reconnectAttempts: number;
|
|
2307
2300
|
/** Epoch ms of the last `CONNECTED` transition. `null` until first connect. */
|
|
2308
2301
|
readonly lastConnectedAt: number | null;
|
|
2309
2302
|
/** Epoch ms of the most recent successful ACK. `null` until first ACK. */
|
|
2310
2303
|
readonly lastAckAt: number | null;
|
|
2311
|
-
/** Whether this client is configured for TLS
|
|
2304
|
+
/** Whether this client is configured for TLS. Mirrors `ClientOptions.tls` being set. */
|
|
2312
2305
|
readonly tls: boolean;
|
|
2313
2306
|
}
|
|
2314
2307
|
/**
|
|
2315
|
-
* MLLP client, composes a single
|
|
2308
|
+
* MLLP client, composes a single {@link Connection} over a {@link NetTransport}
|
|
2316
2309
|
* (production) or any other `Transport` (testing via {@link InMemoryTransport}).
|
|
2317
2310
|
*
|
|
2318
|
-
* Public events, every payload `Object.freeze`'d before emission
|
|
2311
|
+
* Public events, every payload `Object.freeze`'d before emission:
|
|
2319
2312
|
* - `'stateChange'`, `{ from, to, reason? }` from the underlying Connection FSM
|
|
2320
2313
|
* - `'connect'`, `{ connectionId }` once the FSM enters `CONNECTED`
|
|
2321
2314
|
* - `'disconnect'`, `{ connectionId }` once the FSM enters `DISCONNECTED`
|
|
2322
|
-
* - `'reconnecting'`, `{ connectionId, attempt?, delayMs? }`
|
|
2315
|
+
* - `'reconnecting'`, `{ connectionId, attempt?, delayMs? }`
|
|
2323
2316
|
* - `'close'`, `{ connectionId }` once the FSM enters terminal `CLOSED`
|
|
2324
2317
|
* - `'message'`, `{ payload, connectionId, byteOffset, warnings }` for every inbound frame
|
|
2325
2318
|
* - `'warning'`, `MllpWarning` enriched with `connectionId` from the Connection layer
|
|
2326
|
-
* - `'securityWarning'`, `SecurityWarning
|
|
2319
|
+
* - `'securityWarning'`, `SecurityWarning`. Emitted on every successful
|
|
2327
2320
|
* `secureConnect` (initial + every reconnect) when `tls.allowUnverified` is `true`
|
|
2328
2321
|
* with code `MLLP_TLS_VERIFY_DISABLED`. Also mirrored to `process.emitWarning`.
|
|
2329
2322
|
* - `'error'`, re-emitted from Connection. Guarded by `listenerCount('error') > 0` so
|
|
@@ -2335,7 +2328,7 @@ interface ClientStats {
|
|
|
2335
2328
|
* client.on('stateChange', ({ from, to }) => logger.info({ from, to }));
|
|
2336
2329
|
* client.on('message', ({ payload }) => logger.info({ bytes: payload.length }));
|
|
2337
2330
|
* await client.connect();
|
|
2338
|
-
*
|
|
2331
|
+
* const ack = await client.send(payloadBuffer);
|
|
2339
2332
|
* await client.close();
|
|
2340
2333
|
* ```
|
|
2341
2334
|
*/
|
|
@@ -2348,15 +2341,15 @@ declare class MllpClient extends EventEmitter {
|
|
|
2348
2341
|
* is attached, `state` mirrors `_connection.state`.
|
|
2349
2342
|
*/
|
|
2350
2343
|
private _state;
|
|
2351
|
-
/** Per-message ACK timeout in ms
|
|
2344
|
+
/** Per-message ACK timeout in ms. Resolved at construction. */
|
|
2352
2345
|
private readonly _ackTimeoutMs;
|
|
2353
|
-
/** controlId-mode flag
|
|
2346
|
+
/** controlId-mode flag. `false` → FIFO. */
|
|
2354
2347
|
private readonly _correlateByControlId;
|
|
2355
|
-
/** Unified ACK correlator
|
|
2348
|
+
/** Unified ACK correlator. Built during `_attachConnection`. */
|
|
2356
2349
|
private _correlator;
|
|
2357
2350
|
/**
|
|
2358
2351
|
* Periodic ACK-timeout sweep timer. Drives `_correlator.expireDue()` because
|
|
2359
|
-
* the Correlator is timer-free
|
|
2352
|
+
* the Correlator is timer-free. Cleared on close/destroy.
|
|
2360
2353
|
*/
|
|
2361
2354
|
private _ackSweepTimer;
|
|
2362
2355
|
private readonly _autoReconnect;
|
|
@@ -2368,12 +2361,12 @@ declare class MllpClient extends EventEmitter {
|
|
|
2368
2361
|
/** 0-indexed attempt counter for the current reconnect cycle. */
|
|
2369
2362
|
private _attempt;
|
|
2370
2363
|
/**
|
|
2371
|
-
*
|
|
2364
|
+
* Total reconnect attempts since construction, surfaced as
|
|
2372
2365
|
* `getStats().reconnectAttempts`. Incremented at the entry of every
|
|
2373
2366
|
* `_handleDisconnect` invocation that proceeds to schedule a backoff.
|
|
2374
2367
|
*/
|
|
2375
2368
|
private _reconnectAttempts;
|
|
2376
|
-
/** Epoch ms of the last successful ACK. Drives
|
|
2369
|
+
/** Epoch ms of the last successful ACK. Drives backoff-reset. */
|
|
2377
2370
|
private _lastSuccessAt;
|
|
2378
2371
|
/** Epoch ms when the current reconnect cycle began. `null` outside a cycle. */
|
|
2379
2372
|
private _reconnectCycleStartedAt;
|
|
@@ -2389,12 +2382,12 @@ declare class MllpClient extends EventEmitter {
|
|
|
2389
2382
|
private readonly _onBackpressure;
|
|
2390
2383
|
/** Pipeline flag; default `true` (parallel up to highWaterMark). */
|
|
2391
2384
|
private readonly _pipeline;
|
|
2392
|
-
/** Dead-peer idle timer
|
|
2385
|
+
/** Dead-peer idle timer. `null` when not armed. */
|
|
2393
2386
|
private _deadPeerTimer;
|
|
2394
2387
|
/** True once the self-'ack' listener that resets the dead-peer timer
|
|
2395
2388
|
* has been attached. Guards against duplicate listeners on reconnect. */
|
|
2396
2389
|
private _ackResetWired;
|
|
2397
|
-
/** Most recently bound `connect()` signal. Reread on every RetryContext build
|
|
2390
|
+
/** Most recently bound `connect()` signal. Reread on every RetryContext build. */
|
|
2398
2391
|
private _connectSignal;
|
|
2399
2392
|
/** Set when close()/destroy()/abort fires; reconnect handler short-circuits. */
|
|
2400
2393
|
private _userClosed;
|
|
@@ -2422,7 +2415,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2422
2415
|
/**
|
|
2423
2416
|
* Aggregated Correlator-emitted warning counts (MLLP_ACK_*). Connection-level
|
|
2424
2417
|
* warnings are read directly from `_connection.getStats().warningsByCode`
|
|
2425
|
-
* at observation time and merged into the snapshot
|
|
2418
|
+
* at observation time and merged into the snapshot.
|
|
2426
2419
|
*/
|
|
2427
2420
|
private _aggregatedWarningsByCode;
|
|
2428
2421
|
/**
|
|
@@ -2439,8 +2432,8 @@ declare class MllpClient extends EventEmitter {
|
|
|
2439
2432
|
*/
|
|
2440
2433
|
get state(): ConnectionState;
|
|
2441
2434
|
/**
|
|
2442
|
-
* Open a TCP (or TLS
|
|
2443
|
-
* and attach a
|
|
2435
|
+
* Open a TCP (or TLS) connection to the configured `host:port`
|
|
2436
|
+
* and attach a {@link Connection} to it. Resolves once the FSM
|
|
2444
2437
|
* enters `CONNECTED`, for TLS, on `'secureConnect'` (handshake complete,
|
|
2445
2438
|
* including certificate verification when it is on).
|
|
2446
2439
|
*
|
|
@@ -2483,7 +2476,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2483
2476
|
* Build the raw socket + `Transport` pair for a connect / reconnect attempt.
|
|
2484
2477
|
*
|
|
2485
2478
|
* Plaintext (`ClientOptions.tls` unset): a `net.Socket` wrapped in
|
|
2486
|
-
* `NetTransport`. TLS
|
|
2479
|
+
* `NetTransport`. TLS: a `tls.TLSSocket` wrapped in
|
|
2487
2480
|
* `TlsTransport`, verification defaults **on**
|
|
2488
2481
|
* (`rejectUnauthorized: !allowUnverified`), floor `minVersion: 'TLSv1.2'`
|
|
2489
2482
|
* (the IHE ATNA ITI-19 BCP195 floor), `servername` defaulting to
|
|
@@ -2492,7 +2485,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2492
2485
|
private _createSocketAndTransport;
|
|
2493
2486
|
/**
|
|
2494
2487
|
* Wrap a connect-phase socket error as `MllpConnectionError`, classifying
|
|
2495
|
-
* TLS failures
|
|
2488
|
+
* TLS failures into the additive `connectionCause`:
|
|
2496
2489
|
*
|
|
2497
2490
|
* - `'tls-verify'`, certificate-verification failures
|
|
2498
2491
|
* ({@link isTlsVerificationErrorCode}).
|
|
@@ -2516,7 +2509,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2516
2509
|
*/
|
|
2517
2510
|
private _emitContained;
|
|
2518
2511
|
/**
|
|
2519
|
-
* Emit the per-connection insecure-TLS warning
|
|
2512
|
+
* Emit the per-connection insecure-TLS warning when
|
|
2520
2513
|
* `tls.allowUnverified === true`, fires on EVERY successful
|
|
2521
2514
|
* `secureConnect`, initial connect and every reconnect. Emits both a frozen
|
|
2522
2515
|
* `'securityWarning'` event and `process.emitWarning`. No-op for plaintext
|
|
@@ -2525,11 +2518,11 @@ declare class MllpClient extends EventEmitter {
|
|
|
2525
2518
|
private _emitInsecureWarningIfNeeded;
|
|
2526
2519
|
/**
|
|
2527
2520
|
* Wire a Connection's events through to this MllpClient. Every re-emitted
|
|
2528
|
-
* payload is `Object.freeze`'d before emission
|
|
2521
|
+
* payload is `Object.freeze`'d before emission, even though the
|
|
2529
2522
|
* Connection layer already freezes, defense-in-depth, harmless on
|
|
2530
2523
|
* already-frozen objects.
|
|
2531
2524
|
*
|
|
2532
|
-
* Builds the unified `Correlator`
|
|
2525
|
+
* Builds the unified `Correlator` bound to this Connection and
|
|
2533
2526
|
* arms the periodic ACK-timeout sweep. The Correlator is teardown-aware:
|
|
2534
2527
|
* `close()` / `destroy()` clear the sweep timer and reject pending sends.
|
|
2535
2528
|
*
|
|
@@ -2537,37 +2530,36 @@ declare class MllpClient extends EventEmitter {
|
|
|
2537
2530
|
*/
|
|
2538
2531
|
private _attachConnection;
|
|
2539
2532
|
/**
|
|
2540
|
-
* Arm (or re-arm) the dead-peer idle timer
|
|
2533
|
+
* Arm (or re-arm) the dead-peer idle timer.
|
|
2541
2534
|
* No-op when `deadPeerTimeoutMs` is unset.
|
|
2542
2535
|
*/
|
|
2543
2536
|
private _armDeadPeerTimer;
|
|
2544
2537
|
/**
|
|
2545
|
-
* Clear the dead-peer idle timer
|
|
2546
|
-
* every transition out of CONNECTED).
|
|
2538
|
+
* Clear the dead-peer idle timer on every transition out of CONNECTED.
|
|
2547
2539
|
*/
|
|
2548
2540
|
private _clearDeadPeerTimer;
|
|
2549
2541
|
/**
|
|
2550
|
-
* Disconnect handler,
|
|
2542
|
+
* Disconnect handler, reconnect FSM core.
|
|
2551
2543
|
*
|
|
2552
2544
|
* Implements:
|
|
2553
|
-
* -
|
|
2545
|
+
* - Hybrid in-flight handling: controlId mode preserves
|
|
2554
2546
|
* pending sends for resend; FIFO mode rejects in-flight with
|
|
2555
2547
|
* `connectionCause: 'in-flight-orphan'` and queued with `'fifo-unsafe'`.
|
|
2556
|
-
* -
|
|
2548
|
+
* - Classifier-first: permanent errors
|
|
2557
2549
|
* transition straight to CLOSED without invoking `retryStrategy`.
|
|
2558
|
-
* -
|
|
2550
|
+
* - Backoff-reset on recent success: first disconnect after a
|
|
2559
2551
|
* successful ACK on the prior session resets `_attempt` to 0.
|
|
2560
|
-
* -
|
|
2561
|
-
* -
|
|
2562
|
-
* -
|
|
2552
|
+
* - `_reconnectAttempts` counter increment.
|
|
2553
|
+
* - Frozen RetryContext; a `null` return from the strategy halts reconnection.
|
|
2554
|
+
* - Default exponential strategy.
|
|
2563
2555
|
*
|
|
2564
|
-
* Invoked from the SINGLE `_onStateChange` hook, no parallel listener
|
|
2565
|
-
*
|
|
2556
|
+
* Invoked from the SINGLE `_onStateChange` hook, no parallel listener.
|
|
2557
|
+
* Idempotent across same-cycle re-entry: cycle-start flag
|
|
2566
2558
|
* coordinates first-disconnect vs subsequent-within-cycle behavior.
|
|
2567
2559
|
*/
|
|
2568
2560
|
private _handleDisconnect;
|
|
2569
2561
|
/**
|
|
2570
|
-
* Default retry strategy
|
|
2562
|
+
* Default retry strategy: `min(maxDelay, initialDelay * multiplier^attempt)`
|
|
2571
2563
|
* with ±jitter applied.
|
|
2572
2564
|
*/
|
|
2573
2565
|
private _defaultRetryStrategy;
|
|
@@ -2603,7 +2595,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2603
2595
|
arm: () => void;
|
|
2604
2596
|
}): void;
|
|
2605
2597
|
/**
|
|
2606
|
-
* Test seam, capture or rebind the connect-signal mid-flight
|
|
2598
|
+
* Test seam, capture or rebind the connect-signal mid-flight.
|
|
2607
2599
|
*
|
|
2608
2600
|
* @internal
|
|
2609
2601
|
*/
|
|
@@ -2613,28 +2605,25 @@ declare class MllpClient extends EventEmitter {
|
|
|
2613
2605
|
*
|
|
2614
2606
|
* - FIFO mode: passes `null` controlId so `matchAck` returns the head of
|
|
2615
2607
|
* the live store.
|
|
2616
|
-
* - controlId mode: extracts MSA-2
|
|
2617
|
-
*
|
|
2608
|
+
* - controlId mode: extracts MSA-2 and passes it to `matchAck` for keyed
|
|
2609
|
+
* lookup.
|
|
2618
2610
|
*
|
|
2619
2611
|
* Called from the SINGLE `'message'` listener registered in `_attachConnection`.
|
|
2620
|
-
* No parallel listener is registered
|
|
2621
|
-
* anchor (B-04).
|
|
2612
|
+
* No parallel listener is registered.
|
|
2622
2613
|
*/
|
|
2623
2614
|
private _onAckPayload;
|
|
2624
2615
|
/**
|
|
2625
2616
|
* Single source-of-truth for a successfully matched ACK (live-store hit).
|
|
2626
2617
|
*
|
|
2627
|
-
*
|
|
2628
|
-
*
|
|
2629
|
-
* PLAN-06 extends at HOOK_EXTENSION_POINT: ack-matched to bump _ackedTotal and
|
|
2630
|
-
* set _lastAckAt.
|
|
2618
|
+
* Emits the frozen 'ack' event and resolves the pending send. Updates
|
|
2619
|
+
* `_lastSuccessAt`, `_ackedTotal` and `_lastAckAt`.
|
|
2631
2620
|
*
|
|
2632
2621
|
* Called from _onAckPayload when matchAck() returns a non-null PendingAck.
|
|
2633
2622
|
*/
|
|
2634
2623
|
private _onAckMatched;
|
|
2635
2624
|
/**
|
|
2636
2625
|
* Emit a frozen `'drain'` event when the queue depth and bytes fall below
|
|
2637
|
-
* both configured caps
|
|
2626
|
+
* both configured caps. Called from `_onAckMatched`
|
|
2638
2627
|
* (every successful ACK) and from the Correlator's `onTimeout` callback
|
|
2639
2628
|
* (every expired send), both code paths free a live-store slot.
|
|
2640
2629
|
*/
|
|
@@ -2642,28 +2631,24 @@ declare class MllpClient extends EventEmitter {
|
|
|
2642
2631
|
/**
|
|
2643
2632
|
* Single source-of-truth for Connection FSM transitions.
|
|
2644
2633
|
*
|
|
2645
|
-
*
|
|
2646
|
-
*
|
|
2647
|
-
*
|
|
2648
|
-
* CONNECTED → DISCONNECTED|RECONNECTING and trigger _handleDisconnect.
|
|
2649
|
-
* Plan 05 extends at HOOK_EXTENSION_POINT: state-change to clear/arm
|
|
2650
|
-
* dead-peer timer on transitions out of / into CONNECTED.
|
|
2634
|
+
* Re-emits a frozen 'stateChange' event, detects
|
|
2635
|
+
* CONNECTED → DISCONNECTED|RECONNECTING to trigger `_handleDisconnect`, and
|
|
2636
|
+
* clears or arms the dead-peer timer on transitions out of / into CONNECTED.
|
|
2651
2637
|
*
|
|
2652
2638
|
* Called from the SINGLE 'stateChange' listener registered in _attachConnection.
|
|
2653
2639
|
*/
|
|
2654
2640
|
private _onStateChange;
|
|
2655
2641
|
/**
|
|
2656
|
-
* Send an MLLP-framed payload and await the inbound ACK
|
|
2642
|
+
* Send an MLLP-framed payload and await the inbound ACK.
|
|
2657
2643
|
*
|
|
2658
2644
|
* Resolves with the ACK Buffer (framing stripped). Rejects with:
|
|
2659
|
-
* - `DOMException('Aborted', 'AbortError')` if `signal` aborts before ACK
|
|
2660
|
-
*
|
|
2661
|
-
* - `MllpTimeoutError` if no ACK arrives within `ackTimeoutMs` (ERR-02).
|
|
2645
|
+
* - `DOMException('Aborted', 'AbortError')` if `signal` aborts before the ACK.
|
|
2646
|
+
* - `MllpTimeoutError` if no ACK arrives within `ackTimeoutMs`.
|
|
2662
2647
|
* The clock starts at the underlying `write()` flush callback, NOT at
|
|
2663
|
-
* the `send()` call
|
|
2648
|
+
* the `send()` call.
|
|
2664
2649
|
* - `MllpConnectionError({ phase: 'send' })` if the client is not connected.
|
|
2665
2650
|
*
|
|
2666
|
-
* Emits a frozen `'ack'` event on every successful match
|
|
2651
|
+
* Emits a frozen `'ack'` event on every successful match.
|
|
2667
2652
|
*
|
|
2668
2653
|
* @example
|
|
2669
2654
|
* ```typescript
|
|
@@ -2672,19 +2657,19 @@ declare class MllpClient extends EventEmitter {
|
|
|
2672
2657
|
* ```
|
|
2673
2658
|
*
|
|
2674
2659
|
* @param payload Raw bytes; MLLP framing is added internally via `encodeFrame`.
|
|
2675
|
-
* @param opts.signal AbortSignal, aborting cancels the ACK wait
|
|
2660
|
+
* @param opts.signal AbortSignal, aborting cancels the ACK wait.
|
|
2676
2661
|
*/
|
|
2677
2662
|
send(payload: Buffer, opts?: {
|
|
2678
2663
|
signal?: AbortSignal;
|
|
2679
2664
|
ackTimeoutMs?: number;
|
|
2680
2665
|
}): Promise<Buffer>;
|
|
2681
2666
|
/**
|
|
2682
|
-
* 'wait'-mode backpressure handler
|
|
2667
|
+
* 'wait'-mode backpressure handler.
|
|
2683
2668
|
*
|
|
2684
2669
|
* Awaits one of three terminating signals, in order:
|
|
2685
2670
|
* - `'drain'` event → re-enter `send()` (the gate will now pass).
|
|
2686
2671
|
* - `ackTimeoutMs` elapses → reject with `MllpTimeoutError`.
|
|
2687
|
-
* - Caller's `signal` aborts → reject with `AbortError
|
|
2672
|
+
* - Caller's `signal` aborts → reject with `AbortError`. Cleanup
|
|
2688
2673
|
* removes the drain listener AND the abort listener AND clears the
|
|
2689
2674
|
* timer to prevent leaks.
|
|
2690
2675
|
*/
|
|
@@ -2732,7 +2717,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2732
2717
|
*/
|
|
2733
2718
|
destroy(reason?: Error): void;
|
|
2734
2719
|
/**
|
|
2735
|
-
* Returns a JSON-serializable observability snapshot
|
|
2720
|
+
* Returns a JSON-serializable observability snapshot.
|
|
2736
2721
|
*
|
|
2737
2722
|
* All fields are plain values, no Buffers, no class instances, no Maps,
|
|
2738
2723
|
* no circular refs. Safe to `JSON.stringify` directly.
|
|
@@ -2769,15 +2754,15 @@ declare class MllpClient extends EventEmitter {
|
|
|
2769
2754
|
*
|
|
2770
2755
|
* const client = createClient({ host: 'localhost', port: 2575 });
|
|
2771
2756
|
* await client.connect();
|
|
2772
|
-
*
|
|
2757
|
+
* const ack = await client.send(payloadBuffer);
|
|
2773
2758
|
* await client.close();
|
|
2774
2759
|
* ```
|
|
2775
2760
|
*/
|
|
2776
2761
|
declare function createClient(opts: ClientOptions): MllpClient;
|
|
2777
2762
|
/**
|
|
2778
|
-
* Options for {@link createStarterClient}
|
|
2763
|
+
* Options for {@link createStarterClient}.
|
|
2779
2764
|
*
|
|
2780
|
-
* The starter applies opinionated
|
|
2765
|
+
* The starter applies opinionated defaults on top of `ClientOptions`,
|
|
2781
2766
|
* so every override here is optional except `host` + `port`. The
|
|
2782
2767
|
* starter-specific addition is `handleSignals` (mirrors `createStarterServer`).
|
|
2783
2768
|
*
|
|
@@ -2814,31 +2799,30 @@ interface StarterClientOptions {
|
|
|
2814
2799
|
readonly onBackpressure?: "reject" | "wait";
|
|
2815
2800
|
/** Override default `true` (auto-reconnect on transient errors). */
|
|
2816
2801
|
readonly autoReconnect?: boolean;
|
|
2817
|
-
/** Custom reconnect-backoff hook
|
|
2802
|
+
/** Custom reconnect-backoff hook. */
|
|
2818
2803
|
readonly retryStrategy?: RetryStrategy;
|
|
2819
2804
|
/** Drain timeout for `close()` (default `30_000`). */
|
|
2820
2805
|
readonly drainTimeoutMs?: number;
|
|
2821
2806
|
/** FrameReader options (passthrough). */
|
|
2822
2807
|
readonly framing?: ClientOptions["framing"];
|
|
2823
|
-
/** TCP keepalive interval ms
|
|
2808
|
+
/** TCP keepalive interval ms. */
|
|
2824
2809
|
readonly keepaliveIntervalMs?: number;
|
|
2825
|
-
/** Application-idle dead-peer timeout ms
|
|
2810
|
+
/** Application-idle dead-peer timeout ms. */
|
|
2826
2811
|
readonly deadPeerTimeoutMs?: number;
|
|
2827
|
-
/** Enable TLS (MLLPS) for this connection
|
|
2812
|
+
/** Enable TLS (MLLPS) for this connection. Passthrough to `ClientOptions.tls`. */
|
|
2828
2813
|
readonly tls?: TlsOptions | true;
|
|
2829
2814
|
/**
|
|
2830
2815
|
* Register process SIGTERM/SIGINT handlers that close the client. Default
|
|
2831
|
-
* `false
|
|
2832
|
-
* and exit the process. Handlers self-deregister on `'close'
|
|
2816
|
+
* `false`. When `true`, SIGTERM/SIGINT both call `client.close()`
|
|
2817
|
+
* and exit the process. Handlers self-deregister on `'close'`.
|
|
2833
2818
|
*/
|
|
2834
2819
|
readonly handleSignals?: boolean;
|
|
2835
2820
|
}
|
|
2836
2821
|
/**
|
|
2837
|
-
* Three-line MLLP client with batteries-included defaults
|
|
2838
|
-
*
|
|
2839
|
-
* has been awaited.
|
|
2822
|
+
* Three-line MLLP client with batteries-included defaults. The returned
|
|
2823
|
+
* client is already CONNECTED, `connect()` has been awaited.
|
|
2840
2824
|
*
|
|
2841
|
-
*
|
|
2825
|
+
* Defaults:
|
|
2842
2826
|
* - `autoReconnect: true`
|
|
2843
2827
|
* - `ackTimeoutMs: 30_000`
|
|
2844
2828
|
* - `correlateByControlId: false` (FIFO mode, simplest mental model)
|
|
@@ -2865,9 +2849,9 @@ declare function createStarterClient(opts: StarterClientOptions): Promise<MllpCl
|
|
|
2865
2849
|
* MLLP Client typed errors and classifiers.
|
|
2866
2850
|
*
|
|
2867
2851
|
* Exports:
|
|
2868
|
-
* - `MllpTimeoutError
|
|
2869
|
-
* - `MllpBackpressureError`, high-water mark exceeded
|
|
2870
|
-
* - `isTransientConnectionError
|
|
2852
|
+
* - `MllpTimeoutError`, ACK timeout
|
|
2853
|
+
* - `MllpBackpressureError`, high-water mark exceeded
|
|
2854
|
+
* - `isTransientConnectionError`, transient/permanent classifier
|
|
2871
2855
|
*
|
|
2872
2856
|
* Re-exported from `src/client/index.ts` and `src/index.ts`.
|
|
2873
2857
|
*
|
|
@@ -2875,11 +2859,10 @@ declare function createStarterClient(opts: StarterClientOptions): Promise<MllpCl
|
|
|
2875
2859
|
*/
|
|
2876
2860
|
/**
|
|
2877
2861
|
* Thrown (or rejects the `send()` promise) when an ACK does not arrive within
|
|
2878
|
-
* the configured `ackTimeoutMs
|
|
2862
|
+
* the configured `ackTimeoutMs`.
|
|
2879
2863
|
*
|
|
2880
2864
|
* The timeout clock starts at the underlying `write()` flush callback, NOT
|
|
2881
|
-
* at the `send()` call, pre-flush queue time is not charged to the peer
|
|
2882
|
-
* (CLIENT-04).
|
|
2865
|
+
* at the `send()` call, pre-flush queue time is not charged to the peer.
|
|
2883
2866
|
*
|
|
2884
2867
|
* @example
|
|
2885
2868
|
* ```typescript
|
|
@@ -2917,7 +2900,7 @@ declare class MllpTimeoutError extends Error {
|
|
|
2917
2900
|
* failure (as opposed to some other TLS handshake failure), untrusted chain,
|
|
2918
2901
|
* expired/not-yet-valid certificate, hostname mismatch, revocation, etc.
|
|
2919
2902
|
*
|
|
2920
|
-
* Used by `MllpClient`
|
|
2903
|
+
* Used by `MllpClient` to classify a TLS connect failure's
|
|
2921
2904
|
* `connectionCause` as `'tls-verify'` (this set) vs `'tls-handshake'`
|
|
2922
2905
|
* (everything else observed before `'secureConnect'`). Exported so callers
|
|
2923
2906
|
* can apply the same classification to their own error handling.
|
|
@@ -2933,7 +2916,7 @@ declare class MllpTimeoutError extends Error {
|
|
|
2933
2916
|
declare function isTlsVerificationErrorCode(code: string): boolean;
|
|
2934
2917
|
/**
|
|
2935
2918
|
* Detects **TLS-protocol-shaped** errors, failures of the TLS protocol
|
|
2936
|
-
* itself, as opposed to plain TCP-level network failures
|
|
2919
|
+
* itself, as opposed to plain TCP-level network failures.
|
|
2937
2920
|
*
|
|
2938
2921
|
* Apply this only to errors raised on a **TLS** connection; `MllpClient`
|
|
2939
2922
|
* does exactly that (the predicate is consulted only when `ClientOptions.tls`
|
|
@@ -2986,27 +2969,27 @@ declare function isTlsProtocolError(err: unknown): boolean;
|
|
|
2986
2969
|
* Classifies a connection error as transient (eligible for auto-reconnect)
|
|
2987
2970
|
* or permanent (halts auto-reconnect, transitions to CLOSED).
|
|
2988
2971
|
*
|
|
2989
|
-
* Used internally by `MllpClient` BEFORE invoking `retryStrategy`
|
|
2990
|
-
* see `RetryContext.classifiedAs`). Re-exported so callers can implement
|
|
2972
|
+
* Used internally by `MllpClient` BEFORE invoking `retryStrategy`
|
|
2973
|
+
* (see `RetryContext.classifiedAs`). Re-exported so callers can implement
|
|
2991
2974
|
* their own retry policies.
|
|
2992
2975
|
*
|
|
2993
|
-
* Classification table
|
|
2976
|
+
* Classification table:
|
|
2994
2977
|
* - `ENOTFOUND`, `EACCES` → **permanent** (`false`)
|
|
2995
2978
|
* - `ECONNREFUSED`, `ECONNRESET`, `ETIMEDOUT`, `EHOSTUNREACH`,
|
|
2996
2979
|
* `ENETUNREACH`, `EPIPE` → **transient** (`true`)
|
|
2997
2980
|
* - `CERT_*` and `UNABLE_TO_VERIFY_LEAF_SIGNATURE` /
|
|
2998
2981
|
* `DEPTH_ZERO_SELF_SIGNED_CERT` / `SELF_SIGNED_CERT_IN_CHAIN`
|
|
2999
2982
|
* (any {@link isTlsVerificationErrorCode} code) → **permanent** (`false`),
|
|
3000
|
-
* never auto-reconnect-loop into a misconfigured or MITM'd endpoint
|
|
2983
|
+
* never auto-reconnect-loop into a misconfigured or MITM'd endpoint.
|
|
3001
2984
|
* - `ERR_SSL_*` (Node TLS alert codes) → **permanent** (`false`), a TLS
|
|
3002
2985
|
* protocol failure such as a `clientAuth: 'MUST'` server rejecting the
|
|
3003
|
-
* client certificate recurs on every attempt
|
|
2986
|
+
* client certificate recurs on every attempt. On TLS-configured
|
|
3004
2987
|
* connections `MllpClient` additionally consults {@link isTlsProtocolError},
|
|
3005
2988
|
* which also catches `EPROTO`/alert-bearing OpenSSL errors that this
|
|
3006
2989
|
* generic classifier (which cannot know the connection was TLS) leaves
|
|
3007
2990
|
* transient.
|
|
3008
2991
|
* - `MLLP_*` (any {@link MllpFramingError} code, a fatal decoder throw, surfaced with
|
|
3009
|
-
* `connectionCause: 'framing-fatal'`) → **permanent** (`false`)
|
|
2992
|
+
* `connectionCause: 'framing-fatal'`) → **permanent** (`false`). The peer is not
|
|
3010
2993
|
* speaking MLLP, an HTTP probe, a health check, a wrong-port misconfiguration, or is emitting
|
|
3011
2994
|
* frames past `maxFrameSizeBytes`. Every reconnect meets the same bytes, so retrying is an
|
|
3012
2995
|
* unbounded storm against a peer that is already misconfigured. If a peer's quirk is *expected*,
|
|
@@ -3030,11 +3013,10 @@ declare function isTlsProtocolError(err: unknown): boolean;
|
|
|
3030
3013
|
declare function isTransientConnectionError(err: unknown): boolean;
|
|
3031
3014
|
/**
|
|
3032
3015
|
* Thrown (or rejects the `send()` promise) when the in-flight queue exceeds
|
|
3033
|
-
* the configured high-water mark and `onBackpressure: 'reject'` is set
|
|
3034
|
-
* (CLIENT-07, ERR-04).
|
|
3016
|
+
* the configured high-water mark and `onBackpressure: 'reject'` is set.
|
|
3035
3017
|
*
|
|
3036
3018
|
* `highWaterMark` accepts a count cap, a byte cap, or both, when both are
|
|
3037
|
-
* present, the stricter-of-two trigger wins
|
|
3019
|
+
* present, the stricter-of-two trigger wins.
|
|
3038
3020
|
*
|
|
3039
3021
|
* @example
|
|
3040
3022
|
* ```typescript
|
|
@@ -3057,7 +3039,7 @@ declare class MllpBackpressureError extends Error {
|
|
|
3057
3039
|
readonly queueDepth: number;
|
|
3058
3040
|
/** Total bytes of in-flight + queued frames at the moment of rejection. */
|
|
3059
3041
|
readonly queueBytes: number;
|
|
3060
|
-
/** The high-water-mark configuration that was triggered
|
|
3042
|
+
/** The high-water-mark configuration that was triggered. */
|
|
3061
3043
|
readonly highWaterMark: {
|
|
3062
3044
|
readonly count?: number;
|
|
3063
3045
|
readonly bytes?: number;
|