@cosyte/mllp 0.0.1 → 0.0.2
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 +269 -108
- package/README.md +23 -18
- package/TRADEMARKS.md +30 -0
- package/dist/ack-from-hl7/index.cjs +54 -14
- package/dist/ack-from-hl7/index.cjs.map +1 -1
- package/dist/ack-from-hl7/index.d.cts +113 -38
- package/dist/ack-from-hl7/index.d.ts +113 -38
- package/dist/ack-from-hl7/index.mjs +53 -14
- package/dist/ack-from-hl7/index.mjs.map +1 -1
- package/dist/{index-DjGQJtYE.d.cts → index-DAZ4BvJE.d.cts} +5 -5
- package/dist/{index-DjGQJtYE.d.ts → index-DAZ4BvJE.d.ts} +5 -5
- package/dist/index.cjs +90 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +239 -239
- package/dist/index.d.ts +239 -239
- package/dist/index.mjs +90 -90
- package/dist/index.mjs.map +1 -1
- package/dist/testing/index.cjs +3 -3
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +8 -8
- package/dist/testing/index.d.ts +8 -8
- package/dist/testing/index.mjs +3 -3
- package/dist/testing/index.mjs.map +1 -1
- package/package.json +15 -7
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as Transport } from './index-
|
|
1
|
+
import { T as Transport } from './index-DAZ4BvJE.cjs';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
3
|
import { Socket } from 'node:net';
|
|
4
4
|
import { TLSSocket } from 'node:tls';
|
|
@@ -20,7 +20,7 @@ import { TLSSocket } from 'node:tls';
|
|
|
20
20
|
/**
|
|
21
21
|
* Union of all stable MLLP warning codes.
|
|
22
22
|
*
|
|
23
|
-
* These codes are a **public API
|
|
23
|
+
* These codes are a **public API**, they appear in `onWarning` handlers, log pipelines,
|
|
24
24
|
* monitoring dashboards, and error messages. Renaming is a breaking change.
|
|
25
25
|
*
|
|
26
26
|
* @example
|
|
@@ -62,7 +62,7 @@ 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
|
|
65
|
+
* `connectionId` is always `undefined` here, Phase 3 enriches via
|
|
66
66
|
* `{ ...w, connectionId: this.connectionId }` then re-freezes (D-08).
|
|
67
67
|
*
|
|
68
68
|
* @example
|
|
@@ -94,15 +94,15 @@ declare function createWarning(code: WarningCode, byteOffset: number, message: s
|
|
|
94
94
|
/**
|
|
95
95
|
* Thrown for unrecoverable MLLP wire-format violations.
|
|
96
96
|
*
|
|
97
|
-
* - `code
|
|
98
|
-
* - `byteOffset
|
|
99
|
-
* - `snippet
|
|
97
|
+
* - `code`, stable `WarningCode` identifying the violation
|
|
98
|
+
* - `byteOffset`, absolute stream position where the violation was detected
|
|
99
|
+
* - `snippet`, the single framing-boundary byte that broke the structure, or empty when the
|
|
100
100
|
* anomaly is not a specific byte. **Never a run of payload content** (see the `snippet` PHI
|
|
101
101
|
* contract below); the constructor caps whatever it is given to 64 bytes as a backstop.
|
|
102
102
|
*
|
|
103
103
|
* @example
|
|
104
104
|
* ```typescript
|
|
105
|
-
* // Pass only the offending framing-boundary byte
|
|
105
|
+
* // Pass only the offending framing-boundary byte, never a slice of payload content.
|
|
106
106
|
* throw new MllpFramingError('MLLP_MISSING_LEADING_VT', byteOffset, Buffer.from([byte]));
|
|
107
107
|
* ```
|
|
108
108
|
*/
|
|
@@ -115,13 +115,13 @@ declare class MllpFramingError extends Error {
|
|
|
115
115
|
/**
|
|
116
116
|
* Up to 64 bytes copied from around the anomaly.
|
|
117
117
|
*
|
|
118
|
-
* This is a **copied** Buffer
|
|
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
121
|
* **PHI contract (MLLP-9):** the decoder only ever populates this with the single
|
|
122
122
|
* framing-boundary byte that violated the frame structure (whose hex value the
|
|
123
|
-
* `message` already discloses)
|
|
124
|
-
* whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE
|
|
123
|
+
* `message` already discloses), never a run of payload content bytes. For anomalies
|
|
124
|
+
* whose fault is not a specific byte (`MLLP_FRAME_TOO_LARGE`, the accumulated size),
|
|
125
125
|
* the snippet is **empty**: a payload slice on a public error field would leak a
|
|
126
126
|
* field-body slice of clinical content. Callers constructing this directly are
|
|
127
127
|
* responsible for the same discipline.
|
|
@@ -141,10 +141,10 @@ declare class MllpFramingError extends Error {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
|
-
* MLLP frame encoder
|
|
144
|
+
* MLLP frame encoder, canonical `VT + payload + FS + CR` wrapping.
|
|
145
145
|
*
|
|
146
146
|
* The encoder is a **strict emitter** (Postel's Law: conservative on write).
|
|
147
|
-
* There is no option to loosen the output format
|
|
147
|
+
* There is no option to loosen the output format, it always emits canonical framing.
|
|
148
148
|
* Tolerance for delimiter bytes in the payload is available via `allowDelimiterBytesInPayload`,
|
|
149
149
|
* which passes bytes through verbatim and emits an `MllpWarning` for each offending byte.
|
|
150
150
|
*
|
|
@@ -175,12 +175,12 @@ interface EncoderOptions {
|
|
|
175
175
|
* verbatim in the output frame instead of throwing `MllpFramingError`.
|
|
176
176
|
* An `MllpWarning` is emitted per offending byte if `onWarning` is provided.
|
|
177
177
|
*
|
|
178
|
-
* Default: `false` (strict
|
|
178
|
+
* Default: `false` (strict, throws on delimiter bytes).
|
|
179
179
|
*/
|
|
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
|
|
183
|
+
* Invocation is wrapped in try/catch, a throwing handler does not interrupt encoding (WARN-06).
|
|
184
184
|
*
|
|
185
185
|
* @example
|
|
186
186
|
* ```typescript
|
|
@@ -211,11 +211,11 @@ interface EncoderOptions {
|
|
|
211
211
|
* ```typescript
|
|
212
212
|
* import { encodeFrame } from '@cosyte/mllp';
|
|
213
213
|
*
|
|
214
|
-
* // Strict (default)
|
|
214
|
+
* // Strict (default), throws on delimiter bytes in payload
|
|
215
215
|
* const frame = encodeFrame(Buffer.from('MSH|^~\\&|SEND|FAC|RECV|FAC|...'));
|
|
216
216
|
* socket.write(frame);
|
|
217
217
|
*
|
|
218
|
-
* // Tolerant
|
|
218
|
+
* // Tolerant, passes delimiter bytes through with a warning
|
|
219
219
|
* const frame2 = encodeFrame(dirtyPayload, {
|
|
220
220
|
* allowDelimiterBytesInPayload: true,
|
|
221
221
|
* onWarning: (w) => logger.warn(w),
|
|
@@ -225,13 +225,13 @@ interface EncoderOptions {
|
|
|
225
225
|
declare function encodeFrame(payload: Buffer, opts?: EncoderOptions): Buffer;
|
|
226
226
|
|
|
227
227
|
/**
|
|
228
|
-
* MLLP frame decoder
|
|
228
|
+
* MLLP frame decoder, stateful 3-state FSM for chunked byte-stream parsing.
|
|
229
229
|
*
|
|
230
230
|
* Feed raw TCP chunks via `push(chunk)`. Complete MLLP frames fire synchronously
|
|
231
231
|
* via the `onFrame` callback during each `push()` call.
|
|
232
232
|
*
|
|
233
233
|
* The decoder is **strict by default**: any framing deviation throws `MllpFramingError`
|
|
234
|
-
* unless the matching tolerance option is explicitly enabled (Postel's Law
|
|
234
|
+
* unless the matching tolerance option is explicitly enabled (Postel's Law, liberal receiver
|
|
235
235
|
* means explicit tolerance, not silent acceptance).
|
|
236
236
|
*
|
|
237
237
|
* @example
|
|
@@ -252,7 +252,7 @@ declare function encodeFrame(payload: Buffer, opts?: EncoderOptions): Buffer;
|
|
|
252
252
|
/**
|
|
253
253
|
* Options for `FrameReader`.
|
|
254
254
|
*
|
|
255
|
-
* All tolerance opts default to `false
|
|
255
|
+
* All tolerance opts default to `false`, every framing deviation throws unless
|
|
256
256
|
* explicitly enabled. Server-level defaults (allowFsOnly, allowLfAfterFs, allowLeadingWhitespace)
|
|
257
257
|
* are applied by Phase 4 when constructing readers per SERVER-12.
|
|
258
258
|
*
|
|
@@ -309,7 +309,7 @@ interface FrameReaderOptions {
|
|
|
309
309
|
*
|
|
310
310
|
* @example
|
|
311
311
|
* ```typescript
|
|
312
|
-
* // Hardened enforcement
|
|
312
|
+
* // Hardened enforcement, no tolerance, all violations throw
|
|
313
313
|
* const reader = new FrameReader({ onFrame: fn, strict: true });
|
|
314
314
|
* ```
|
|
315
315
|
*/
|
|
@@ -320,9 +320,9 @@ interface FrameReaderOptions {
|
|
|
320
320
|
* Complete frames fire synchronously via `onFrame` callback during `push()`.
|
|
321
321
|
*
|
|
322
322
|
* The decoder operates as a 3-state FSM:
|
|
323
|
-
* - `SCANNING_FOR_VT
|
|
324
|
-
* - `READING_PAYLOAD
|
|
325
|
-
* - `EXPECTING_CR
|
|
323
|
+
* - `SCANNING_FOR_VT`, waiting for the 0x0B frame-start byte
|
|
324
|
+
* - `READING_PAYLOAD`, accumulating payload bytes until FS (0x1C)
|
|
325
|
+
* - `EXPECTING_CR`, received FS, waiting for CR (0x0D) to complete the frame
|
|
326
326
|
*
|
|
327
327
|
* @example
|
|
328
328
|
* ```typescript
|
|
@@ -337,7 +337,7 @@ declare class FrameReader {
|
|
|
337
337
|
private readonly _opts;
|
|
338
338
|
private readonly _maxFrameSize;
|
|
339
339
|
private _state;
|
|
340
|
-
/** Reusable accumulator buffer
|
|
340
|
+
/** Reusable accumulator buffer, grown by doubling as needed. */
|
|
341
341
|
private _accumulator;
|
|
342
342
|
/** Number of payload bytes written to accumulator in current frame. */
|
|
343
343
|
private _writePos;
|
|
@@ -349,7 +349,7 @@ declare class FrameReader {
|
|
|
349
349
|
private _wsCount;
|
|
350
350
|
/** Byte offset of the VT byte that started the current frame. */
|
|
351
351
|
private _frameStartOffset;
|
|
352
|
-
/** Per-frame warning accumulator
|
|
352
|
+
/** Per-frame warning accumulator, cleared after each _deliverFrame(). */
|
|
353
353
|
private _frameWarnings;
|
|
354
354
|
/**
|
|
355
355
|
* Construct a chunked MLLP frame reader.
|
|
@@ -396,7 +396,7 @@ declare class FrameReader {
|
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
/**
|
|
399
|
-
* NetTransport
|
|
399
|
+
* NetTransport, `Transport` implementation wrapping a `net.Socket`.
|
|
400
400
|
*
|
|
401
401
|
* This is the **only** place in the codebase that consumes `net.Socket`'s
|
|
402
402
|
* EventEmitter surface. All higher layers (Connection, Server, Client) work
|
|
@@ -455,13 +455,13 @@ declare class NetTransport implements Transport {
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
/**
|
|
458
|
-
* TlsTransport
|
|
458
|
+
* TlsTransport, `Transport` implementation wrapping a `tls.TLSSocket` (Phase 8).
|
|
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
|
|
462
462
|
* completes) rather than `'connect'` (fires once the raw TCP handshake
|
|
463
463
|
* completes). Consumers of `Transport` (Connection, Client, Server) never see
|
|
464
|
-
* the difference
|
|
464
|
+
* the difference, they only ever observe "the transport is ready."
|
|
465
465
|
*
|
|
466
466
|
* @example
|
|
467
467
|
* ```typescript
|
|
@@ -484,7 +484,7 @@ declare class NetTransport implements Transport {
|
|
|
484
484
|
* `'connect'` event that `NetTransport` uses.
|
|
485
485
|
*
|
|
486
486
|
* Each `onXxx(fn)` call replaces the prior handler and re-registers on the
|
|
487
|
-
* socket (`socket.removeAllListeners(event)` + `socket.on(event, fn)`)
|
|
487
|
+
* socket (`socket.removeAllListeners(event)` + `socket.on(event, fn)`),
|
|
488
488
|
* the same set-once semantics as `NetTransport`.
|
|
489
489
|
*
|
|
490
490
|
* @example
|
|
@@ -511,8 +511,8 @@ declare class TlsTransport implements Transport {
|
|
|
511
511
|
/** See {@link Transport.onData}. */
|
|
512
512
|
onData(fn: (chunk: Buffer) => void): void;
|
|
513
513
|
/**
|
|
514
|
-
* See {@link Transport.onConnect}. Armed on `'secureConnect'
|
|
515
|
-
* handshake-complete event
|
|
514
|
+
* See {@link Transport.onConnect}. Armed on `'secureConnect'`, the TLS
|
|
515
|
+
* handshake-complete event, not the raw TCP `'connect'` event.
|
|
516
516
|
*/
|
|
517
517
|
onConnect(fn: () => void): void;
|
|
518
518
|
/** See {@link Transport.onClose}. */
|
|
@@ -524,7 +524,7 @@ declare class TlsTransport implements Transport {
|
|
|
524
524
|
/**
|
|
525
525
|
* Security-warning codes and payload shape shared by the client and server (Phase 8).
|
|
526
526
|
*
|
|
527
|
-
* These codes are a **public API
|
|
527
|
+
* These codes are a **public API**, they appear in `'securityWarning'` event
|
|
528
528
|
* handlers, log pipelines, and monitoring dashboards. Renaming or removing a
|
|
529
529
|
* code is a breaking change (mirrors the `WarningCode` guardrail in
|
|
530
530
|
* `src/framing/registry.ts`).
|
|
@@ -540,8 +540,8 @@ declare class TlsTransport implements Transport {
|
|
|
540
540
|
* @packageDocumentation
|
|
541
541
|
*/
|
|
542
542
|
/**
|
|
543
|
-
* Emitted (client-side) on every successful `secureConnect
|
|
544
|
-
* AND every reconnect
|
|
543
|
+
* Emitted (client-side) on every successful `secureConnect`, initial connect
|
|
544
|
+
* AND every reconnect, when {@link TlsOptions.allowUnverified} is `true`.
|
|
545
545
|
* Certificate verification is disabled for the connection; this is the loud,
|
|
546
546
|
* per-connection reminder that the channel is not authenticated per IHE ATNA
|
|
547
547
|
* ITI-19 (https://profiles.ihe.net/ITI/TF/Volume2/ITI-19.html).
|
|
@@ -584,7 +584,7 @@ type SecurityWarningCode = typeof MLLP_TLS_VERIFY_DISABLED | typeof MLLP_BIND_AL
|
|
|
584
584
|
* {@link MllpClient} (`MLLP_TLS_VERIFY_DISABLED`) and {@link MllpServer}
|
|
585
585
|
* (`MLLP_BIND_ALL_INTERFACES`).
|
|
586
586
|
*
|
|
587
|
-
* Never carries payload bytes or PHI
|
|
587
|
+
* Never carries payload bytes or PHI, only routing metadata (host/port) and
|
|
588
588
|
* a fixed, static message string.
|
|
589
589
|
*
|
|
590
590
|
* @example
|
|
@@ -612,14 +612,14 @@ interface SecurityWarning {
|
|
|
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
|
|
615
|
-
* Floor using BCP195" option, ITI TF-2 §3.19.6.2.3
|
|
615
|
+
* Floor using BCP195" option, ITI TF-2 §3.19.6.2.3, TLS >= 1.2 required;
|
|
616
616
|
* mutual node authentication; certificate validation via chain-of-trust or
|
|
617
617
|
* direct comparison. ITI TF-2 §3.19.6.2.3 mandates four TLS 1.2 cipher
|
|
618
618
|
* suites: `TLS_DHE_RSA_WITH_AES_128_GCM_SHA256`,
|
|
619
619
|
* `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`, `TLS_DHE_RSA_WITH_AES_256_GCM_SHA384`,
|
|
620
620
|
* `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`. Node's default TLS 1.2 cipher list
|
|
621
621
|
* already includes both mandated ECDHE suites, so this package pins to Node
|
|
622
|
-
* defaults rather than bundling a cipher list
|
|
622
|
+
* defaults rather than bundling a cipher list, see `docs-content/tls.md`.
|
|
623
623
|
*
|
|
624
624
|
* @example
|
|
625
625
|
* ```typescript
|
|
@@ -631,7 +631,7 @@ interface SecurityWarning {
|
|
|
631
631
|
*/
|
|
632
632
|
/**
|
|
633
633
|
* A PEM-encoded credential (certificate, key, or CA), matching Node's
|
|
634
|
-
* `tls.connect`/`tls.createServer` input shape
|
|
634
|
+
* `tls.connect`/`tls.createServer` input shape, a single PEM string/Buffer,
|
|
635
635
|
* or an array of them (chain / multiple trust anchors).
|
|
636
636
|
*
|
|
637
637
|
* @example
|
|
@@ -644,7 +644,7 @@ type PemInput = string | Buffer | Array<string | Buffer>;
|
|
|
644
644
|
/**
|
|
645
645
|
* Client-side TLS options (Phase 8, `ClientOptions.tls`).
|
|
646
646
|
*
|
|
647
|
-
* Passing `true` for `ClientOptions.tls` is equivalent to `{}
|
|
647
|
+
* Passing `true` for `ClientOptions.tls` is equivalent to `{}`, TLS enabled
|
|
648
648
|
* with all defaults, including certificate verification **on**.
|
|
649
649
|
*
|
|
650
650
|
* @example
|
|
@@ -674,9 +674,9 @@ interface TlsOptions {
|
|
|
674
674
|
/**
|
|
675
675
|
* Minimum negotiated TLS protocol version.
|
|
676
676
|
*
|
|
677
|
-
* Default `'TLSv1.2'
|
|
677
|
+
* Default `'TLSv1.2'`, the IHE ATNA ITI-19 "TLS 1.2 Floor" (BCP195) floor
|
|
678
678
|
* (ITI TF-2 §3.19.6.2.3). `'TLSv1.0'`/`'TLSv1.1'` are intentionally **not**
|
|
679
|
-
* expressible by this type
|
|
679
|
+
* expressible by this type, the floor cannot be lowered through this API.
|
|
680
680
|
*
|
|
681
681
|
* @default 'TLSv1.2'
|
|
682
682
|
*/
|
|
@@ -695,7 +695,7 @@ interface TlsOptions {
|
|
|
695
695
|
/**
|
|
696
696
|
* Loud, explicit dev opt-out from certificate verification (maps to
|
|
697
697
|
* `tls.connect`'s `rejectUnauthorized: false`). There is deliberately no
|
|
698
|
-
* raw `rejectUnauthorized` surface on this type
|
|
698
|
+
* raw `rejectUnauthorized` surface on this type, this is the only door,
|
|
699
699
|
* and it is loud: every successful connection (initial + every reconnect)
|
|
700
700
|
* emits a `'securityWarning'` (`MLLP_TLS_VERIFY_DISABLED`) event and calls
|
|
701
701
|
* `process.emitWarning`.
|
|
@@ -712,11 +712,11 @@ interface TlsOptions {
|
|
|
712
712
|
* Mirrors the IHE ATNA "Authenticate Node" mutual-auth requirement
|
|
713
713
|
* (https://profiles.ihe.net/ITI/TF/Volume2/ITI-19.html):
|
|
714
714
|
*
|
|
715
|
-
* - `'NONE'
|
|
716
|
-
* - `'WANT'
|
|
715
|
+
* - `'NONE'`, no client certificate requested (default).
|
|
716
|
+
* - `'WANT'`, client certificate requested but NOT required; an untrusted or
|
|
717
717
|
* absent client certificate does not reject the connection. The peer
|
|
718
718
|
* certificate (if any) is surfaced on the `'connection'` event.
|
|
719
|
-
* - `'MUST'
|
|
719
|
+
* - `'MUST'`, client certificate required AND verified against
|
|
720
720
|
* {@link ServerTlsOptions.ca}; the ATNA mutual-authentication mode. A
|
|
721
721
|
* missing or untrusted client certificate rejects the handshake.
|
|
722
722
|
*
|
|
@@ -757,7 +757,7 @@ interface ServerTlsOptions {
|
|
|
757
757
|
*/
|
|
758
758
|
readonly clientAuth?: ClientAuth;
|
|
759
759
|
/**
|
|
760
|
-
* Minimum negotiated TLS protocol version
|
|
760
|
+
* Minimum negotiated TLS protocol version, the IHE ATNA ITI-19 "TLS 1.2
|
|
761
761
|
* Floor" (BCP195) floor (ITI TF-2 §3.19.6.2.3).
|
|
762
762
|
*
|
|
763
763
|
* @default 'TLSv1.2'
|
|
@@ -775,7 +775,7 @@ interface ServerTlsOptions {
|
|
|
775
775
|
}
|
|
776
776
|
|
|
777
777
|
/**
|
|
778
|
-
* Connection
|
|
778
|
+
* Connection, 6-state FSM over a Transport with lifecycle events, warning streams,
|
|
779
779
|
* and getStats() observability.
|
|
780
780
|
*
|
|
781
781
|
* @example
|
|
@@ -835,7 +835,7 @@ interface ReconnectingEvent {
|
|
|
835
835
|
readonly delayMs?: number;
|
|
836
836
|
}
|
|
837
837
|
/**
|
|
838
|
-
* Return type of `connection.getStats()
|
|
838
|
+
* Return type of `connection.getStats()`, JSON-serializable per OBS-04.
|
|
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.
|
|
@@ -921,14 +921,14 @@ declare class Connection extends EventEmitter {
|
|
|
921
921
|
private _lastByteOutAt;
|
|
922
922
|
/** Ring buffer: last MAX_WARNINGS entries (OBS-05). */
|
|
923
923
|
private _warningBuffer;
|
|
924
|
-
/** Accurate total counts per code
|
|
924
|
+
/** Accurate total counts per code, unaffected by ring buffer truncation (OBS-05). */
|
|
925
925
|
private readonly _warningsByCode;
|
|
926
926
|
/** Set to true once the ring buffer overflows (OBS-05). */
|
|
927
927
|
private _warningsTruncated;
|
|
928
928
|
private _onWarningFn;
|
|
929
929
|
private _drainPromise;
|
|
930
930
|
/**
|
|
931
|
-
* beforeClose hook
|
|
931
|
+
* beforeClose hook, no-op default that resolves immediately.
|
|
932
932
|
*
|
|
933
933
|
* Phase 4 (Server) and Phase 5 (Client) override this instance property to
|
|
934
934
|
* register ACK-drain and send-queue drain logic respectively (D-07/D-08).
|
|
@@ -1024,7 +1024,7 @@ declare class Connection extends EventEmitter {
|
|
|
1024
1024
|
* Abruptly destroy the connection, discarding any pending writes.
|
|
1025
1025
|
*
|
|
1026
1026
|
* Transitions from any non-terminal state directly to `CLOSED` and calls
|
|
1027
|
-
* `transport.destroy(reason)`. Emits `'close'` event. Idempotent
|
|
1027
|
+
* `transport.destroy(reason)`. Emits `'close'` event. Idempotent, safe to
|
|
1028
1028
|
* call multiple times.
|
|
1029
1029
|
*
|
|
1030
1030
|
* @param reason - Optional error to propagate to the transport.
|
|
@@ -1059,12 +1059,12 @@ declare class Connection extends EventEmitter {
|
|
|
1059
1059
|
* Node throws `ERR_UNHANDLED_ERROR` when `'error'` is emitted on an `EventEmitter` with no
|
|
1060
1060
|
* listener. Every `'error'` emission in this class is reached from inside a transport callback
|
|
1061
1061
|
* (the socket's `'data'`/`'error'` listener), so an unguarded emit would become an **uncaught
|
|
1062
|
-
* exception that kills the process
|
|
1062
|
+
* exception that kills the process**, the exact failure the containment above exists to
|
|
1063
1063
|
* prevent, merely relocated one frame up the stack.
|
|
1064
1064
|
*
|
|
1065
1065
|
* `Connection` is a public export and can legitimately be driven with no `'error'` listener
|
|
1066
1066
|
* (`MllpServer` and `MllpClient` each attach one; a bare `Connection` need not). The connection
|
|
1067
|
-
* is still torn down and `'stateChange'`/`'close'` still fire, so a caller is never blind
|
|
1067
|
+
* is still torn down and `'stateChange'`/`'close'` still fire, so a caller is never blind,
|
|
1068
1068
|
* they simply do not get the typed error object they never asked for. Mirrors
|
|
1069
1069
|
* `MllpServer._emitErrorIfListened`.
|
|
1070
1070
|
*/
|
|
@@ -1077,7 +1077,7 @@ declare class Connection extends EventEmitter {
|
|
|
1077
1077
|
* consumers keep the stable `code` and `byteOffset`), then **destroy this connection only**.
|
|
1078
1078
|
*
|
|
1079
1079
|
* Why destroy rather than resynchronize: once `push` has thrown, the reader's position within
|
|
1080
|
-
* the byte stream is no longer trustworthy
|
|
1080
|
+
* the byte stream is no longer trustworthy, the next bytes could be the middle of a message.
|
|
1081
1081
|
* Guessing where the next frame starts is how a clinical message gets silently mis-split, so
|
|
1082
1082
|
* the connection is dropped instead. A server drops that one peer and keeps serving everyone
|
|
1083
1083
|
* else.
|
|
@@ -1089,17 +1089,17 @@ declare class Connection extends EventEmitter {
|
|
|
1089
1089
|
* engine that is already unwell. A compatibility failure is not a network blip.
|
|
1090
1090
|
*
|
|
1091
1091
|
* If a peer's quirk is *expected* (trailing junk, keepalive bytes, a missing leading VT), the
|
|
1092
|
-
* decoder's tolerance opt-ins are the supported answer
|
|
1092
|
+
* decoder's tolerance opt-ins are the supported answer, they turn the throw into a warning.
|
|
1093
1093
|
*/
|
|
1094
1094
|
private _onFatalFramingError;
|
|
1095
1095
|
/**
|
|
1096
|
-
* Backstop for a NON-framing throw escaping `FrameReader.push
|
|
1096
|
+
* Backstop for a NON-framing throw escaping `FrameReader.push`, i.e. our own bug, or a
|
|
1097
1097
|
* subscriber's, rather than the peer's bytes.
|
|
1098
1098
|
*
|
|
1099
1099
|
* `_onFrameDecoded` already contains subscriber throws at the dispatch site, so this should be
|
|
1100
1100
|
* unreachable. It exists because "unreachable" and "cannot kill the process from inside a
|
|
1101
1101
|
* socket `'data'` handler" are different claims, and only the second one is safe to bet a
|
|
1102
|
-
* clinical interface on. Reported honestly as an internal receive error
|
|
1102
|
+
* clinical interface on. Reported honestly as an internal receive error, never dressed up as
|
|
1103
1103
|
* a peer framing fault.
|
|
1104
1104
|
*/
|
|
1105
1105
|
private _onInternalReceiveError;
|
|
@@ -1141,33 +1141,33 @@ type ConnectionErrorPhase = "connect" | "send" | "receive" | "close" | "reconnec
|
|
|
1141
1141
|
/**
|
|
1142
1142
|
* Stable cause codes for `MllpConnectionError`.
|
|
1143
1143
|
*
|
|
1144
|
-
* These codes are a **public API
|
|
1144
|
+
* These codes are a **public API**, they appear in error inspection
|
|
1145
1145
|
* by callers, log pipelines, and monitoring dashboards. Renaming or
|
|
1146
1146
|
* removing a member is a breaking change.
|
|
1147
1147
|
*
|
|
1148
|
-
* - `'fifo-unsafe'
|
|
1148
|
+
* - `'fifo-unsafe'`, A queued send was rejected during reconnect because
|
|
1149
1149
|
* FIFO ordering cannot be safely resumed across sessions (CLIENT-17).
|
|
1150
|
-
* - `'in-flight-orphan'
|
|
1150
|
+
* - `'in-flight-orphan'`, An in-flight send (already write-flushed,
|
|
1151
1151
|
* ACK timer started) was rejected during reconnect in FIFO mode because
|
|
1152
1152
|
* the at-most-once delivery contract cannot be preserved across a
|
|
1153
1153
|
* socket drop (D-08, healthcare medication/orders semantics).
|
|
1154
|
-
* - `'tls-verify'` (Phase 8)
|
|
1154
|
+
* - `'tls-verify'` (Phase 8), The TLS handshake failed certificate
|
|
1155
1155
|
* verification (untrusted chain, expired/not-yet-valid cert, hostname
|
|
1156
1156
|
* mismatch, revocation, …). See {@link isTlsVerificationErrorCode} for the
|
|
1157
1157
|
* exact underlying error codes. Classified **permanent** by
|
|
1158
|
-
* `isTransientConnectionError
|
|
1158
|
+
* `isTransientConnectionError`, never auto-reconnect-looped into a
|
|
1159
1159
|
* misconfigured or MITM'd endpoint.
|
|
1160
|
-
* - `'tls-handshake'` (Phase 8)
|
|
1160
|
+
* - `'tls-handshake'` (Phase 8), A TLS-**protocol**-shaped handshake
|
|
1161
1161
|
* failure observed before `'secureConnect'`: `ERR_SSL_*` codes, `EPROTO`,
|
|
1162
1162
|
* or an OpenSSL alert-bearing error (protocol version mismatch, no shared
|
|
1163
1163
|
* cipher, a required mutual-TLS client certificate rejected by the
|
|
1164
1164
|
* server, …). See `isTlsProtocolError` for the boundary. Pure TCP-level
|
|
1165
1165
|
* failures (`ECONNREFUSED`, `ETIMEDOUT`, …) on a TLS-configured
|
|
1166
|
-
* connection carry **no** `connectionCause
|
|
1166
|
+
* connection carry **no** `connectionCause`, the same shape as plaintext.
|
|
1167
1167
|
*
|
|
1168
1168
|
* Scope note: the `'tls-verify'`/`'tls-handshake'` values are attached on
|
|
1169
1169
|
* the client's **initial `connect()` path**. Failures on the auto-reconnect
|
|
1170
|
-
* path surface as raw socket errors
|
|
1170
|
+
* path surface as raw socket errors, their transient/permanent
|
|
1171
1171
|
* classification still applies, but they do not (yet) carry a
|
|
1172
1172
|
* `connectionCause`.
|
|
1173
1173
|
*
|
|
@@ -1184,9 +1184,9 @@ type ConnectionErrorCause = "fifo-unsafe" | "in-flight-orphan" | "tls-verify" |
|
|
|
1184
1184
|
* Thrown (or emitted via `onError`) for socket-layer problems such as
|
|
1185
1185
|
* connection refused, ECONNRESET, ETIMEDOUT, or DNS failure.
|
|
1186
1186
|
*
|
|
1187
|
-
* - `cause
|
|
1188
|
-
* - `phase
|
|
1189
|
-
* - `connectionCause
|
|
1187
|
+
* - `cause`, the original OS or TLS error
|
|
1188
|
+
* - `phase`, which connection lifecycle phase the failure occurred in
|
|
1189
|
+
* - `connectionCause`, optional stable cause-code (e.g. for FIFO reconnect rejections)
|
|
1190
1190
|
*
|
|
1191
1191
|
* @example
|
|
1192
1192
|
* ```typescript
|
|
@@ -1231,14 +1231,14 @@ declare class MllpConnectionError extends Error {
|
|
|
1231
1231
|
*
|
|
1232
1232
|
* ## Why this is safety-critical
|
|
1233
1233
|
*
|
|
1234
|
-
* A positive acknowledgement (`AA`) tells the sender "you may forget this message
|
|
1234
|
+
* A positive acknowledgement (`AA`) tells the sender "you may forget this message, I
|
|
1235
1235
|
* have it." If a server emits `AA` *before* the message is durably handled and then the
|
|
1236
1236
|
* process crashes, the message is **silently lost** with no record on either side. For
|
|
1237
1237
|
* clinical messages (an admit, an order, a result) that is a patient-safety failure.
|
|
1238
1238
|
*
|
|
1239
1239
|
* The contract this module enforces: a positive ACK (`AA`) is only built once the
|
|
1240
1240
|
* application handler has resolved success. A handler that throws or rejects yields a
|
|
1241
|
-
* **negative** ACK (`AE` by default, `AR` when the handler asks for it)
|
|
1241
|
+
* **negative** ACK (`AE` by default, `AR` when the handler asks for it), never `AA`.
|
|
1242
1242
|
*
|
|
1243
1243
|
* Spec: HL7 v2.5.1 ch. 2 §2.9.2 (original acknowledgement mode), §2.9.3 (enhanced
|
|
1244
1244
|
* accept/application acknowledgement), Table 0008 (Acknowledgment Code), §2.9.2.2
|
|
@@ -1247,14 +1247,14 @@ declare class MllpConnectionError extends Error {
|
|
|
1247
1247
|
* @packageDocumentation
|
|
1248
1248
|
*/
|
|
1249
1249
|
/**
|
|
1250
|
-
* HL7 Table 0008
|
|
1250
|
+
* HL7 Table 0008, Acknowledgment Code. A **stable public API**.
|
|
1251
1251
|
*
|
|
1252
1252
|
* Two families, by mode (HL7 v2.5.1 §2.9):
|
|
1253
1253
|
*
|
|
1254
1254
|
* - **Original mode (§2.9.2):** `AA` accept, `AE` application error, `AR` application
|
|
1255
1255
|
* reject. The single ACK reports application-level outcome.
|
|
1256
1256
|
* - **Enhanced mode (§2.9.3):** `CA` commit accept, `CE` commit error, `CR` commit
|
|
1257
|
-
* reject
|
|
1257
|
+
* reject, the *accept* acknowledgement, distinct from a later application ACK.
|
|
1258
1258
|
*
|
|
1259
1259
|
* `AE` vs `AR`: `AE` is a processing **error** (the sender may resend later, e.g. a
|
|
1260
1260
|
* transient downstream outage); `AR` is a **reject** (the sender should not resend the
|
|
@@ -1284,7 +1284,7 @@ type NegativeAckCode = "AE" | "AR";
|
|
|
1284
1284
|
* returns. Throwing **any** error from a commit-gated handler yields `AE` by default;
|
|
1285
1285
|
* throw an `MllpAckError` to choose `AR` (application reject) instead.
|
|
1286
1286
|
*
|
|
1287
|
-
* The `message` is **never** copied into the ACK bytes or any emitted event
|
|
1287
|
+
* The `message` is **never** copied into the ACK bytes or any emitted event, it may
|
|
1288
1288
|
* carry PHI. Only the static, non-PHI `ackCode` influences the wire response.
|
|
1289
1289
|
*
|
|
1290
1290
|
* @example
|
|
@@ -1320,7 +1320,7 @@ declare class MllpAckError extends Error {
|
|
|
1320
1320
|
* Resolve the negative acknowledgement code for a handler failure.
|
|
1321
1321
|
*
|
|
1322
1322
|
* An {@link MllpAckError} carries an explicit `ackCode`; any other thrown value maps to
|
|
1323
|
-
* `AE` (application error
|
|
1323
|
+
* `AE` (application error, the default, since most handler failures are transient and
|
|
1324
1324
|
* a resend may succeed).
|
|
1325
1325
|
*
|
|
1326
1326
|
* @example
|
|
@@ -1338,27 +1338,27 @@ declare function resolveNackCode(err: unknown): NegativeAckCode;
|
|
|
1338
1338
|
*
|
|
1339
1339
|
* The rule this enforces: **never answer `AA` for a message you could not correlate.** A
|
|
1340
1340
|
* positive ACK is a promise the sender may forget the message; if it names a control ID
|
|
1341
|
-
* the sender cannot match
|
|
1341
|
+
* the sender cannot match, or names one of several messages it never read, the sender
|
|
1342
1342
|
* times out and resends, committing a **duplicate clinical message** (or worse, believes a
|
|
1343
1343
|
* destroyed message was delivered). Three payload-shaped reasons make a positive ACK
|
|
1344
1344
|
* uncorrelatable, all peer-reachable off the wire:
|
|
1345
1345
|
*
|
|
1346
|
-
* 1. **No readable `MSH`.** `readMshSegment` returns `null
|
|
1346
|
+
* 1. **No readable `MSH`.** `readMshSegment` returns `null`, e.g. a `BOM`/`SP`/`TAB`
|
|
1347
1347
|
* before `MSH` (which shares the `MSH`'s segment line, so `MSH` heads no segment), or
|
|
1348
1348
|
* a bare fragment delivered after a mid-payload `VT` discard (`MLLP_TRAILING_BYTES`).
|
|
1349
1349
|
* MSA-2 would be empty: an ACK that correlates to nothing.
|
|
1350
1350
|
* 2. **Empty MSH-10.** The `MSH` is readable but carries no message control ID, so there
|
|
1351
|
-
* is nothing to echo
|
|
1351
|
+
* is nothing to echo, again `MSA|AA|` with an empty MSA-2.
|
|
1352
1352
|
* 3. **A batch or concatenated messages.** An `FHS`/`BHS`/`BTS`/`FTS` envelope (§2.10.3)
|
|
1353
1353
|
* or a second `MSH` in the same frame: a single MSA-2 can echo only ONE control ID, so
|
|
1354
1354
|
* a positive ACK naming the first silently drops the rest (see
|
|
1355
|
-
* {@link containsBatchOrExtraMessage}). Batch ACK is its own feature (`MLLP-BATCH`)
|
|
1355
|
+
* {@link containsBatchOrExtraMessage}). Batch ACK is its own feature (`MLLP-BATCH`),
|
|
1356
1356
|
* until it is designed, a batch must stay a **loud non-positive** answer.
|
|
1357
1357
|
*
|
|
1358
1358
|
* This is a **refusal**, not a tolerance widening: it never makes an unreadable message
|
|
1359
1359
|
* readable, never re-bases on a located `MSH`, never parses a batch. It only recognizes
|
|
1360
1360
|
* the shapes for which a positive disposition would be a lie, so the builder can fall back
|
|
1361
|
-
* to `AE`. A **negative** requested code (`AE`/`AR`/`CE`/`CR`) is unaffected
|
|
1361
|
+
* to `AE`. A **negative** requested code (`AE`/`AR`/`CE`/`CR`) is unaffected, it is
|
|
1362
1362
|
* already non-positive, and echoing whatever control ID it can find is still useful.
|
|
1363
1363
|
*
|
|
1364
1364
|
* Pure, byte-level, never throws.
|
|
@@ -1375,12 +1375,12 @@ declare function rawAckUncorrelatable(payload: Buffer): boolean;
|
|
|
1375
1375
|
* Build a minimal original-mode HL7 v2 acknowledgement from raw inbound payload bytes,
|
|
1376
1376
|
* **without a parser** (parser-driven ACKs are the `@cosyte/mllp/ack-from-hl7` subpath).
|
|
1377
1377
|
*
|
|
1378
|
-
* Locates the `MSH` segment (the first `CR`/`LF`-delimited segment starting with `MSH
|
|
1378
|
+
* Locates the `MSH` segment (the first `CR`/`LF`-delimited segment starting with `MSH`,
|
|
1379
1379
|
* so a leading `CR` or an `FHS`/`BHS` batch header does not hide it), reads the field
|
|
1380
1380
|
* separator from **MSH-1** and the encoding characters from **MSH-2**, then splits that
|
|
1381
1381
|
* segment on that separator to read fields. The ACK swaps sender/receiver per HL7 ACK
|
|
1382
|
-
* rules, echoes the inbound MSH-10 into MSA-2 (§2.9.2.2), sets MSA-1 to `code`, and
|
|
1383
|
-
* negative codes
|
|
1382
|
+
* rules, echoes the inbound MSH-10 into MSA-2 (§2.9.2.2), sets MSA-1 to `code`, and, for
|
|
1383
|
+
* negative codes, adds a **static, PHI-free** MSA-3 reason. A fresh control ID fills the
|
|
1384
1384
|
* ACK's own MSH-10.
|
|
1385
1385
|
*
|
|
1386
1386
|
* ## Why the delimiters are read, not assumed
|
|
@@ -1390,7 +1390,7 @@ declare function rawAckUncorrelatable(payload: Buffer): boolean;
|
|
|
1390
1390
|
* convention, not the spec. Assuming it was wrong in two compounding ways:
|
|
1391
1391
|
*
|
|
1392
1392
|
* 1. **Reading.** Splitting a `!`-delimited message on `|` yields ONE field, so every
|
|
1393
|
-
* echoed field
|
|
1393
|
+
* echoed field, including MSH-10, came back empty. The ACK went out as
|
|
1394
1394
|
* `MSA|AA|` with **no correlation id at all**: the sender cannot match it, times
|
|
1395
1395
|
* out, and resends → a duplicate clinical message. That is the exact failure this
|
|
1396
1396
|
* package's correlator exists to prevent, manufactured by the ACK builder itself.
|
|
@@ -1404,28 +1404,28 @@ declare function rawAckUncorrelatable(payload: Buffer): boolean;
|
|
|
1404
1404
|
*
|
|
1405
1405
|
* ## It reads the MSH through the SHARED scanner
|
|
1406
1406
|
*
|
|
1407
|
-
* The MSH read is `readMshSegment` from `src/internal/control-id.ts
|
|
1407
|
+
* The MSH read is `readMshSegment` from `src/internal/control-id.ts`, the same call the
|
|
1408
1408
|
* client's correlator makes to derive the key it will later match this ACK against. That
|
|
1409
1409
|
* is deliberate and it is the whole point: this builder used to re-derive the read itself
|
|
1410
1410
|
* (`payload.toString("latin1").split("\r")`, hunting for an `MSH` anywhere in the
|
|
1411
1411
|
* payload), and the two disagreed on real inputs. On a **truncated MSH** followed by a
|
|
1412
1412
|
* `PID` the correlator keyed on the PID's MRN while this builder echoed an empty MSA-2. On
|
|
1413
|
-
* a payload with a **leading `CR
|
|
1414
|
-
* which real senders emit
|
|
1413
|
+
* a payload with a **leading `CR`**, which the MLLP decoder passes straight through, and
|
|
1414
|
+
* which real senders emit, this builder found the `MSH` and echoed MSH-10 correctly while
|
|
1415
1415
|
* the correlator, requiring `MSH` at byte 0, gave up. Every such disagreement is an ACK the
|
|
1416
1416
|
* sender cannot match → timeout → resend → **duplicate clinical message**.
|
|
1417
1417
|
*
|
|
1418
|
-
* The fix is one scan
|
|
1418
|
+
* The fix is one scan, but note *which* scan. The first attempt made them agree by
|
|
1419
1419
|
* requiring `MSH` at byte 0 everywhere, which "resolved" the leading-`CR` disagreement by
|
|
1420
1420
|
* degrading the side that had been **right**: `buildRawAck` began emitting a positive `AA`
|
|
1421
1421
|
* with an empty MSA-2, silently, for a message whose MSH-10 was plainly present. Agreement
|
|
1422
1422
|
* is not the goal; agreeing on the *correct*, *tolerant* answer is. A lenient reader may
|
|
1423
|
-
* never drop data that is there (Postel's Law
|
|
1423
|
+
* never drop data that is there (Postel's Law, CLAUDE.md).
|
|
1424
1424
|
*
|
|
1425
1425
|
* ## The fail-safe downgrade: never a positive `AA`/`CA` it cannot correlate
|
|
1426
1426
|
*
|
|
1427
1427
|
* A positive acknowledgement is a promise the sender may forget the message. If it names a
|
|
1428
|
-
* control ID the sender cannot match
|
|
1428
|
+
* control ID the sender cannot match, or names one message out of several it never read,
|
|
1429
1429
|
* the sender times out and resends, committing a **duplicate clinical message**. So a
|
|
1430
1430
|
* requested positive code is **downgraded** to its non-positive counterpart (`AA`→`AE`,
|
|
1431
1431
|
* `CA`→`CE`) whenever the payload cannot carry a correlatable positive ACK: no readable
|
|
@@ -1433,10 +1433,10 @@ declare function rawAckUncorrelatable(payload: Buffer): boolean;
|
|
|
1433
1433
|
* acknowledge. See {@link rawAckUncorrelatable} for the exact conditions and why each is a
|
|
1434
1434
|
* refusal rather than a widened reader. A requested **negative** code (`AE`/`AR`/`CE`/`CR`)
|
|
1435
1435
|
* is never touched. This mirrors the parser-backed `buildMllpAck`, which downgrades and
|
|
1436
|
-
* warns on an unparseable inbound
|
|
1436
|
+
* warns on an unparseable inbound, the two builders' fail-safe semantics now agree.
|
|
1437
1437
|
*
|
|
1438
1438
|
* **Never throws** and **never copies payload content** beyond the routing/control
|
|
1439
|
-
* metadata above
|
|
1439
|
+
* metadata above, `readMshSegment` stops at the MSH's segment terminator, so no field of
|
|
1440
1440
|
* any later segment (PID and friends) can be reached, let alone echoed. On a missing or
|
|
1441
1441
|
* unreadable `MSH` it returns a minimal well-formed ACK carrying the (downgraded) `code` so
|
|
1442
1442
|
* the caller can still respond.
|
|
@@ -1444,7 +1444,7 @@ declare function rawAckUncorrelatable(payload: Buffer): boolean;
|
|
|
1444
1444
|
* @param payload - Raw decoded HL7 v2 payload bytes (MLLP framing already stripped).
|
|
1445
1445
|
* @param code - Requested MSA-1 acknowledgement code. A positive `AA`/`CA` is downgraded to
|
|
1446
1446
|
* `AE`/`CE` when the message cannot be correlated (see above).
|
|
1447
|
-
* @returns ACK payload bytes (no framing
|
|
1447
|
+
* @returns ACK payload bytes (no framing, the caller wraps with `encodeFrame`).
|
|
1448
1448
|
*
|
|
1449
1449
|
* @example
|
|
1450
1450
|
* ```typescript
|
|
@@ -1455,7 +1455,7 @@ declare function rawAckUncorrelatable(payload: Buffer): boolean;
|
|
|
1455
1455
|
declare function buildRawAck(payload: Buffer, code: AckCode): Buffer;
|
|
1456
1456
|
|
|
1457
1457
|
/**
|
|
1458
|
-
* MLLP Server
|
|
1458
|
+
* MLLP Server, createServer(), createStarterServer(), and MllpServer class.
|
|
1459
1459
|
*
|
|
1460
1460
|
* Provides the server-side MLLP transport: listen for inbound TCP connections,
|
|
1461
1461
|
* decode MLLP-framed messages, surface them as Buffer payloads, and support
|
|
@@ -1479,7 +1479,7 @@ declare function buildRawAck(payload: Buffer, code: AckCode): Buffer;
|
|
|
1479
1479
|
/**
|
|
1480
1480
|
* Metadata attached to each decoded MLLP message (SERVER-03).
|
|
1481
1481
|
*
|
|
1482
|
-
* All fields are `readonly
|
|
1482
|
+
* All fields are `readonly`, the object is `Object.freeze()`'d before emission.
|
|
1483
1483
|
*
|
|
1484
1484
|
* @example
|
|
1485
1485
|
* ```typescript
|
|
@@ -1498,30 +1498,30 @@ interface MessageMeta {
|
|
|
1498
1498
|
}
|
|
1499
1499
|
/**
|
|
1500
1500
|
* Why the server sent a **negative** acknowledgement instead of `AA` on the auto-ACK path.
|
|
1501
|
-
* A stable, **PHI-free** enum (no payload bytes, no control ID)
|
|
1501
|
+
* A stable, **PHI-free** enum (no payload bytes, no control ID), safe to log and to key
|
|
1502
1502
|
* metrics on.
|
|
1503
1503
|
*
|
|
1504
|
-
* - `'handler-rejected'
|
|
1504
|
+
* - `'handler-rejected'`, a commit-gated `onMessage` handler threw/rejected (the commit
|
|
1505
1505
|
* contract: a positive ACK cannot precede a successful commit).
|
|
1506
|
-
* - `'uncorrelatable-inbound'
|
|
1506
|
+
* - `'uncorrelatable-inbound'`, the inbound could not carry a correlatable positive ACK
|
|
1507
1507
|
* (no readable `MSH`, an empty MSH-10, or a batch/concatenated-message shape). A positive
|
|
1508
1508
|
* `AA` here names a control ID the sender cannot match → timeout → resend → **duplicate
|
|
1509
1509
|
* clinical message**. See {@link rawAckUncorrelatable}.
|
|
1510
|
-
* - `'discarded-bytes'
|
|
1510
|
+
* - `'discarded-bytes'`, the decoder flagged `MLLP_TRAILING_BYTES` for this frame: a
|
|
1511
1511
|
* mid-payload `VT` made it **discard accumulated bytes** and deliver only the fragment
|
|
1512
1512
|
* after it. The clinical message was destroyed in transit; a positive `AA` would tell the
|
|
1513
1513
|
* sender a message we never received was delivered.
|
|
1514
1514
|
*/
|
|
1515
1515
|
type NackReason = "handler-rejected" | "uncorrelatable-inbound" | "discarded-bytes";
|
|
1516
1516
|
/**
|
|
1517
|
-
* Payload of the server `'nack'` event
|
|
1517
|
+
* Payload of the server `'nack'` event, emitted whenever the server responds with a
|
|
1518
1518
|
* **negative** acknowledgement instead of `AA` on the auto-ACK path (the fail-safe commit
|
|
1519
1519
|
* contract). This fires both when a commit-gated `autoAck: 'AA'` handler throws/rejects and
|
|
1520
1520
|
* when the server **downgrades** a positive auto-ACK because the inbound could not be
|
|
1521
1521
|
* correlated ({@link NackReason}).
|
|
1522
1522
|
*
|
|
1523
1523
|
* **PHI-safe by construction:** carries only the connection ID, the resolved
|
|
1524
|
-
* acknowledgement code, and a static `reason
|
|
1524
|
+
* acknowledgement code, and a static `reason`, never the payload, the inbound control ID,
|
|
1525
1525
|
* or the thrown error's message (which may carry PHI). The object is `Object.freeze()`'d
|
|
1526
1526
|
* before emission.
|
|
1527
1527
|
*
|
|
@@ -1600,11 +1600,11 @@ interface ServerOptions {
|
|
|
1600
1600
|
/**
|
|
1601
1601
|
* Called for each decoded MLLP message.
|
|
1602
1602
|
*
|
|
1603
|
-
* Its role depends on `autoAck
|
|
1603
|
+
* Its role depends on `autoAck`, this is the **commit contract** (HL7 v2.5.1 §2.9.2):
|
|
1604
1604
|
*
|
|
1605
1605
|
* - **`autoAck: 'AA'` + this handler ⇒ commit-gated (the safe default).** The handler
|
|
1606
1606
|
* is the durable-commit step. The server **awaits** it and only then sends the ACK:
|
|
1607
|
-
* resolve ⇒ `AA`; **throw/reject ⇒ `AE`** (or `AR` via {@link MllpAckError})
|
|
1607
|
+
* resolve ⇒ `AA`; **throw/reject ⇒ `AE`** (or `AR` via {@link MllpAckError}), a
|
|
1608
1608
|
* positive ACK can never precede a successful commit. Do **not** call `conn.send()`
|
|
1609
1609
|
* here in this mode.
|
|
1610
1610
|
* - **`autoAck` unset ⇒ manual mode.** The handler owns the response; build and send the
|
|
@@ -1617,7 +1617,7 @@ interface ServerOptions {
|
|
|
1617
1617
|
onMessage?: (payload: Buffer, meta: MessageMeta, conn: Connection) => void | Promise<void>;
|
|
1618
1618
|
/**
|
|
1619
1619
|
* FrameReader tolerance options applied to every accepted connection (SERVER-12).
|
|
1620
|
-
* Merged with SERVER_DEFAULT_FRAMING
|
|
1620
|
+
* Merged with SERVER_DEFAULT_FRAMING, caller-supplied values override defaults.
|
|
1621
1621
|
* `onFrame` and `onWarning` are managed internally and must not be supplied here.
|
|
1622
1622
|
*/
|
|
1623
1623
|
framing?: Omit<FrameReaderOptions, "onFrame" | "onWarning">;
|
|
@@ -1628,21 +1628,21 @@ interface ServerOptions {
|
|
|
1628
1628
|
/**
|
|
1629
1629
|
* Auto-ACK mode. When set, the server builds and sends the ACK for each message.
|
|
1630
1630
|
*
|
|
1631
|
-
* - **`'AA'
|
|
1631
|
+
* - **`'AA'`**, auto-acknowledge with the fail-safe **commit contract**:
|
|
1632
1632
|
* - **With an `onMessage` handler ⇒ commit-gated (recommended).** The server awaits
|
|
1633
1633
|
* `onMessage` (the durable-commit step), then sends `AA` on success or a **negative**
|
|
1634
1634
|
* ACK on failure (`AE` by default; `AR` via {@link MllpAckError}). The positive ACK
|
|
1635
|
-
* **cannot precede a successful commit
|
|
1635
|
+
* **cannot precede a successful commit**, a handler throw can never yield `AA`.
|
|
1636
1636
|
* - **Without an `onMessage` handler ⇒ transport-accept.** `AA` is sent on frame
|
|
1637
|
-
* receipt. This `AA` means only **"bytes received and framed"
|
|
1637
|
+
* receipt. This `AA` means only **"bytes received and framed"**, *not*
|
|
1638
1638
|
* "application-processed". ⚠️ For clinical messages this is unsafe on its own:
|
|
1639
1639
|
* pair `'AA'` with an `onMessage` handler that durably commits, so the ACK reflects
|
|
1640
1640
|
* real processing.
|
|
1641
|
-
* - **`fn
|
|
1641
|
+
* - **`fn`**, `fn(payload, meta, conn)` builds the ACK bytes the server sends; the
|
|
1642
1642
|
* caller fully owns MSA-1 (e.g. to emit enhanced-mode `CA`/`CE`/`CR`).
|
|
1643
1643
|
*
|
|
1644
1644
|
* The `'message'` event always fires BEFORE the ACK is sent (D-03). Do NOT call
|
|
1645
|
-
* `conn.send()` in `onMessage` when `autoAck` is set
|
|
1645
|
+
* `conn.send()` in `onMessage` when `autoAck` is set, this results in two ACKs.
|
|
1646
1646
|
*/
|
|
1647
1647
|
autoAck?: "AA" | ((payload: Buffer, meta: MessageMeta, conn: Connection) => Buffer | Promise<Buffer>);
|
|
1648
1648
|
/**
|
|
@@ -1676,14 +1676,14 @@ interface ServerOptions {
|
|
|
1676
1676
|
*/
|
|
1677
1677
|
tls?: ServerTlsOptions;
|
|
1678
1678
|
/**
|
|
1679
|
-
* Opt-in required to bind a wildcard host
|
|
1679
|
+
* Opt-in required to bind a wildcard host, a bind-safety guardrail
|
|
1680
1680
|
* (Phase 8). Without this flag, `listen()` rejects a wildcard host in
|
|
1681
1681
|
* two tiers: **literal spellings** (`'0.0.0.0'`, `'::'`, `''`, `'::0'`,
|
|
1682
1682
|
* `'0:0:0:0:0:0:0:0'`, `'::ffff:0.0.0.0'`, …) reject **pre-bind** (fast
|
|
1683
1683
|
* path, nothing is ever bound); **resolver-only shorthands** (`'0'`,
|
|
1684
1684
|
* `'0.0'`, `'0x0.0.0.0'`, hostnames resolving to the unspecified
|
|
1685
1685
|
* address, …) are caught **post-bind** against the OS-normalized bound
|
|
1686
|
-
* address
|
|
1686
|
+
* address, the just-bound server closes before any connection can be
|
|
1687
1687
|
* accepted and `listen()` rejects, with no listening state and no
|
|
1688
1688
|
* `'listening'` event either way. When `true`, binding a wildcard host
|
|
1689
1689
|
* emits a one-time `'securityWarning'` (`MLLP_BIND_ALL_INTERFACES`) at
|
|
@@ -1694,7 +1694,7 @@ interface ServerOptions {
|
|
|
1694
1694
|
allowWildcardBind?: boolean;
|
|
1695
1695
|
}
|
|
1696
1696
|
/**
|
|
1697
|
-
* Options for `createStarterServer()
|
|
1697
|
+
* Options for `createStarterServer()`, the "three lines of code" factory (SERVER-08).
|
|
1698
1698
|
*
|
|
1699
1699
|
* Extends `ServerOptions` with `port`, `host`, and `handleSignals`. Defaults:
|
|
1700
1700
|
* `autoAck: 'AA'`, `drainTimeoutMs: 30_000`, `Symbol.asyncDispose` wired.
|
|
@@ -1715,7 +1715,7 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1715
1715
|
/** Port to listen on. */
|
|
1716
1716
|
port: number;
|
|
1717
1717
|
/**
|
|
1718
|
-
* Host to bind to. Default `'127.0.0.1'` (Phase 8 bind-safety hardening
|
|
1718
|
+
* Host to bind to. Default `'127.0.0.1'` (Phase 8 bind-safety hardening,
|
|
1719
1719
|
* was `'0.0.0.0'`; binding all interfaces now requires
|
|
1720
1720
|
* `ServerOptions.allowWildcardBind: true`).
|
|
1721
1721
|
*/
|
|
@@ -1725,13 +1725,13 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1725
1725
|
* call `server.close()` then `process.exit(0)` (D-09). Default: `false`.
|
|
1726
1726
|
*
|
|
1727
1727
|
* Handlers are automatically removed when `server.close()` is called, so
|
|
1728
|
-
* `process.listenerCount('SIGTERM') === 0` after close() completes
|
|
1728
|
+
* `process.listenerCount('SIGTERM') === 0` after close() completes, preventing
|
|
1729
1729
|
* handler accumulation across test instances or multiple server restarts.
|
|
1730
1730
|
*/
|
|
1731
1731
|
handleSignals?: boolean;
|
|
1732
1732
|
}
|
|
1733
1733
|
/**
|
|
1734
|
-
* MLLP TCP server
|
|
1734
|
+
* MLLP TCP server, wraps `net.Server` without extending it (D-02).
|
|
1735
1735
|
*
|
|
1736
1736
|
* Each accepted socket is wrapped in a `NetTransport`, connected to a `Connection`
|
|
1737
1737
|
* with server-level framing options, and added to `_connections`. Messages are
|
|
@@ -1739,7 +1739,7 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1739
1739
|
*
|
|
1740
1740
|
* Public events: `'listening'`, `'connection'`, `'message'`, `'nack'`, `'error'`, `'close'`.
|
|
1741
1741
|
* The `'nack'` event ({@link NackEvent}) fires whenever the server returns a negative ACK
|
|
1742
|
-
* instead of `AA` on the auto-ACK path
|
|
1742
|
+
* instead of `AA` on the auto-ACK path, a commit-gated `autoAck: 'AA'` handler failing, or
|
|
1743
1743
|
* a positive auto-ACK **downgraded** because the inbound could not be correlated (an
|
|
1744
1744
|
* unreadable/uncorrelatable message, or one whose bytes the decoder discarded). The
|
|
1745
1745
|
* {@link NackReason} distinguishes the causes.
|
|
@@ -1747,16 +1747,16 @@ interface StarterServerOptions extends ServerOptions {
|
|
|
1747
1747
|
* **`'error'` contract:** underlying `net.Server`/`tls.Server` errors are forwarded to the
|
|
1748
1748
|
* `'error'` event whenever a listener is attached. With **no** listener, the outcome depends on
|
|
1749
1749
|
* server state: during a `listen()` (the bind window) the error **rejects the `listen()` promise**
|
|
1750
|
-
*
|
|
1750
|
+
* the primary error surface, and the process never crashes on a bind error; with no `listen()`
|
|
1751
1751
|
* in flight and the server not serving (e.g. a stale async error after `close()`) the error is
|
|
1752
1752
|
* dropped; but **while serving**, an unlistened runtime error (e.g. accept-loop `EMFILE`) keeps
|
|
1753
|
-
* Node's fail-loud crash-on-unlistened-`'error'` convention
|
|
1753
|
+
* Node's fail-loud crash-on-unlistened-`'error'` convention, a silent accept outage is
|
|
1754
1754
|
* impossible. Caveat for `'error'` listeners: the forwarder runs **before** the internal
|
|
1755
1755
|
* `listen()` rejection handler, so an `'error'` listener that synchronously calls `close()`
|
|
1756
1756
|
* during the bind window changes the `listen()` rejection from the bind error to the typed
|
|
1757
|
-
* close-during-listen `MllpConnectionError
|
|
1757
|
+
* close-during-listen `MllpConnectionError`, match on the `'error'` event payload, not the
|
|
1758
1758
|
* rejection, if you do that.
|
|
1759
|
-
* Phase 8 (TLS/MLLPS) adds two more: `'tlsClientError'` (a failed TLS handshake
|
|
1759
|
+
* Phase 8 (TLS/MLLPS) adds two more: `'tlsClientError'` (a failed TLS handshake, the
|
|
1760
1760
|
* server logs it and keeps serving other connections) and `'securityWarning'` (loud,
|
|
1761
1761
|
* one-time notice when a wildcard host is bound via `allowWildcardBind: true`).
|
|
1762
1762
|
* All event payloads are `Object.freeze()`'d before emission (SERVER-10).
|
|
@@ -1778,13 +1778,13 @@ declare class MllpServer extends EventEmitter {
|
|
|
1778
1778
|
private readonly _netServer;
|
|
1779
1779
|
private readonly _connections;
|
|
1780
1780
|
private readonly _opts;
|
|
1781
|
-
/** `true` when `ServerOptions.tls` is set
|
|
1781
|
+
/** `true` when `ServerOptions.tls` is set, `_netServer` is actually a `tls.Server`. */
|
|
1782
1782
|
private readonly _isTls;
|
|
1783
1783
|
private _listening;
|
|
1784
|
-
/** Phase 8
|
|
1784
|
+
/** Phase 8, single-flight guard: `true` while a `listen()` call is settling. */
|
|
1785
1785
|
private _listenInFlight;
|
|
1786
1786
|
/**
|
|
1787
|
-
* Phase 8
|
|
1787
|
+
* Phase 8, settle hook for an in-flight `listen()`. Non-null only while a
|
|
1788
1788
|
* `listen()` is in flight; invoked by `close()` so a close racing startup
|
|
1789
1789
|
* rejects the pending `listen()` (typed) instead of leaving it hung with
|
|
1790
1790
|
* the single-flight guard stuck. Cleared on every settle path.
|
|
@@ -1794,7 +1794,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1794
1794
|
private _host;
|
|
1795
1795
|
private _acceptedTotal;
|
|
1796
1796
|
private _closedTotal;
|
|
1797
|
-
/** Phase 8
|
|
1797
|
+
/** Phase 8, count of `'tlsClientError'` events since listen(). */
|
|
1798
1798
|
private _tlsClientErrorsTotal;
|
|
1799
1799
|
/**
|
|
1800
1800
|
* Construct an MLLP server. Created idle; call `listen()` (or use
|
|
@@ -1810,23 +1810,23 @@ declare class MllpServer extends EventEmitter {
|
|
|
1810
1810
|
* `Object.freeze({ port: actualPort, host: actualHost })`.
|
|
1811
1811
|
*
|
|
1812
1812
|
* **Single-flight:** one `listen()` per server lifecycle at a time. A call
|
|
1813
|
-
* while the server is already listening
|
|
1814
|
-
* still in flight
|
|
1813
|
+
* while the server is already listening, or while another `listen()` is
|
|
1814
|
+
* still in flight, rejects with a typed `MllpConnectionError` (concurrent
|
|
1815
1815
|
* binds raced each other's post-bind safety checks). Call `close()` before
|
|
1816
1816
|
* re-listening; sequential `listen()` → `close()` → `listen()` is fine.
|
|
1817
1817
|
*
|
|
1818
1818
|
* **`close()` during an in-flight `listen()`** rejects that `listen()` with
|
|
1819
1819
|
* a typed `MllpConnectionError` (never a hang) and clears the single-flight
|
|
1820
|
-
* guard
|
|
1820
|
+
* guard, a subsequent `listen()` on the same server works. This makes
|
|
1821
1821
|
* `Symbol.asyncDispose` (which delegates to `close()`) safe even before a
|
|
1822
1822
|
* `listen()` has settled. (Exception: `close({ signal })` with an
|
|
1823
|
-
* already-aborted signal is a no-op AbortError rejection
|
|
1823
|
+
* already-aborted signal is a no-op AbortError rejection, the in-flight
|
|
1824
1824
|
* `listen()` is left to settle on its own bind outcome; see `close()`.)
|
|
1825
1825
|
*
|
|
1826
1826
|
* @param port - TCP port to bind. Use `0` to let the OS assign an ephemeral port.
|
|
1827
1827
|
* @param hostOrOpts - Host string or object with `host` and optional `signal`.
|
|
1828
1828
|
* Default host: `'127.0.0.1'` (Phase 8 bind-safety hardening). Wildcard hosts
|
|
1829
|
-
* are rejected unless `ServerOptions.allowWildcardBind: true
|
|
1829
|
+
* are rejected unless `ServerOptions.allowWildcardBind: true`, literal
|
|
1830
1830
|
* spellings pre-bind, resolver-only shorthands post-bind via the
|
|
1831
1831
|
* OS-normalized bound address.
|
|
1832
1832
|
*
|
|
@@ -1845,18 +1845,18 @@ declare class MllpServer extends EventEmitter {
|
|
|
1845
1845
|
* Stop accepting new connections and gracefully close all active connections.
|
|
1846
1846
|
*
|
|
1847
1847
|
* Sequence (D-06):
|
|
1848
|
-
* 1. `net.Server.close()
|
|
1848
|
+
* 1. `net.Server.close()`, stops accepting new connections immediately
|
|
1849
1849
|
* 2. If `_connections` is empty: resolves immediately (no drain needed)
|
|
1850
|
-
* 3. Calls `_drainAll(drainTimeoutMs)
|
|
1850
|
+
* 3. Calls `_drainAll(drainTimeoutMs)`, Promise.all + side-effect setTimeout
|
|
1851
1851
|
* that force-destroys stragglers after the drain window
|
|
1852
1852
|
*
|
|
1853
1853
|
* Calling `close()` while a `listen()` is still in flight proactively
|
|
1854
1854
|
* settles that `listen()` with a typed `MllpConnectionError` rejection
|
|
1855
|
-
* and clears the single-flight guard
|
|
1855
|
+
* and clears the single-flight guard, the server is immediately
|
|
1856
1856
|
* re-listenable. `Symbol.asyncDispose` (which delegates here) is
|
|
1857
1857
|
* therefore safe before `listen()` settles. **Qualification:** a
|
|
1858
1858
|
* `close({ signal })` whose signal is **already aborted** rejects
|
|
1859
|
-
* immediately with `AbortError` and performs no work
|
|
1859
|
+
* immediately with `AbortError` and performs no work, it does NOT close
|
|
1860
1860
|
* the server and does NOT settle the in-flight `listen()`, which simply
|
|
1861
1861
|
* continues and settles on its own bind outcome.
|
|
1862
1862
|
*
|
|
@@ -1876,7 +1876,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1876
1876
|
/**
|
|
1877
1877
|
* Drain all active connections with a shared timeout.
|
|
1878
1878
|
*
|
|
1879
|
-
* Uses Promise.all (not Promise.race) for coordination
|
|
1879
|
+
* Uses Promise.all (not Promise.race) for coordination, the timeout is a
|
|
1880
1880
|
* side effect that calls `conn.destroy()` on stragglers after `drainTimeoutMs`.
|
|
1881
1881
|
* Promise.all resolves when all `conn.close()` promises settle (which happens
|
|
1882
1882
|
* because `conn.destroy()` transitions connections to CLOSED).
|
|
@@ -1885,7 +1885,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1885
1885
|
*/
|
|
1886
1886
|
private _drainAll;
|
|
1887
1887
|
/**
|
|
1888
|
-
* Async disposal
|
|
1888
|
+
* Async disposal, delegates to `close()` for `await using` support.
|
|
1889
1889
|
*
|
|
1890
1890
|
* @example
|
|
1891
1891
|
* ```typescript
|
|
@@ -1909,7 +1909,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1909
1909
|
*/
|
|
1910
1910
|
getStats(): ServerStats;
|
|
1911
1911
|
/**
|
|
1912
|
-
* Emit on `'error'` only when a listener is attached
|
|
1912
|
+
* Emit on `'error'` only when a listener is attached, an unlistened
|
|
1913
1913
|
* `EventEmitter` `'error'` emission throws (see the class JSDoc error
|
|
1914
1914
|
* contract). The single guard shared by the optional-tap forwarding sites
|
|
1915
1915
|
* in this class; the constructor's net.Server forwarder deliberately does
|
|
@@ -1919,7 +1919,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1919
1919
|
/**
|
|
1920
1920
|
* Emit a server event, containing a throwing subscriber.
|
|
1921
1921
|
*
|
|
1922
|
-
* Every event this server emits is reachable from a callback we do not own
|
|
1922
|
+
* Every event this server emits is reachable from a callback we do not own, the `net.Server`
|
|
1923
1923
|
* `'connection'` listener, the `tls.Server` `'tlsClientError'` listener, a socket's `'data'`
|
|
1924
1924
|
* listener (via `Connection`), or the `catch` block of a `void`-ed async ACK task. A throwing
|
|
1925
1925
|
* subscriber would therefore unwind into that callback and kill the process (or reject a void-ed
|
|
@@ -1932,7 +1932,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1932
1932
|
private _onSocketAccepted;
|
|
1933
1933
|
/**
|
|
1934
1934
|
* Extract a minimal, content-free peer-certificate summary (Phase 8, ATNA
|
|
1935
|
-
* ITI-19 mutual authentication)
|
|
1935
|
+
* ITI-19 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).
|
|
@@ -1945,29 +1945,29 @@ declare class MllpServer extends EventEmitter {
|
|
|
1945
1945
|
* then dispatches: resolve ⇒ `AA`; throw/reject ⇒ a negative ACK (`AE` by default,
|
|
1946
1946
|
* `AR` via {@link MllpAckError}). A positive ACK can never precede a successful commit.
|
|
1947
1947
|
*
|
|
1948
|
-
* Without an `onMessage` handler, `'AA'` degrades to a **transport-accept
|
|
1948
|
+
* Without an `onMessage` handler, `'AA'` degrades to a **transport-accept**, `AA`
|
|
1949
1949
|
* meaning "bytes received and framed", not "application-processed".
|
|
1950
1950
|
*
|
|
1951
1951
|
* A handler failure is **expected flow**, not a server error: it produces a negative
|
|
1952
1952
|
* ACK and a `'nack'` event. The thrown error's message is never placed on the wire or
|
|
1953
|
-
* in the event (it may carry PHI)
|
|
1953
|
+
* in the event (it may carry PHI), only the static `ackCode` and the inbound control
|
|
1954
1954
|
* metadata reach the peer.
|
|
1955
1955
|
*/
|
|
1956
1956
|
private _sendCommitAck;
|
|
1957
1957
|
/**
|
|
1958
1958
|
* Resolve the auto-ACK code for a message the server would otherwise acknowledge
|
|
1959
|
-
* **positively** (`AA`)
|
|
1959
|
+
* **positively** (`AA`), the no-handler transport-accept and the post-commit success
|
|
1960
1960
|
* paths. Fail-safe: downgrade to `AE` and emit a PHI-safe `'nack'` whenever the message
|
|
1961
1961
|
* cannot carry a correlatable positive ACK, so `AA` never names a control ID the sender
|
|
1962
1962
|
* cannot match (→ timeout → resend → **duplicate clinical message**).
|
|
1963
1963
|
*
|
|
1964
1964
|
* Two independent disqualifiers, one from the bytes and one from the frame:
|
|
1965
1965
|
*
|
|
1966
|
-
* - **`rawAckUncorrelatable(payload)
|
|
1966
|
+
* - **`rawAckUncorrelatable(payload)`**, no readable `MSH`, an empty MSH-10, or a
|
|
1967
1967
|
* batch/concatenated-message shape. This is the SAME predicate `buildRawAck` enforces
|
|
1968
1968
|
* on the wire; it is re-checked here so the downgrade is **observable** (a `'nack'`
|
|
1969
1969
|
* event) rather than a silent code change inside the builder.
|
|
1970
|
-
* - **`MLLP_TRAILING_BYTES`** in the frame's warnings
|
|
1970
|
+
* - **`MLLP_TRAILING_BYTES`** in the frame's warnings, a mid-payload `VT` made the
|
|
1971
1971
|
* decoder discard accumulated bytes and deliver only the fragment after it. The
|
|
1972
1972
|
* clinical message was destroyed in transit, so even a fragment that happens to carry
|
|
1973
1973
|
* a readable MSH must not be positively acknowledged. This condition is invisible to
|
|
@@ -1976,7 +1976,7 @@ declare class MllpServer extends EventEmitter {
|
|
|
1976
1976
|
* accumulating the delivered frame, so it is frame-scoped: it names *this* payload's own
|
|
1977
1977
|
* discard, never a neighbouring frame's trailing junk.
|
|
1978
1978
|
*
|
|
1979
|
-
* `'discarded-bytes'` takes precedence in the reason when both hold
|
|
1979
|
+
* `'discarded-bytes'` takes precedence in the reason when both hold, it is the root cause.
|
|
1980
1980
|
*/
|
|
1981
1981
|
private _resolveAutoAckPositive;
|
|
1982
1982
|
/**
|
|
@@ -1990,19 +1990,19 @@ declare class MllpServer extends EventEmitter {
|
|
|
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
|
|
1993
|
+
* on the connection, the server never crashes (D-04).
|
|
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 (D-04)
|
|
1999
|
+
* a `MllpConnectionError({ phase: 'send' })` on the connection (D-04), the peer will
|
|
2000
2000
|
* time out waiting and may retry. The server never crashes.
|
|
2001
2001
|
*/
|
|
2002
2002
|
private _dispatchAck;
|
|
2003
2003
|
}
|
|
2004
2004
|
/**
|
|
2005
|
-
* Factory function
|
|
2005
|
+
* Factory function, creates a new `MllpServer` with the supplied options.
|
|
2006
2006
|
*
|
|
2007
2007
|
* Prefer `createServer()` over `new MllpServer()` for forward-compatible construction.
|
|
2008
2008
|
*
|
|
@@ -2020,12 +2020,12 @@ declare class MllpServer extends EventEmitter {
|
|
|
2020
2020
|
*/
|
|
2021
2021
|
declare function createServer(opts?: ServerOptions): MllpServer;
|
|
2022
2022
|
/**
|
|
2023
|
-
* Starter factory
|
|
2023
|
+
* Starter factory, creates, configures, and starts an `MllpServer` in one call.
|
|
2024
2024
|
*
|
|
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
|
|
2028
|
+
* NOTE: Stub at Plan 01 scope, Plan 04 fills in the full implementation.
|
|
2029
2029
|
*
|
|
2030
2030
|
* @example
|
|
2031
2031
|
* ```typescript
|
|
@@ -2040,14 +2040,14 @@ declare function createServer(opts?: ServerOptions): MllpServer;
|
|
|
2040
2040
|
declare function createStarterServer(opts: StarterServerOptions): Promise<MllpServer>;
|
|
2041
2041
|
|
|
2042
2042
|
/**
|
|
2043
|
-
* MLLP Client
|
|
2043
|
+
* MLLP Client, `createClient()` factory and `MllpClient` class.
|
|
2044
2044
|
*
|
|
2045
2045
|
* Provides the client-side MLLP transport: connect to an MLLP server over TCP,
|
|
2046
2046
|
* encode and send framed messages, decode inbound ACKs, and surface lifecycle
|
|
2047
2047
|
* events with frozen payloads. Supports `AbortSignal` cancellation on every
|
|
2048
2048
|
* awaitable and `Symbol.asyncDispose` for `await using` ergonomics.
|
|
2049
2049
|
*
|
|
2050
|
-
* Phase 5 PLAN-01 shipped the lifecycle scaffolding
|
|
2050
|
+
* Phase 5 PLAN-01 shipped the lifecycle scaffolding, `connect()`, `close()`,
|
|
2051
2051
|
* `destroy()`, event re-emission. PLAN-02 added `send()` + `MllpTimeoutError`.
|
|
2052
2052
|
* The `correlateByControlId` option (MSH-10 → MSA-2 ACK matching) lights up
|
|
2053
2053
|
* out-of-order ACK handling. Subsequent plans add auto-reconnect (Plan 04),
|
|
@@ -2070,7 +2070,7 @@ declare function createStarterServer(opts: StarterServerOptions): Promise<MllpSe
|
|
|
2070
2070
|
* Context passed to a custom `retryStrategy` hook on each reconnect attempt
|
|
2071
2071
|
* (CLIENT-12, D-15).
|
|
2072
2072
|
*
|
|
2073
|
-
* Frozen via `Object.freeze` before invocation
|
|
2073
|
+
* Frozen via `Object.freeze` before invocation, handlers cannot mutate
|
|
2074
2074
|
* (T-05-04-04 mitigation).
|
|
2075
2075
|
*
|
|
2076
2076
|
* @example
|
|
@@ -2093,7 +2093,7 @@ interface RetryContext {
|
|
|
2093
2093
|
readonly totalElapsedMs: number;
|
|
2094
2094
|
/** Ms since the last successful ACK. `Infinity` if no success seen. */
|
|
2095
2095
|
readonly sinceLastSuccessMs: number;
|
|
2096
|
-
/** CLIENT-18 classification (Composition A
|
|
2096
|
+
/** CLIENT-18 classification (Composition A, D-16). */
|
|
2097
2097
|
readonly classifiedAs: "transient" | "permanent";
|
|
2098
2098
|
/**
|
|
2099
2099
|
* The same `AbortSignal` passed into `connect()`. If no signal was
|
|
@@ -2104,15 +2104,15 @@ interface RetryContext {
|
|
|
2104
2104
|
}
|
|
2105
2105
|
/**
|
|
2106
2106
|
* Custom reconnect-backoff hook (CLIENT-12). Return `null` to halt
|
|
2107
|
-
* reconnection (D-17)
|
|
2107
|
+
* reconnection (D-17), the FSM transitions to `CLOSED`.
|
|
2108
2108
|
*/
|
|
2109
2109
|
type RetryStrategy = (ctx: RetryContext) => number | null;
|
|
2110
2110
|
/**
|
|
2111
2111
|
* Combined count + byte-based queue cap. Stricter-of-two wins (D-23).
|
|
2112
2112
|
*
|
|
2113
|
-
* - `number
|
|
2114
|
-
* - `{ bytes }
|
|
2115
|
-
* - `{ count, bytes }
|
|
2113
|
+
* - `number`, count cap only (default 64).
|
|
2114
|
+
* - `{ bytes }`, byte cap only.
|
|
2115
|
+
* - `{ count, bytes }`, both caps; whichever trips first wins.
|
|
2116
2116
|
*
|
|
2117
2117
|
* @example
|
|
2118
2118
|
* ```typescript
|
|
@@ -2129,7 +2129,7 @@ type HighWaterMark = number | {
|
|
|
2129
2129
|
/**
|
|
2130
2130
|
* Options for {@link createClient} and the {@link MllpClient} constructor.
|
|
2131
2131
|
*
|
|
2132
|
-
* Phase 5 plans extend this incrementally
|
|
2132
|
+
* Phase 5 plans extend this incrementally, new fields are additive and optional.
|
|
2133
2133
|
*
|
|
2134
2134
|
* @example
|
|
2135
2135
|
* ```typescript
|
|
@@ -2147,7 +2147,7 @@ interface ClientOptions {
|
|
|
2147
2147
|
readonly drainTimeoutMs?: number;
|
|
2148
2148
|
/**
|
|
2149
2149
|
* Per-message ACK timeout in milliseconds (CLIENT-04). The clock starts at
|
|
2150
|
-
* the underlying `write()` flush callback, NOT at the `send()` call
|
|
2150
|
+
* the underlying `write()` flush callback, NOT at the `send()` call,
|
|
2151
2151
|
* pre-flush queue time is not charged to the peer. Default: `30_000`.
|
|
2152
2152
|
*/
|
|
2153
2153
|
readonly ackTimeoutMs?: number;
|
|
@@ -2174,7 +2174,7 @@ interface ClientOptions {
|
|
|
2174
2174
|
* `CONNECTED → DISCONNECTED → RECONNECTING → CONNECTING → CONNECTED`
|
|
2175
2175
|
* with exponential backoff per D-19 unless overridden by
|
|
2176
2176
|
* {@link ClientOptions.retryStrategy}. Permanent errors halt and
|
|
2177
|
-
* transition directly to `CLOSED` (Composition A
|
|
2177
|
+
* transition directly to `CLOSED` (Composition A, D-16).
|
|
2178
2178
|
*/
|
|
2179
2179
|
readonly autoReconnect?: boolean;
|
|
2180
2180
|
/**
|
|
@@ -2206,8 +2206,8 @@ interface ClientOptions {
|
|
|
2206
2206
|
/**
|
|
2207
2207
|
* Behavior when the high-water mark is exceeded (CLIENT-07).
|
|
2208
2208
|
*
|
|
2209
|
-
* - `'reject'` (default)
|
|
2210
|
-
* - `'wait'
|
|
2209
|
+
* - `'reject'` (default), `send()` rejects with `MllpBackpressureError`.
|
|
2210
|
+
* - `'wait'`, `send()` awaits the `'drain'` event OR the per-message
|
|
2211
2211
|
* `ackTimeoutMs` OR `signal` abort, whichever fires first (CLIENT-11).
|
|
2212
2212
|
*
|
|
2213
2213
|
* @default 'reject'
|
|
@@ -2216,9 +2216,9 @@ interface ClientOptions {
|
|
|
2216
2216
|
/**
|
|
2217
2217
|
* Strict serialization send → await-ACK → send (CLIENT-19, D-06).
|
|
2218
2218
|
*
|
|
2219
|
-
* - `true` (default)
|
|
2219
|
+
* - `true` (default), concurrent in-flight sends up to
|
|
2220
2220
|
* {@link ClientOptions.highWaterMark}.
|
|
2221
|
-
* - `false
|
|
2221
|
+
* - `false`, collapses the in-flight set to ≤1 (the unified Correlator's
|
|
2222
2222
|
* `maxInFlight=1`); the next `send()` waits for the prior ACK before
|
|
2223
2223
|
* reaching the wire.
|
|
2224
2224
|
*
|
|
@@ -2246,7 +2246,7 @@ interface ClientOptions {
|
|
|
2246
2246
|
readonly deadPeerTimeoutMs?: number;
|
|
2247
2247
|
/**
|
|
2248
2248
|
* Enable TLS (MLLPS) for this connection (Phase 8). `true` enables TLS with
|
|
2249
|
-
* all defaults
|
|
2249
|
+
* all defaults, including certificate verification **on**. Pass a
|
|
2250
2250
|
* {@link TlsOptions} object to customize (`ca`/`cert`/`key`, minimum
|
|
2251
2251
|
* version, ciphers, `allowUnverified`, …).
|
|
2252
2252
|
*
|
|
@@ -2259,13 +2259,13 @@ interface ClientOptions {
|
|
|
2259
2259
|
/**
|
|
2260
2260
|
* Observability snapshot returned by {@link MllpClient.getStats} (OBS-01, D-26).
|
|
2261
2261
|
*
|
|
2262
|
-
* All fields are JSON-serializable (OBS-04)
|
|
2262
|
+
* All fields are JSON-serializable (OBS-04), no Buffers, no class instances,
|
|
2263
2263
|
* no Maps, no circular references. `lastConnectedAt` and `lastAckAt` are
|
|
2264
|
-
* **epoch milliseconds** (numbers), NOT `Date` instances
|
|
2264
|
+
* **epoch milliseconds** (numbers), NOT `Date` instances, log-pipeline
|
|
2265
2265
|
* friendly per D-26.
|
|
2266
2266
|
*
|
|
2267
2267
|
* `warningsByCode` keys are constrained to the public {@link WarningCode}
|
|
2268
|
-
* union
|
|
2268
|
+
* union, adding/removing a code is a breaking change (CLAUDE.md
|
|
2269
2269
|
* stable-codes guardrail enforced at the type boundary, B-05).
|
|
2270
2270
|
*
|
|
2271
2271
|
* @example
|
|
@@ -2276,7 +2276,7 @@ interface ClientOptions {
|
|
|
2276
2276
|
* ```
|
|
2277
2277
|
*/
|
|
2278
2278
|
interface ClientStats {
|
|
2279
|
-
/** Current FSM state
|
|
2279
|
+
/** Current FSM state, mirrors `client.state`. */
|
|
2280
2280
|
readonly state: ConnectionState;
|
|
2281
2281
|
/** Live Connection's id, or `null` before the first connect (or post-CLOSED). */
|
|
2282
2282
|
readonly connectionId: string | null;
|
|
@@ -2284,7 +2284,7 @@ interface ClientStats {
|
|
|
2284
2284
|
readonly queueDepth: number;
|
|
2285
2285
|
/** Sum of `frame.length` across live correlator entries. */
|
|
2286
2286
|
readonly queueBytes: number;
|
|
2287
|
-
/** Entries with `sentAt !== null
|
|
2287
|
+
/** Entries with `sentAt !== null`, actually written to the wire / awaiting ACK. */
|
|
2288
2288
|
readonly inFlight: number;
|
|
2289
2289
|
/**
|
|
2290
2290
|
* Aggregated warning counts. Keys are constrained to the public
|
|
@@ -2312,21 +2312,21 @@ interface ClientStats {
|
|
|
2312
2312
|
readonly tls: boolean;
|
|
2313
2313
|
}
|
|
2314
2314
|
/**
|
|
2315
|
-
* MLLP client
|
|
2315
|
+
* MLLP client, composes a single Phase 3 {@link Connection} over a {@link NetTransport}
|
|
2316
2316
|
* (production) or any other `Transport` (testing via {@link InMemoryTransport}).
|
|
2317
2317
|
*
|
|
2318
|
-
* Public events
|
|
2319
|
-
* - `'stateChange'
|
|
2320
|
-
* - `'connect'
|
|
2321
|
-
* - `'disconnect'
|
|
2322
|
-
* - `'reconnecting'
|
|
2323
|
-
* - `'close'
|
|
2324
|
-
* - `'message'
|
|
2325
|
-
* - `'warning'
|
|
2326
|
-
* - `'securityWarning'
|
|
2318
|
+
* Public events, every payload `Object.freeze`'d before emission (D-25):
|
|
2319
|
+
* - `'stateChange'`, `{ from, to, reason? }` from the underlying Connection FSM
|
|
2320
|
+
* - `'connect'`, `{ connectionId }` once the FSM enters `CONNECTED`
|
|
2321
|
+
* - `'disconnect'`, `{ connectionId }` once the FSM enters `DISCONNECTED`
|
|
2322
|
+
* - `'reconnecting'`, `{ connectionId, attempt?, delayMs? }` (Plan 04 populates)
|
|
2323
|
+
* - `'close'`, `{ connectionId }` once the FSM enters terminal `CLOSED`
|
|
2324
|
+
* - `'message'`, `{ payload, connectionId, byteOffset, warnings }` for every inbound frame
|
|
2325
|
+
* - `'warning'`, `MllpWarning` enriched with `connectionId` from the Connection layer
|
|
2326
|
+
* - `'securityWarning'`, `SecurityWarning` (Phase 8). Emitted on every successful
|
|
2327
2327
|
* `secureConnect` (initial + every reconnect) when `tls.allowUnverified` is `true`
|
|
2328
|
-
*
|
|
2329
|
-
* - `'error'
|
|
2328
|
+
* with code `MLLP_TLS_VERIFY_DISABLED`. Also mirrored to `process.emitWarning`.
|
|
2329
|
+
* - `'error'`, re-emitted from Connection. Guarded by `listenerCount('error') > 0` so
|
|
2330
2330
|
* absence of a listener does NOT crash the process (server precedent).
|
|
2331
2331
|
*
|
|
2332
2332
|
* @example
|
|
@@ -2368,7 +2368,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2368
2368
|
/** 0-indexed attempt counter for the current reconnect cycle. */
|
|
2369
2369
|
private _attempt;
|
|
2370
2370
|
/**
|
|
2371
|
-
* W-02
|
|
2371
|
+
* W-02, total reconnect attempts since construction. Read by PLAN-06 for
|
|
2372
2372
|
* `getStats().reconnectAttempts`. Incremented at the entry of every
|
|
2373
2373
|
* `_handleDisconnect` invocation that proceeds to schedule a backoff.
|
|
2374
2374
|
*/
|
|
@@ -2389,7 +2389,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2389
2389
|
private readonly _onBackpressure;
|
|
2390
2390
|
/** Pipeline flag; default `true` (parallel up to highWaterMark). */
|
|
2391
2391
|
private readonly _pipeline;
|
|
2392
|
-
/** Dead-peer idle timer (Plan 05
|
|
2392
|
+
/** Dead-peer idle timer (Plan 05, D-11). `null` when not armed. */
|
|
2393
2393
|
private _deadPeerTimer;
|
|
2394
2394
|
/** True once the self-'ack' listener that resets the dead-peer timer
|
|
2395
2395
|
* has been attached. Guards against duplicate listeners on reconnect. */
|
|
@@ -2403,7 +2403,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2403
2403
|
/** Listener-removal handle for the abort listener bound in connect(). */
|
|
2404
2404
|
private _abortListener;
|
|
2405
2405
|
/**
|
|
2406
|
-
* Test-only seam
|
|
2406
|
+
* Test-only seam, when set, `_beginReconnectAttempt` builds the new
|
|
2407
2407
|
* Connection through this factory instead of opening a real net.Socket.
|
|
2408
2408
|
*
|
|
2409
2409
|
* @internal
|
|
@@ -2439,9 +2439,9 @@ declare class MllpClient extends EventEmitter {
|
|
|
2439
2439
|
*/
|
|
2440
2440
|
get state(): ConnectionState;
|
|
2441
2441
|
/**
|
|
2442
|
-
* Open a TCP (or TLS
|
|
2442
|
+
* Open a TCP (or TLS, Phase 8) connection to the configured `host:port`
|
|
2443
2443
|
* and attach a Phase 3 {@link Connection} to it. Resolves once the FSM
|
|
2444
|
-
* enters `CONNECTED
|
|
2444
|
+
* enters `CONNECTED`, for TLS, on `'secureConnect'` (handshake complete,
|
|
2445
2445
|
* including certificate verification when it is on).
|
|
2446
2446
|
*
|
|
2447
2447
|
* Rejects with:
|
|
@@ -2461,11 +2461,11 @@ declare class MllpClient extends EventEmitter {
|
|
|
2461
2461
|
*
|
|
2462
2462
|
* **TLS 1.3 + mutual TLS caveat (RFC 8446 §4.4.2):** `connect()` resolving
|
|
2463
2463
|
* does NOT guarantee that a `clientAuth: 'MUST'` server accepted your
|
|
2464
|
-
* client certificate. Under TLS 1.3 the client's handshake
|
|
2465
|
-
* `'secureConnect'
|
|
2464
|
+
* client certificate. Under TLS 1.3 the client's handshake, and its
|
|
2465
|
+
* `'secureConnect'`, can complete before the server finishes validating
|
|
2466
2466
|
* the certificate; a rejection then surfaces moments later as a typed
|
|
2467
2467
|
* post-connect error (`'error'` event with an `ERR_SSL_*`/alert cause,
|
|
2468
|
-
* classified **permanent
|
|
2468
|
+
* classified **permanent**, no auto-reconnect loop). ACK correlation via
|
|
2469
2469
|
* {@link MllpClient.send} remains the delivery guarantee: no send resolves
|
|
2470
2470
|
* without its ACK, so a rejected session can never silently "deliver".
|
|
2471
2471
|
*
|
|
@@ -2484,7 +2484,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2484
2484
|
*
|
|
2485
2485
|
* Plaintext (`ClientOptions.tls` unset): a `net.Socket` wrapped in
|
|
2486
2486
|
* `NetTransport`. TLS (Phase 8): a `tls.TLSSocket` wrapped in
|
|
2487
|
-
* `TlsTransport
|
|
2487
|
+
* `TlsTransport`, verification defaults **on**
|
|
2488
2488
|
* (`rejectUnauthorized: !allowUnverified`), floor `minVersion: 'TLSv1.2'`
|
|
2489
2489
|
* (the IHE ATNA ITI-19 BCP195 floor), `servername` defaulting to
|
|
2490
2490
|
* `ClientOptions.host`.
|
|
@@ -2494,12 +2494,12 @@ declare class MllpClient extends EventEmitter {
|
|
|
2494
2494
|
* Wrap a connect-phase socket error as `MllpConnectionError`, classifying
|
|
2495
2495
|
* TLS failures (Phase 8) into the additive `connectionCause`:
|
|
2496
2496
|
*
|
|
2497
|
-
* - `'tls-verify'
|
|
2497
|
+
* - `'tls-verify'`, certificate-verification failures
|
|
2498
2498
|
* ({@link isTlsVerificationErrorCode}).
|
|
2499
|
-
* - `'tls-handshake'
|
|
2499
|
+
* - `'tls-handshake'`, TLS-**protocol**-shaped failures only
|
|
2500
2500
|
* ({@link isTlsProtocolError}): `ERR_SSL_*`, `EPROTO`, OpenSSL
|
|
2501
2501
|
* alert-bearing errors.
|
|
2502
|
-
* - **No `connectionCause
|
|
2502
|
+
* - **No `connectionCause`**, pure TCP-level failures (`ECONNREFUSED`,
|
|
2503
2503
|
* `ETIMEDOUT`, …) even on a TLS-configured connection; these carry the
|
|
2504
2504
|
* same shape as plaintext connect failures.
|
|
2505
2505
|
*/
|
|
@@ -2507,7 +2507,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2507
2507
|
/**
|
|
2508
2508
|
* Emit a client event, containing a throwing subscriber.
|
|
2509
2509
|
*
|
|
2510
|
-
* Every event this client emits is reached from a callback we do not own
|
|
2510
|
+
* Every event this client emits is reached from a callback we do not own, a socket's
|
|
2511
2511
|
* `'connect'`/`'secureConnect'`/`'data'`/`'error'` listener, or a backoff timer. A throwing
|
|
2512
2512
|
* subscriber would unwind into it and kill the process, and on several paths it would also skip
|
|
2513
2513
|
* the work queued after the emit (the `resolve()` of `connect()`, the backoff scheduling).
|
|
@@ -2517,7 +2517,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2517
2517
|
private _emitContained;
|
|
2518
2518
|
/**
|
|
2519
2519
|
* Emit the per-connection insecure-TLS warning (Phase 8) when
|
|
2520
|
-
* `tls.allowUnverified === true
|
|
2520
|
+
* `tls.allowUnverified === true`, fires on EVERY successful
|
|
2521
2521
|
* `secureConnect`, initial connect and every reconnect. Emits both a frozen
|
|
2522
2522
|
* `'securityWarning'` event and `process.emitWarning`. No-op for plaintext
|
|
2523
2523
|
* connections or when verification is on.
|
|
@@ -2526,7 +2526,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2526
2526
|
/**
|
|
2527
2527
|
* Wire a Connection's events through to this MllpClient. Every re-emitted
|
|
2528
2528
|
* payload is `Object.freeze`'d before emission (D-25), even though the
|
|
2529
|
-
* Connection layer already freezes
|
|
2529
|
+
* Connection layer already freezes, defense-in-depth, harmless on
|
|
2530
2530
|
* already-frozen objects.
|
|
2531
2531
|
*
|
|
2532
2532
|
* Builds the unified `Correlator` (D-03/A1) bound to this Connection and
|
|
@@ -2537,23 +2537,23 @@ declare class MllpClient extends EventEmitter {
|
|
|
2537
2537
|
*/
|
|
2538
2538
|
private _attachConnection;
|
|
2539
2539
|
/**
|
|
2540
|
-
* Arm (or re-arm) the dead-peer idle timer (Plan 05
|
|
2540
|
+
* Arm (or re-arm) the dead-peer idle timer (Plan 05, CLIENT-08, D-11).
|
|
2541
2541
|
* No-op when `deadPeerTimeoutMs` is unset.
|
|
2542
2542
|
*/
|
|
2543
2543
|
private _armDeadPeerTimer;
|
|
2544
2544
|
/**
|
|
2545
|
-
* Clear the dead-peer idle timer (Plan 05
|
|
2545
|
+
* Clear the dead-peer idle timer (Plan 05, D-14 timer cleanup on
|
|
2546
2546
|
* every transition out of CONNECTED).
|
|
2547
2547
|
*/
|
|
2548
2548
|
private _clearDeadPeerTimer;
|
|
2549
2549
|
/**
|
|
2550
|
-
* Disconnect handler
|
|
2550
|
+
* Disconnect handler, Plan 04 reconnect FSM core.
|
|
2551
2551
|
*
|
|
2552
2552
|
* Implements:
|
|
2553
2553
|
* - CLIENT-17 hybrid in-flight handling (D-08): controlId mode preserves
|
|
2554
2554
|
* pending sends for resend; FIFO mode rejects in-flight with
|
|
2555
2555
|
* `connectionCause: 'in-flight-orphan'` and queued with `'fifo-unsafe'`.
|
|
2556
|
-
* - CLIENT-18 classifier-first (Composition A
|
|
2556
|
+
* - CLIENT-18 classifier-first (Composition A, D-16): permanent errors
|
|
2557
2557
|
* transition straight to CLOSED without invoking `retryStrategy`.
|
|
2558
2558
|
* - W-01 backoff-reset on recent success: first disconnect after a
|
|
2559
2559
|
* successful ACK on the prior session resets `_attempt` to 0.
|
|
@@ -2561,7 +2561,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2561
2561
|
* - D-15 frozen RetryContext + D-17 null-return halts.
|
|
2562
2562
|
* - D-19 default exponential strategy.
|
|
2563
2563
|
*
|
|
2564
|
-
* Invoked from the SINGLE `_onStateChange` hook
|
|
2564
|
+
* Invoked from the SINGLE `_onStateChange` hook, no parallel listener
|
|
2565
2565
|
* (B-04). Idempotent across same-cycle re-entry: cycle-start flag
|
|
2566
2566
|
* coordinates first-disconnect vs subsequent-within-cycle behavior.
|
|
2567
2567
|
*/
|
|
@@ -2594,7 +2594,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2594
2594
|
*/
|
|
2595
2595
|
private _afterReconnectArmed;
|
|
2596
2596
|
/**
|
|
2597
|
-
* Test seam
|
|
2597
|
+
* Test seam, install a factory that produces the next reconnect Connection.
|
|
2598
2598
|
*
|
|
2599
2599
|
* @internal
|
|
2600
2600
|
*/
|
|
@@ -2603,7 +2603,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2603
2603
|
arm: () => void;
|
|
2604
2604
|
}): void;
|
|
2605
2605
|
/**
|
|
2606
|
-
* Test seam
|
|
2606
|
+
* Test seam, capture or rebind the connect-signal mid-flight (W-07).
|
|
2607
2607
|
*
|
|
2608
2608
|
* @internal
|
|
2609
2609
|
*/
|
|
@@ -2617,7 +2617,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2617
2617
|
* anchor and passes it to `matchAck` for keyed lookup.
|
|
2618
2618
|
*
|
|
2619
2619
|
* Called from the SINGLE `'message'` listener registered in `_attachConnection`.
|
|
2620
|
-
* No parallel listener is registered
|
|
2620
|
+
* No parallel listener is registered, downstream plans extend at the named
|
|
2621
2621
|
* anchor (B-04).
|
|
2622
2622
|
*/
|
|
2623
2623
|
private _onAckPayload;
|
|
@@ -2634,9 +2634,9 @@ declare class MllpClient extends EventEmitter {
|
|
|
2634
2634
|
private _onAckMatched;
|
|
2635
2635
|
/**
|
|
2636
2636
|
* Emit a frozen `'drain'` event when the queue depth and bytes fall below
|
|
2637
|
-
* both configured caps (Plan 05
|
|
2637
|
+
* both configured caps (Plan 05, D-24). Called from `_onAckMatched`
|
|
2638
2638
|
* (every successful ACK) and from the Correlator's `onTimeout` callback
|
|
2639
|
-
* (every expired send)
|
|
2639
|
+
* (every expired send), both code paths free a live-store slot.
|
|
2640
2640
|
*/
|
|
2641
2641
|
private _maybeEmitDrain;
|
|
2642
2642
|
/**
|
|
@@ -2672,14 +2672,14 @@ declare class MllpClient extends EventEmitter {
|
|
|
2672
2672
|
* ```
|
|
2673
2673
|
*
|
|
2674
2674
|
* @param payload Raw bytes; MLLP framing is added internally via `encodeFrame`.
|
|
2675
|
-
* @param opts.signal AbortSignal
|
|
2675
|
+
* @param opts.signal AbortSignal, aborting cancels the ACK wait (CLIENT-11).
|
|
2676
2676
|
*/
|
|
2677
2677
|
send(payload: Buffer, opts?: {
|
|
2678
2678
|
signal?: AbortSignal;
|
|
2679
2679
|
ackTimeoutMs?: number;
|
|
2680
2680
|
}): Promise<Buffer>;
|
|
2681
2681
|
/**
|
|
2682
|
-
* 'wait'-mode backpressure handler (Plan 05
|
|
2682
|
+
* 'wait'-mode backpressure handler (Plan 05, CLIENT-07/CLIENT-11).
|
|
2683
2683
|
*
|
|
2684
2684
|
* Awaits one of three terminating signals, in order:
|
|
2685
2685
|
* - `'drain'` event → re-enter `send()` (the gate will now pass).
|
|
@@ -2695,7 +2695,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2695
2695
|
*/
|
|
2696
2696
|
private _teardownCorrelator;
|
|
2697
2697
|
/**
|
|
2698
|
-
* **Test seam
|
|
2698
|
+
* **Test seam**, attach an externally-built {@link Connection} directly,
|
|
2699
2699
|
* bypassing the `net.createConnection` + `NetTransport` path. Used by
|
|
2700
2700
|
* lifecycle tests driving `InMemoryTransport.pair()` for determinism.
|
|
2701
2701
|
*
|
|
@@ -2722,7 +2722,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2722
2722
|
signal?: AbortSignal;
|
|
2723
2723
|
}): Promise<void>;
|
|
2724
2724
|
/**
|
|
2725
|
-
* Abruptly destroy the client
|
|
2725
|
+
* Abruptly destroy the client, force-transitions the underlying Connection
|
|
2726
2726
|
* to `CLOSED` immediately. No-op if no Connection is attached. Idempotent.
|
|
2727
2727
|
*
|
|
2728
2728
|
* @example
|
|
@@ -2734,7 +2734,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2734
2734
|
/**
|
|
2735
2735
|
* Returns a JSON-serializable observability snapshot (OBS-01, D-26).
|
|
2736
2736
|
*
|
|
2737
|
-
* All fields are plain values
|
|
2737
|
+
* All fields are plain values, no Buffers, no class instances, no Maps,
|
|
2738
2738
|
* no circular refs. Safe to `JSON.stringify` directly.
|
|
2739
2739
|
*
|
|
2740
2740
|
* `inFlight` is the count of correlator entries with `sentAt !== null`
|
|
@@ -2749,7 +2749,7 @@ declare class MllpClient extends EventEmitter {
|
|
|
2749
2749
|
*/
|
|
2750
2750
|
getStats(): ClientStats;
|
|
2751
2751
|
/**
|
|
2752
|
-
* Async disposal
|
|
2752
|
+
* Async disposal, delegates to {@link MllpClient.close} for `await using` support.
|
|
2753
2753
|
*
|
|
2754
2754
|
* @example
|
|
2755
2755
|
* ```typescript
|
|
@@ -2835,20 +2835,20 @@ interface StarterClientOptions {
|
|
|
2835
2835
|
}
|
|
2836
2836
|
/**
|
|
2837
2837
|
* Three-line MLLP client with batteries-included defaults (PLAN-06,
|
|
2838
|
-
* CLIENT-10, D-22). The returned client is already CONNECTED
|
|
2838
|
+
* CLIENT-10, D-22). The returned client is already CONNECTED, `connect()`
|
|
2839
2839
|
* has been awaited.
|
|
2840
2840
|
*
|
|
2841
2841
|
* D-22 defaults:
|
|
2842
2842
|
* - `autoReconnect: true`
|
|
2843
2843
|
* - `ackTimeoutMs: 30_000`
|
|
2844
|
-
* - `correlateByControlId: false` (FIFO mode
|
|
2844
|
+
* - `correlateByControlId: false` (FIFO mode, simplest mental model)
|
|
2845
2845
|
* - `pipeline: true`
|
|
2846
2846
|
* - `highWaterMark: 64`
|
|
2847
2847
|
* - `onBackpressure: 'reject'`
|
|
2848
2848
|
* - `handleSignals: false` (opt-in)
|
|
2849
2849
|
*
|
|
2850
2850
|
* The factory is **async**, so the literal three-line north-star snippet
|
|
2851
|
-
* has an explicit `await` BEFORE `createStarterClient(...)
|
|
2851
|
+
* has an explicit `await` BEFORE `createStarterClient(...)`, without it,
|
|
2852
2852
|
* the `using` declaration would receive a `Promise`, not an `MllpClient`,
|
|
2853
2853
|
* and `Symbol.asyncDispose` would not run at scope exit.
|
|
2854
2854
|
*
|
|
@@ -2865,9 +2865,9 @@ declare function createStarterClient(opts: StarterClientOptions): Promise<MllpCl
|
|
|
2865
2865
|
* MLLP Client typed errors and classifiers.
|
|
2866
2866
|
*
|
|
2867
2867
|
* Exports:
|
|
2868
|
-
* - `MllpTimeoutError` (PLAN-02)
|
|
2869
|
-
* - `MllpBackpressureError
|
|
2870
|
-
* - `isTransientConnectionError` (PLAN-04)
|
|
2868
|
+
* - `MllpTimeoutError` (PLAN-02), ACK timeout (ERR-02)
|
|
2869
|
+
* - `MllpBackpressureError`, high-water mark exceeded (ERR-04, plan 05)
|
|
2870
|
+
* - `isTransientConnectionError` (PLAN-04), transient/permanent classifier (CLIENT-18)
|
|
2871
2871
|
*
|
|
2872
2872
|
* Re-exported from `src/client/index.ts` and `src/index.ts`.
|
|
2873
2873
|
*
|
|
@@ -2878,7 +2878,7 @@ declare function createStarterClient(opts: StarterClientOptions): Promise<MllpCl
|
|
|
2878
2878
|
* the configured `ackTimeoutMs` (ERR-02).
|
|
2879
2879
|
*
|
|
2880
2880
|
* The timeout clock starts at the underlying `write()` flush callback, NOT
|
|
2881
|
-
* at the `send()` call
|
|
2881
|
+
* at the `send()` call, pre-flush queue time is not charged to the peer
|
|
2882
2882
|
* (CLIENT-04).
|
|
2883
2883
|
*
|
|
2884
2884
|
* @example
|
|
@@ -2914,7 +2914,7 @@ declare class MllpTimeoutError extends Error {
|
|
|
2914
2914
|
}
|
|
2915
2915
|
/**
|
|
2916
2916
|
* Set of Node/OpenSSL error codes that indicate a **certificate-verification**
|
|
2917
|
-
* failure (as opposed to some other TLS handshake failure)
|
|
2917
|
+
* failure (as opposed to some other TLS handshake failure), untrusted chain,
|
|
2918
2918
|
* expired/not-yet-valid certificate, hostname mismatch, revocation, etc.
|
|
2919
2919
|
*
|
|
2920
2920
|
* Used by `MllpClient` (Phase 8) to classify a TLS connect failure's
|
|
@@ -2932,7 +2932,7 @@ declare class MllpTimeoutError extends Error {
|
|
|
2932
2932
|
*/
|
|
2933
2933
|
declare function isTlsVerificationErrorCode(code: string): boolean;
|
|
2934
2934
|
/**
|
|
2935
|
-
* Detects **TLS-protocol-shaped** errors
|
|
2935
|
+
* Detects **TLS-protocol-shaped** errors, failures of the TLS protocol
|
|
2936
2936
|
* itself, as opposed to plain TCP-level network failures (Phase 8).
|
|
2937
2937
|
*
|
|
2938
2938
|
* Apply this only to errors raised on a **TLS** connection; `MllpClient`
|
|
@@ -2941,12 +2941,12 @@ declare function isTlsVerificationErrorCode(code: string): boolean;
|
|
|
2941
2941
|
*
|
|
2942
2942
|
* **TLS-protocol-shaped (`true`):**
|
|
2943
2943
|
* - `code` starting `ERR_SSL_` (Node's TLS alert codes, e.g.
|
|
2944
|
-
* `ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED
|
|
2944
|
+
* `ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED`, a `clientAuth: 'MUST'`
|
|
2945
2945
|
* server rejecting the client's certificate).
|
|
2946
|
-
* - `code === 'EPROTO'
|
|
2946
|
+
* - `code === 'EPROTO'`, on a TLS connection this is OpenSSL failing the
|
|
2947
2947
|
* handshake (protocol version mismatch, no shared cipher, a TLS ≤1.2
|
|
2948
2948
|
* mTLS rejection).
|
|
2949
|
-
* - `message` containing `ssl` or `alert` (`/\bssl\b|\balert\b/i
|
|
2949
|
+
* - `message` containing `ssl` or `alert` (`/\bssl\b|\balert\b/i`, "SSL
|
|
2950
2950
|
* routines", "tlsv13 alert certificate required", …). This message check
|
|
2951
2951
|
* is a **heuristic backstop** over the code-based checks above, not a
|
|
2952
2952
|
* precise boundary: it exists to catch OpenSSL errors that surface without
|
|
@@ -2954,19 +2954,19 @@ declare function isTlsVerificationErrorCode(code: string): boolean;
|
|
|
2954
2954
|
* TLS is configured. An arbitrary non-TLS error whose message happens to
|
|
2955
2955
|
* contain those words would also match.
|
|
2956
2956
|
*
|
|
2957
|
-
* **NOT TLS-protocol-shaped (`false`)
|
|
2957
|
+
* **NOT TLS-protocol-shaped (`false`), plain network failures, which stay
|
|
2958
2958
|
* transient for the reconnect classifier:** `ECONNREFUSED`, `ETIMEDOUT`,
|
|
2959
2959
|
* `EHOSTUNREACH`, `ENETUNREACH`, `EPIPE`, and a plain `ECONNRESET` carrying
|
|
2960
|
-
* no TLS alert context
|
|
2960
|
+
* no TLS alert context, a network blip during (or after) a handshake
|
|
2961
2961
|
* should still auto-heal.
|
|
2962
2962
|
*
|
|
2963
|
-
* Certificate-**verification** failures are a separate class
|
|
2963
|
+
* Certificate-**verification** failures are a separate class, see
|
|
2964
2964
|
* {@link isTlsVerificationErrorCode}; `MllpClient` checks that first and
|
|
2965
2965
|
* labels those `connectionCause: 'tls-verify'`.
|
|
2966
2966
|
*
|
|
2967
2967
|
* Why this matters: under TLS 1.3 (RFC 8446 §4.4.2) a `clientAuth: 'MUST'`
|
|
2968
2968
|
* server can reject the client's certificate AFTER the client's own
|
|
2969
|
-
* `'secureConnect'
|
|
2969
|
+
* `'secureConnect'`, the rejection then surfaces as a post-connect socket
|
|
2970
2970
|
* error. A misconfigured mTLS client must never auto-reconnect-loop against
|
|
2971
2971
|
* a server that will always reject it, so `MllpClient` classifies
|
|
2972
2972
|
* TLS-protocol-shaped errors as **permanent**.
|
|
@@ -2976,7 +2976,7 @@ declare function isTlsVerificationErrorCode(code: string): boolean;
|
|
|
2976
2976
|
* import { isTlsProtocolError, MllpConnectionError } from '@cosyte/mllp';
|
|
2977
2977
|
* client.on('error', ({ error }) => {
|
|
2978
2978
|
* if (error instanceof MllpConnectionError && isTlsProtocolError(error.cause)) {
|
|
2979
|
-
* // TLS protocol failure
|
|
2979
|
+
* // TLS protocol failure, a configuration problem, not a network blip.
|
|
2980
2980
|
* }
|
|
2981
2981
|
* });
|
|
2982
2982
|
* ```
|
|
@@ -2987,7 +2987,7 @@ declare function isTlsProtocolError(err: unknown): boolean;
|
|
|
2987
2987
|
* or permanent (halts auto-reconnect, transitions to CLOSED).
|
|
2988
2988
|
*
|
|
2989
2989
|
* Used internally by `MllpClient` BEFORE invoking `retryStrategy` (Composition A
|
|
2990
|
-
*
|
|
2990
|
+
* see `RetryContext.classifiedAs`). Re-exported so callers can implement
|
|
2991
2991
|
* their own retry policies.
|
|
2992
2992
|
*
|
|
2993
2993
|
* Classification table (CLIENT-18, D-16):
|
|
@@ -2996,22 +2996,22 @@ declare function isTlsProtocolError(err: unknown): boolean;
|
|
|
2996
2996
|
* `ENETUNREACH`, `EPIPE` → **transient** (`true`)
|
|
2997
2997
|
* - `CERT_*` and `UNABLE_TO_VERIFY_LEAF_SIGNATURE` /
|
|
2998
2998
|
* `DEPTH_ZERO_SELF_SIGNED_CERT` / `SELF_SIGNED_CERT_IN_CHAIN`
|
|
2999
|
-
* (any {@link isTlsVerificationErrorCode} code) → **permanent** (`false`)
|
|
2999
|
+
* (any {@link isTlsVerificationErrorCode} code) → **permanent** (`false`),
|
|
3000
3000
|
* never auto-reconnect-loop into a misconfigured or MITM'd endpoint (Phase 8).
|
|
3001
|
-
* - `ERR_SSL_*` (Node TLS alert codes) → **permanent** (`false`)
|
|
3001
|
+
* - `ERR_SSL_*` (Node TLS alert codes) → **permanent** (`false`), a TLS
|
|
3002
3002
|
* protocol failure such as a `clientAuth: 'MUST'` server rejecting the
|
|
3003
3003
|
* client certificate recurs on every attempt (Phase 8). On TLS-configured
|
|
3004
3004
|
* connections `MllpClient` additionally consults {@link isTlsProtocolError},
|
|
3005
3005
|
* which also catches `EPROTO`/alert-bearing OpenSSL errors that this
|
|
3006
3006
|
* generic classifier (which cannot know the connection was TLS) leaves
|
|
3007
3007
|
* transient.
|
|
3008
|
-
* - `MLLP_*` (any {@link MllpFramingError} code
|
|
3008
|
+
* - `MLLP_*` (any {@link MllpFramingError} code, a fatal decoder throw, surfaced with
|
|
3009
3009
|
* `connectionCause: 'framing-fatal'`) → **permanent** (`false`) (MLLP-10). The peer is not
|
|
3010
|
-
* speaking MLLP
|
|
3010
|
+
* speaking MLLP, an HTTP probe, a health check, a wrong-port misconfiguration, or is emitting
|
|
3011
3011
|
* frames past `maxFrameSizeBytes`. Every reconnect meets the same bytes, so retrying is an
|
|
3012
3012
|
* unbounded storm against a peer that is already misconfigured. If a peer's quirk is *expected*,
|
|
3013
|
-
* the decoder's tolerance opt-ins are the supported answer
|
|
3014
|
-
* - non-Error / unknown / no-code → **transient** (`true`)
|
|
3013
|
+
* the decoder's tolerance opt-ins are the supported answer, they make it a warning, not a fatal.
|
|
3014
|
+
* - non-Error / unknown / no-code → **transient** (`true`), Postel's Law
|
|
3015
3015
|
* default. Reconnect attempts are bounded by `retryStrategy` and the
|
|
3016
3016
|
* 30s backoff cap, so the default is safe.
|
|
3017
3017
|
*
|
|
@@ -3033,7 +3033,7 @@ declare function isTransientConnectionError(err: unknown): boolean;
|
|
|
3033
3033
|
* the configured high-water mark and `onBackpressure: 'reject'` is set
|
|
3034
3034
|
* (CLIENT-07, ERR-04).
|
|
3035
3035
|
*
|
|
3036
|
-
* `highWaterMark` accepts a count cap, a byte cap, or both
|
|
3036
|
+
* `highWaterMark` accepts a count cap, a byte cap, or both, when both are
|
|
3037
3037
|
* present, the stricter-of-two trigger wins (D-23).
|
|
3038
3038
|
*
|
|
3039
3039
|
* @example
|
|
@@ -3079,7 +3079,7 @@ declare class MllpBackpressureError extends Error {
|
|
|
3079
3079
|
}
|
|
3080
3080
|
|
|
3081
3081
|
/**
|
|
3082
|
-
* `@cosyte/mllp
|
|
3082
|
+
* `@cosyte/mllp`, Production-grade MLLP client and server for Node.js.
|
|
3083
3083
|
*
|
|
3084
3084
|
* Transport-only sibling to `@cosyte/hl7`. Handles framing, ACKs, reconnects,
|
|
3085
3085
|
* backpressure, and TLS without requiring knowledge of the MLLP spec.
|
|
@@ -3096,7 +3096,7 @@ declare class MllpBackpressureError extends Error {
|
|
|
3096
3096
|
* Package version marker exported from the `@cosyte/mllp` root.
|
|
3097
3097
|
*
|
|
3098
3098
|
* Kept in lockstep with `package.json` by `scripts/sync-version.mjs`, which the `version` script
|
|
3099
|
-
* runs immediately after `changeset version`. The `: string` annotation is deliberate
|
|
3099
|
+
* runs immediately after `changeset version`. The `: string` annotation is deliberate, without it
|
|
3100
3100
|
* TypeScript infers the *literal* type (`declare const VERSION = "0.0.0"`), which leaks the current
|
|
3101
3101
|
* release into consumers' types and makes an equality check against any other version a compile
|
|
3102
3102
|
* error.
|