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.
| Parameter | Type | Description |
|---|---|---|
| parsed | ParsedMessage | The structured message from parse. |
| dict | Dictionary | DictionaryJSON | The dictionary it was parsed against (a DictionaryJSON is loaded for you). |
Returns InboundMessage<B> — A read-only, typed view over the same message.
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: InboundEnvelopeThe standard header/trailer fields, by name. See
InboundEnvelope.readonly parsed: ParsedMessageThe
ParsedMessagethis 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, andparsealready reported it asparse/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: MThe
MsgType(tag 35) value, as a literal type.
InboundEnvelopeinterfacesince 0.6.0
interface InboundEnvelopeSource: 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?: stringBeginString(8) — the dialect the frame declared.readonly BodyLength?: numberBodyLength(9), as declared on the wire.readonly MsgType?: stringMsgType(35). Also available unconditionally asMessageView.msgType.readonly MsgSeqNum?: numberMsgSeqNum(34) — the sequence number this message arrived with.readonly SenderCompID?: stringSenderCompID(49) — the counterparty that sent it.readonly TargetCompID?: stringTargetCompID(56) — us, on a well-addressed message.readonly SenderSubID?: stringSenderSubID(50).readonly TargetSubID?: stringTargetSubID(57).readonly SendingTime?: stringSendingTime(52), verbatim (YYYYMMDD-HH:MM:SS[.sss]) — never coerced to aDate.readonly OrigSendingTime?: stringOrigSendingTime(122) on a resend, verbatim.readonly PossDupFlag?: boolean | stringPossDupFlag(43) — set when the counterparty flagged a possible duplicate.boolean | stringbecause 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 bareBooleanyieldstrue/false. Compare against both, or readParsedField.rawoffInboundMessage.parsed.readonly PossResend?: boolean | stringPossResend(97). Enumerated likePossDupFlag— see the note there.readonly CheckSum?: stringCheckSum(10), verbatim — three digits, leading zeros intact.readonly [name: string]: DecodedValue | readonly InboundBody[] | undefinedAny 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 InboundBodySource: 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 (FieldValue — string | 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[] | undefinedA 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.
msgTypeandenvelopeare 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
MsgTypecannot be a member. A fallback member typedmsgType: stringoverlaps every literal, so nocaseever eliminates it and its loosegetpoisons every branch. Exclude unknown messages first, with a guard frominboundKnownGuard— which also matches how they parse: an unrecognisedMsgTypeis 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);| Parameter | Type | Description |
|---|---|---|
| dict | Dictionary | DictionaryJSON | The 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.