Skip to main content
Version: v0.0.3

Class: MllpClient

MLLP client, composes a single Connection over a NetTransport (production) or any other Transport (testing via InMemoryTransport).

Public events, every payload Object.freeze'd before emission:

  • 'stateChange', { from, to, reason? } from the underlying Connection FSM
  • 'connect', { connectionId } once the FSM enters CONNECTED
  • 'disconnect', { connectionId } once the FSM enters DISCONNECTED
  • 'reconnecting', { connectionId, attempt?, delayMs? }
  • 'close', { connectionId } once the FSM enters terminal CLOSED
  • 'message', { payload, connectionId, byteOffset, warnings } for every inbound frame
  • 'warning', MllpWarning enriched with connectionId from the Connection layer
  • 'securityWarning', SecurityWarning. Emitted on every successful secureConnect (initial + every reconnect) when tls.allowUnverified is true with code MLLP_TLS_VERIFY_DISABLED. Also mirrored to process.emitWarning.
  • 'error', re-emitted from Connection. Guarded by listenerCount('error') > 0 so absence of a listener does NOT crash the process (server precedent).

Example

const client = createClient({ host: 'localhost', port: 2575 });
client.on('stateChange', ({ from, to }) => logger.info({ from, to }));
client.on('message', ({ payload }) => logger.info({ bytes: payload.length }));
await client.connect();
const ack = await client.send(payloadBuffer);
await client.close();

Extends

  • EventEmitter

Constructors

Constructor

new MllpClient(opts): MllpClient

Construct an MLLP client. Created idle; call connect() (or use createClient/createStarterClient) to open the connection.

Parameters

opts

ClientOptions

Client options (host/port, ACK timeout, reconnect/backpressure policy, …).

Returns

MllpClient

Overrides

EventEmitter.constructor

Accessors

state

Get Signature

get state(): ConnectionState

Current FSM state. Mirrors the underlying Connection's state once attached; before connect() (or after a CLOSED Connection is dropped) reports the client-level baseline ('DISCONNECTED').

Returns

ConnectionState

Methods

_attachExistingConnection()

_attachExistingConnection(conn): void

Internal

Test seam, attach an externally-built Connection directly, bypassing the net.createConnection + NetTransport path. Used by lifecycle tests driving InMemoryTransport.pair() for determinism.

Parameters

conn

Connection

Returns

void


_captureConnectSignal()

_captureConnectSignal(signal): void

Internal

Test seam, capture or rebind the connect-signal mid-flight.

Parameters

signal

AbortSignal

Returns

void


_setReconnectFactory()

_setReconnectFactory(factory): void

Internal

Test seam, install a factory that produces the next reconnect Connection.

Parameters

factory

() => object

Returns

void


[asyncDispose]()

[asyncDispose](): Promise<void>

Async disposal, delegates to MllpClient.close for await using support.

Returns

Promise<void>

Example

await using client = createClient({ host: 'localhost', port: 2575 });
await client.connect();
// client.close() is called automatically at end of block

close()

close(opts?): Promise<void>

Gracefully close the client.

Delegates to Connection.close, which transitions CONNECTED → DRAINING → DISCONNECTED (or CLOSED on drain timeout). No-op if no Connection is attached.

Rejects with DOMException('Aborted', 'AbortError') if signal aborts mid-drain; on abort, the underlying Connection is force-destroyed.

Parameters

opts?
drainTimeoutMs?

number

signal?

AbortSignal

Returns

Promise<void>

Example

await client.close({ drainTimeoutMs: 5_000 });

connect()

connect(opts?): Promise<void>

Open a TCP (or TLS) connection to the configured host:port and attach a Connection to it. Resolves once the FSM enters CONNECTED, for TLS, on 'secureConnect' (handshake complete, including certificate verification when it is on).

Rejects with:

  • DOMException('Aborted', 'AbortError') if signal is provided and aborts before the connect resolves.
  • MllpConnectionError({ phase: 'connect' }) if the underlying socket emits error before connecting, OR if the client is already connecting/connected. TLS failures carry a connectionCause: 'tls-verify' for certificate verification failures, 'tls-handshake' for TLS-protocol-shaped failures (isTlsProtocolError); pure TCP failures carry none.

Dual failure signal on initial connect: the Connection's transport error handler is attached before this promise's own error listener, so a pre-connect socket error produces BOTH the promise rejection AND a client 'error' event (when an 'error' listener is attached). Handle whichever fits your flow; they describe the same underlying failure.

TLS 1.3 + mutual TLS caveat (RFC 8446 §4.4.2): connect() resolving does NOT guarantee that a clientAuth: 'MUST' server accepted your client certificate. Under TLS 1.3 the client's handshake, and its 'secureConnect', can complete before the server finishes validating the certificate; a rejection then surfaces moments later as a typed post-connect error ('error' event with an ERR_SSL_*/alert cause, classified permanent, no auto-reconnect loop). ACK correlation via MllpClient.send remains the delivery guarantee: no send resolves without its ACK, so a rejected session can never silently "deliver".

Parameters

opts?
signal?

AbortSignal

Returns

Promise<void>

Example

const ac = new AbortController();
setTimeout(() => ac.abort(), 5_000);
await client.connect({ signal: ac.signal });

destroy()

destroy(reason?): void

Abruptly destroy the client, force-transitions the underlying Connection to CLOSED immediately. No-op if no Connection is attached. Idempotent.

Parameters

reason?

Error

Returns

void

Example

client.destroy(new Error('shutting down'));

getStats()

getStats(): ClientStats

Returns a JSON-serializable observability snapshot.

All fields are plain values, no Buffers, no class instances, no Maps, no circular refs. Safe to JSON.stringify directly.

inFlight is the count of correlator entries with sentAt !== null (entries actually written to the wire and awaiting ACK), distinct from queueDepth which counts ALL live correlator entries (including pre-flush and serialization-queued sends).

Returns

ClientStats

Example

setInterval(() => logger.info(JSON.stringify(client.getStats())), 60_000);

send()

send(payload, opts?): Promise<Buffer<ArrayBufferLike>>

Send an MLLP-framed payload and await the inbound ACK.

Resolves with the ACK Buffer (framing stripped). Rejects with:

  • DOMException('Aborted', 'AbortError') if signal aborts before the ACK.
  • MllpTimeoutError if no ACK arrives within ackTimeoutMs. The clock starts at the underlying write() flush callback, NOT at the send() call.
  • MllpConnectionError({ phase: 'send' }) if the client is not connected.

Emits a frozen 'ack' event on every successful match.

Parameters

payload

Buffer

Raw bytes; MLLP framing is added internally via encodeFrame.

opts?
ackTimeoutMs?

number

signal?

AbortSignal

AbortSignal, aborting cancels the ACK wait.

Returns

Promise<Buffer<ArrayBufferLike>>

Example

const ack = await client.send(payloadBuffer);
logger.info({ ack: ack.toString('utf8') });