Inbound messages API

The read side of the typed message layer: a parsed message re-keyed by field name, its session envelope split off, and the guards that narrow it — to one MsgType, or to the union a switch dispatches on. Generated from @boarteam/fix 0.6.1 as installed; the doc text is the library’s own. Guide: Reading inbound messages.

toInboundfunctionsince 0.6.0

function toInbound<B extends object = any>(parsed: ParsedMessage, dict: Dictionary | DictionaryJSON): InboundMessage<B>

Source: packages/fix/src/inbound.ts:220

Convert a ParsedMessage into a typed, name-keyed InboundMessage.

Purely dictionary-driven and total — it never throws and never re-reads the wire. Each top-level field is resolved to its dictionary name and routed by Dictionary.envelopeTags: header/trailer tags to the envelope, everything else to the body. Repeating groups become arrays of entry objects under their counter's name, recursively, so a nested group inside an entry reads the same way as one at the top.

A tag the dictionary does not know is not put in the body. It has no name to key by, and inventing one ("9999") would make the body un-renderable. It stays reachable on InboundMessage.parsed, and parse has already reported it as parse/unknown-tag.

The body type B is a claim about the message, not a check: pass a generated body type when the MsgType is known at the call site, or leave the default and narrow with a guard from inboundTypeGuard (see InboundBody for why the default is any). Nothing here validates the claim — that is validate's job, against the ParsedMessage.

ParameterTypeDescription
parsedParsedMessageThe structured message from parse.
dictDictionary | DictionaryJSONThe dictionary it was parsed against (a DictionaryJSON is loaded for you).

Returns InboundMessage<B>A read-only, typed view over the same message.

Example
import { inboundTypeGuard, loadDictionary, parse, toInbound } from '@boarteam/fix';import { dictionary as fix44 } from '@boarteam/fix-dict-fix44';const dictionary = loadDictionary(fix44);const isInboundType = inboundTypeGuard();const raw =  '8=FIX.4.4|9=100|35=W|49=VENUE|56=ME|34=3|52=20260817-09:30:01|262=req-1|55=EURUSD|268=1|269=0|270=1.101|271=1000000|10=192|';const { message } = parse(raw, dictionary, { soh: '|' });const inbound = toInbound(message, dictionary);if (isInboundType(inbound, 'W')) {  // Body fields by name, groups as entry arrays, envelope on the side.  console.log(inbound.get('Symbol'), inbound.get('NoMDEntries').length, inbound.envelope.MsgSeqNum);  // → EURUSD 1 3}

The // → lines are asserted outputs: the library's doctest executes this exact block against the built packages and fails the build when it prints anything else.

InboundMessageinterfacesince 0.6.0

interface InboundMessage<B extends object>

Source: packages/fix/src/inbound.ts:135

A received message as a typed read model: the MessageView read surface over a name-keyed body, plus the session envelope and the parsed original.

The body carries the message's own fields keyed by name, with the envelope split off into envelope and group counters keyed to arrays of entry objects — the shape a generated <Msg>Body type describes, so inboundTypeGuard can narrow an unnarrowed inbound message to InboundMessage<MarketDataSnapshotFullRefreshBody> and every get() after it is typed. It holds what the wire actually carried: an optional field that did not arrive is absent, and a field that does not belong to this message (which parse reports as parse/tag-not-in-message) is present but untyped.

Being a MessageView it also renders: inbound.render(envelope) re-emits the body with a fresh envelope, which is the useful shape for a proxy that re-signs what it forwards. For a byte-exact echo of what arrived, go through toEncodeMessage(inbound.parsed) instead — see the module note on raw vs value.

  • readonly envelope: InboundEnvelope

    The standard header/trailer fields, by name. See InboundEnvelope.

  • readonly parsed: ParsedMessage

    The ParsedMessage this view was built from — the tag-keyed original, still the source of truth for re-encoding and the only place a tag unknown to the dictionary survives (such a tag has no name to key a body by, and parse already reported it as parse/unknown-tag).

InboundMessageOfinterfacesince 0.6.0

interface InboundMessageOf<B extends object, M extends string>

Source: packages/fix/src/inbound.ts:156

An InboundMessage whose MsgType is pinned to the literal M rather than a bare string — the member shape of InboundUnion, and what a narrowing guard produces.

The pin is what makes msgType usable as a discriminant: switch narrows a union by a literal-typed property, and InboundMessage alone declares msgType: string, which discriminates nothing. Pinning also means a narrowed message reports the truth — after a guard for 'W', msgType really is 'W', not string.

  • readonly msgType: M

    The MsgType (tag 35) value, as a literal type.

InboundEnvelopeinterfacesince 0.6.0

interface InboundEnvelope

Source: packages/fix/src/inbound.ts:75

The session envelope of a received message — the standard header and trailer fields, keyed by name and carrying their coerced values.

This is the half of a message the generated MessageBodies types deliberately exclude: on the way out those fields belong to the session layer and are supplied to MessageView.render, so a body type never mentions them. On the way IN they are simply present in the bytes, and reading MsgSeqNum off a received message is the most ordinary thing a session does — hence a typed home for them here rather than a detour back through the tag-keyed ParsedMessage.

Which fields land here is decided by the dictionary's header/trailer components (see Dictionary.envelopeTags), not by this list: the named properties below are the ones stable across FIX 4.x and FIXT, typed for convenience, and the index signature carries everything else the dictionary's header defines (OnBehalfOfCompID, NoHops entries' fields, ApplVerID, …).

  • readonly BeginString?: string

    BeginString (8) — the dialect the frame declared.

  • readonly BodyLength?: number

    BodyLength (9), as declared on the wire.

  • readonly MsgType?: string

    MsgType (35). Also available unconditionally as MessageView.msgType.

  • readonly MsgSeqNum?: number

    MsgSeqNum (34) — the sequence number this message arrived with.

  • readonly SenderCompID?: string

    SenderCompID (49) — the counterparty that sent it.

  • readonly TargetCompID?: string

    TargetCompID (56) — us, on a well-addressed message.

  • readonly SenderSubID?: string

    SenderSubID (50).

  • readonly TargetSubID?: string

    TargetSubID (57).

  • readonly SendingTime?: string

    SendingTime (52), verbatim (YYYYMMDD-HH:MM:SS[.sss]) — never coerced to a Date.

  • readonly OrigSendingTime?: string

    OrigSendingTime (122) on a resend, verbatim.

  • readonly PossDupFlag?: boolean | string

    PossDupFlag (43) — set when the counterparty flagged a possible duplicate.

    boolean | string because the answer depends on the dictionary, not on this field: an enumerated value is decoded opaquely (kept as 'Y'/'N') so leading-zero and multi-character codes survive, and the standard dictionaries do enumerate tag 43. A dictionary that types it as a bare Boolean yields true/false. Compare against both, or read ParsedField.raw off InboundMessage.parsed.

  • readonly PossResend?: boolean | string

    PossResend (97). Enumerated like PossDupFlag — see the note there.

  • readonly CheckSum?: string

    CheckSum (10), verbatim — three digits, leading zeros intact.

  • readonly [name: string]: DecodedValue | readonly InboundBody[] | undefined

    Any other header/trailer field the dictionary defines, by name — including a group the header itself declares (FIX 4.4's NoHops), whose entries read the same way as a body group's.

InboundBodyinterfacesince 0.6.0

interface InboundBody

Source: packages/fix/src/inbound.ts:53

The loose body shape of a received message — the read-side counterpart of UntypedBody. A repeating-group entry has the same shape, so it is also what an untyped entry array holds.

It is NOT UntypedBody: that type describes what a caller may write (FieldValuestring | number | boolean, never absent), whereas a value read off the wire is a DecodedValue (adding string[] for MultipleValueString) and an optional field is genuinely absent.

It is also not the default body of toInbound, which is any — pass this explicitly (toInbound<InboundBody>(…)) when you mean to read a message whose type you will not narrow. The default cannot be this type, or any other loose one: a generated body is an interface, and TypeScript never gives an interface an implicit index signature, so no index-signature type is ever its supertype. A narrowing predicate whose target is not assignable to the declared type yields an INTERSECTION rather than a replacement, and the loose get overload would then win at every call site — silently undoing the narrowing. any is what keeps inboundTypeGuard an actual narrowing, and it is the same choice MessageTypeGuard makes on the write side.

  • readonly [name: string]: DecodedValue | readonly InboundBody[] | undefined

    A field by name, or a repeating group by its counter's name.

InboundOftypesince 0.6.0

type InboundOf<Bodies, M extends keyof Bodies & string> = InboundMessageOf<Bodies[M] & object, M>

Source: packages/fix/src/inbound.ts:309

The typed read surface of a received message whose MsgType value is M in a Bodies registry — the inbound counterpart of MessageOf, for annotating a narrowed message (a handler parameter, a variable).

InboundUniontypesince 0.6.0

type InboundUnion<Bodies> = {    [M in keyof Bodies & string]: InboundOf<Bodies, M>;}[keyof Bodies & string]

Source: packages/fix/src/inbound.ts:377

Every message in a Bodies registry as ONE discriminated union, keyed on msgType — what turns switch (message.msgType) into a narrowing dispatch instead of a chain of guards.

Each member is an InboundOf, so msgType is a literal in every branch and get() inside a case is typed to that message's body while InboundMessage.envelope and InboundMessage.parsed stay reachable. Handle every member and default narrows to never, giving compile-time exhaustiveness; handle a subset and default stays live and typed — a known message this consumer does not process.

if (isKnownInbound(inbound)) {  switch (inbound.msgType) {    case MsgType.Logon:            return onLogon(inbound);      // LogonBody    case MsgType.MarketDataRequest: return onRequest(inbound);   // MarketDataRequestBody    default:                        return;                      // known, not ours  }}

Two consequences of being a union, both worth knowing before reaching for it:

  • Read the envelope before the switch, the body after. msgType and envelope are the same type in every member, so they read fine unnarrowed; get() is a different signature per member, and calling it on the un-narrowed union is a "not callable" error. That is the right discipline anyway — session fields before dispatch, body fields inside it.
  • An unknown MsgType cannot be a member. A fallback member typed msgType: string overlaps every literal, so no case ever eliminates it and its loose get poisons every branch. Exclude unknown messages first, with a guard from inboundKnownGuard — which also matches how they parse: an unrecognised MsgType is read FLAT, with no groups reconstructed, so it was never union-shaped.

inboundTypeGuardfunctionsince 0.6.0

function inboundTypeGuard<Bodies>(): InboundTypeGuard<Bodies>

Source: packages/fix/src/inbound.ts:334

Build an InboundTypeGuard bound to a Bodies registry (the generated MessageBodies map of MsgType value → body type). Dict packages re-export the result as isInboundType; a consumer can bind one itself from any dict package's exported MessageBodies:

import { inboundTypeGuard } from '@boarteam/fix';import type { MessageBodies } from '@boarteam/fix-dict-fix44';const isInboundType = inboundTypeGuard<MessageBodies>();if (isInboundType(inbound, 'W')) {  inbound.get('NoMDEntries');   // the typed entry array  inbound.envelope.MsgSeqNum;   // still reachable after narrowing}

Pure and side-effect-free: the only runtime work is message.msgType === msgType.

Returns InboundTypeGuard<Bodies>The narrowing guard; its runtime is a plain msgType compare.

InboundTypeGuardtypesince 0.6.0

type InboundTypeGuard<Bodies> = <M extends keyof Bodies & string>(message: InboundMessage<any>, msgType: M) => message is InboundOf<Bodies, M>

Source: packages/fix/src/inbound.ts:296

A Bodies-bound narrowing guard for received messages — the inbound counterpart of MessageTypeGuard, and the reason a switch (message.msgType) with a cast per branch is no longer necessary.

It narrows to InboundMessage, not MessageView, so the InboundMessage.envelope and InboundMessage.parsed of the message survive the narrowing — a session almost always wants MsgSeqNum alongside the body fields it just gained types for.

inboundKnownGuardfunctionsince 0.6.0

function inboundKnownGuard<Bodies>(dict: Dictionary | DictionaryJSON): InboundKnownGuard<Bodies>

Source: packages/fix/src/inbound.ts:413

Build an InboundKnownGuard over a dictionary — the step that has to precede a switch (message.msgType) dispatch, because an unknown MsgType cannot be a union member (see InboundUnion).

The runtime test is dictionary.messageByMsgType(msgType) !== undefined. That stands in for "is a key of Bodies", which is not testable at runtime — Bodies is a type. The two agree as long as the dictionary and the Bodies map come from the SAME dict package, since the generator emits MessageBodies from this very dictionary; pairing a Bodies from one dialect with a dictionary from another is a mistake this cannot catch, the same way messageFactory cannot.

import { inboundKnownGuard } from '@boarteam/fix';import { dictionary, type MessageBodies } from '@boarteam/fix-dict-fix44';const isKnownInbound = inboundKnownGuard<MessageBodies>(dictionary);
ParameterTypeDescription
dictDictionary | DictionaryJSONThe dictionary the messages were parsed against (a DictionaryJSON is loaded for you).

Returns InboundKnownGuard<Bodies>The narrowing guard; its runtime is one dictionary lookup.

InboundKnownGuardtypesince 0.6.0

type InboundKnownGuard<Bodies> = (message: InboundMessage<any>) => message is InboundUnion<Bodies>

Source: packages/fix/src/inbound.ts:386

A guard that separates a received message the dictionary knows from one it does not, narrowing the former to InboundUnion so it can be dispatched with switch. Build one with inboundKnownGuard.