Dictionary extensions API

Venue-specific additions as one declaration: the extension schema, the runtime merge that applies it, and the literal-typed map helpers that keep Tags.SymbolName hovering as 1007. Generated from @boarteam/fix 0.5.2 as installed; the doc text is the library’s own. Guide: Extending a dictionary.

defineExtensionfunctionsince 0.2.0

function defineExtension<const E extends DictionaryExtension>(ext: E & GuardWidening<E>): E

Source: packages/fix/src/dictionary/extension.ts:212

Pin an extension declaration's literal types once — a zero-cost identity whose TS 5 const type parameter keeps every tag: 1007 / msgType: 'UP1' literal without as const, so the SAME value can drive extendDictionary (runtime) and tagsOf/msgTypesOf (types).

const ctrader = defineExtension({  id: 'ctrader',  fields: {    SymbolName:   { tag: 1007, type: 'String' },    SymbolDigits: { tag: 1008, type: 'int' },  },  messages: {    SecurityList: { groups: { NoRelatedSym: { append: ['SymbolName', 'SymbolDigits'] } } },  },});
ParameterTypeDescription
extE & GuardWidening<E>The extension declaration, written inline so its literals stay narrow.

Returns EThe same object, its literal types pinned.

extendDictionaryfunctionsince 0.2.0

function extendDictionary(base: DictionaryJSON, ...extensions: DictionaryExtension[]): ExtendResult

Source: packages/fix/src/dictionary/extendDictionary.ts:74

Apply venue extension declarations to a dictionary, producing a NEW DictionaryJSON the engine consumes unchanged. Never throws on data problems — every collision, unresolvable reference, malformed entry, or unsafe placement is a returned FixIssue (extend/* codes), and the offending operation is skipped or reverted rather than applied unsafely.

Semantics:

  • Pure: inputs are never mutated; the result is deterministic.
  • Composable: extensions apply left to right; later extensions see earlier ones.
  • Idempotent: re-applying an extension changes nothing (duplicate members are skipped with a warning; identical field redefinitions are silent).
  • Delimiter-safe: placements are append/after-anchor only, and every operation is checked against the corruption hazards the grammar alone cannot rule out — a group entry delimiter shifting (extend/group-delimiter-shift, reverted), a placed member colliding with a group scope that could be open on the wire around the insertion point in either direction (extend/ambiguous-boundary, skipped; optional members between are treated as potentially absent), a new group with no resolvable entry delimiter (extend/unresolvable-group-delimiter, skipped), and a component reference cycle (extend/component-cycle, reverted).

Run validateDictionary on the result as the final gate, exactly as for any other dictionary.

const { dictionary, issues } = extendDictionary(fix44, ctrader);if (issues.some((i) => i.severity === 'error')) {  // an operation was skipped or reverted — fix the extension declaration}const engine = createFixEngine(dictionary);
ParameterTypeDescription
baseDictionaryJSONThe dictionary to extend; never mutated.
extensionsDictionaryExtension[]Extension declarations, applied left to right.

Returns ExtendResultThe merged dictionary plus extend/* diagnostics.

ExtendResultinterfacesince 0.2.0

interface ExtendResult

Source: packages/fix/src/dictionary/extendDictionary.ts:23

The result of extendDictionary: the merged dictionary plus diagnostics.

  • dictionary: DictionaryJSON

    A fresh DictionaryJSON (deep clone — the base and the extensions are never mutated), consumed unchanged by loadDictionary/createFixEngine. When the base passes validateDictionary and issues carries no error-severity entries, the result passes validateDictionary too.

  • issues: FixIssue[]

    extend/* diagnostics, returned as data — never thrown. Severity encodes the outcome: error = the operation was skipped or reverted, warning = applied (or, for extend/duplicate-member, already satisfied) but review, info = advisory.

DictionaryExtensioninterfacesince 0.2.0

interface DictionaryExtension

Source: packages/fix/src/dictionary/extension.ts:125

One venue extension declaration — the single source of truth for both layers.

  • readonly id?: string

    Provenance label, e.g. 'ctrader'; echoed into issue paths and the merged JSON.

  • readonly fields?: Readonly<Record<string, ExtensionFieldDef>>

    New fields, keyed by NAME.

  • readonly enums?: Readonly<Record<string, readonly ExtensionEnumValue[]>>

    Enum values appended to EXISTING fields, keyed by field NAME.

  • readonly components?: Readonly<Record<string, ComponentExtension>>

    New components and additions to existing ones, keyed by component name.

  • readonly messages?: Readonly<Record<string, MessageExtension | NewMessageSpec>>

    New messages and patches to existing ones, keyed by message name.

ExtensionFieldDefinterfacesince 0.2.0

interface ExtensionFieldDef

Source: packages/fix/src/dictionary/extension.ts:38

A new field. Keyed by its NAME in DictionaryExtension.fields; the tag lives here. isGroupCounter is never authored — it is inferred from type deriving from NumInGroup, mirroring the invariant documented on the dictionary contract.

  • readonly tag: number

    The field's numeric tag, e.g. 1007.

  • readonly type: string

    Must name an existing datatype in the target dictionary ('String', 'int', 'Price', …).

  • readonly enumValues?: readonly ExtensionEnumValue[]

    Allowed values, when the new field is enumerated.

  • readonly lengthField?: number

    For a data-based field: the tag of the companion Length field preceding it.

  • readonly description?: string

    Human description, carried into the merged FieldDef.

ExtensionEnumValueinterfacesince 0.2.0

interface ExtensionEnumValue

Source: packages/fix/src/dictionary/extension.ts:24

One allowed value to add to a field's enum. description defaults to name.

  • readonly value: string

    The on-the-wire value, verbatim and opaque ("1", "004", "AB").

  • readonly name: string

    A code-identifier-safe name, e.g. "Buy". The identity on conflicts.

  • readonly description?: string

    Human description; defaults to name when omitted.

MemberSpectypesince 0.2.0

type MemberSpec = string | {    readonly field: string;    readonly reqd?: Reqd;} | {    readonly component: string;    readonly reqd?: Reqd;} | {    /** A NEW nested repeating group; `group` names its counter field. */    readonly group: string;    readonly reqd?: Reqd;    readonly members: readonly MemberSpec[];}

Source: packages/fix/src/dictionary/extension.ts:56

A member to place into a body. The string shorthand is an optional field by name (reqd: 'N'). Names resolve against the extension's own DictionaryExtension.fields first, then the base dictionary's fields/components.

GroupExtensioninterfacesince 0.2.0

interface GroupExtension

Source: packages/fix/src/dictionary/extension.ts:68

Members to add to one repeating group's entry body.

  • readonly append: readonly MemberSpec[]

    The members to add to the entry body, in order.

  • readonly after?: string

    Anchor member (field/component/group-counter name) to insert immediately AFTER; omit to append at the end of the entry body (which is also the encode position). before/start are deliberately unsupported: a group's first body field is its entry delimiter, and inserting ahead of it would change entry detection.

MessageExtensioninterfacesince 0.2.0

interface MessageExtension

Source: packages/fix/src/dictionary/extension.ts:81

A patch to an existing message (addressed by name via the record key).

  • readonly msgType?: never

    Never set on a patch — its presence is what marks an entry as a NewMessageSpec.

  • readonly append?: readonly MemberSpec[]

    Appended at the end of the body, kept BEFORE the trailing Standard Message Trailer.

  • readonly after?: string

    Anchor for append, as in GroupExtension.after.

  • readonly groups?: Readonly<Record<string, GroupExtension>>

    Additions to EXISTING repeating groups, keyed by counter-field-name path — dotted for nesting ('NoRelatedSym.NoUnderlyings'). A group that lives behind a shared component is auto-resolved only when it is reachable via exactly one single-reference component chain; otherwise the placement is refused with a extend/target-not-found hint naming the component to target explicitly.

NewMessageSpecinterfacesince 0.2.0

interface NewMessageSpec

Source: packages/fix/src/dictionary/extension.ts:99

A brand-new message (the presence of msgType is what marks an entry as new).

  • readonly msgType: string

    The MsgType (tag 35) value, case-sensitive ('U1', 'UP1', …).

  • readonly category?: MessageCategory

    Session (admin) vs application (app) message. Defaults to 'app'.

  • readonly members: readonly MemberSpec[]

    Body only — the base dictionary's Standard Message Header/Trailer component refs are injected automatically (reported via extend/header-trailer-injected).

ComponentExtensiontypesince 0.2.0

type ComponentExtension = {    readonly members: readonly MemberSpec[];} | {    readonly append?: readonly MemberSpec[];    readonly after?: string;    readonly groups?: Readonly<Record<string, GroupExtension>>;}

Source: packages/fix/src/dictionary/extension.ts:116

A component entry: { members } defines a NEW component; the other form extends an existing one (additions become visible in every referencing scope — deliberate, and reported via extend/component-fanout).

tagsOffunctionsince 0.2.0

function tagsOf<const E extends DictionaryExtension>(ext: E): TagsOf<E>

Source: packages/fix/src/dictionary/extension.ts:226

Derive the literal name → tag map from an extension declaration, typed as TagsOf. Pure and total; collisions are the runtime merge's concern.

export const Tags = extendTags(Fix44Tags, tagsOf(ctrader)); // Tags.SymbolName: 1007
ParameterTypeDescription
extEThe extension declaration to derive from.

Returns TagsOf<E>The literal name → tag map of the extension’s fields.

msgTypesOffunctionsince 0.2.0

function msgTypesOf<const E extends DictionaryExtension>(ext: E): MsgTypesOf<E>

Source: packages/fix/src/dictionary/extension.ts:240

Derive the literal name → msgType map of an extension's NEW messages, typed as MsgTypesOf. Message patches (entries without msgType) are skipped.

ParameterTypeDescription
extEThe extension declaration to derive from.

Returns MsgTypesOf<E>The literal name → msgType map of the extension’s NEW messages.

TagsOftypesince 0.2.0

type TagsOf<E extends DictionaryExtension> = Readonly<{    [K in keyof FieldsOf<E> & string]: FieldsOf<E>[K] extends {        tag: infer T extends number;    } ? T : never;}>

Source: packages/fix/src/dictionary/extension.ts:148

The literal name → tag map type derived from an extension's fields record — TagsOf<typeof ctrader> is { readonly SymbolName: 1007; readonly SymbolDigits: 1008 }. Feed the value-level counterpart tagsOf to extendTags.

MsgTypesOftypesince 0.2.0

type MsgTypesOf<E extends DictionaryExtension> = Readonly<{    [K in keyof MsgsOf<E> & string as MsgsOf<E>[K] extends {        msgType: string;    } ? K : never]: MsgsOf<E>[K] extends {        msgType: infer M extends string;    } ? M : never;}>

Source: packages/fix/src/dictionary/extension.ts:158

The literal name → msgType map type derived from an extension's NEW messages (entries carrying msgType); message patches are excluded.

extendTagsfunctionsince 0.2.0

function extendTags<B extends Record<string, number>, const T extends Record<string, number>>(base: B, ext: T): ExtendTags<B, T>

Source: packages/fix/src/dictionary/extendTags.ts:74

Merge venue-specific tag entries over a base Tags map, keeping every literal type. The const type parameter (TS ≥ 5.0) pins call-site literals without as const; on a name collision the extension wins (object-spread semantics), and the ExtendTags result type mirrors that exactly.

Pure: returns a new object; never mutates base or ext.

const Tags = extendTags(Fix44Tags, { SymbolName: 1007, SymbolDigits: 1008 });Tags.SymbolName; // typed 1007type TagName = keyof typeof Tags; // includes 'SymbolName'

Note: literals survive only for call-site object literals (or as const values) — an extension pre-declared as Record<string, number> is already widened and yields number-typed entries.

ParameterTypeDescription
baseBThe shipped Tags map to extend.
extTVenue entries; on a name collision the extension wins.

Returns ExtendTags<B, T>The merged map, literal types preserved.

extendMsgTypesfunctionsince 0.2.0

function extendMsgTypes<B extends Record<string, string>, const T extends Record<string, string>>(base: B, ext: T): ExtendMsgTypes<B, T>

Source: packages/fix/src/dictionary/extendTags.ts:114

Merge venue-specific message-type entries over a base MsgType map — the string-valued mirror of extendTags.

const MsgType = extendMsgTypes(Fix44MsgType, { CTraderPing: 'UP1' });MsgType.CTraderPing; // typed 'UP1'
ParameterTypeDescription
baseBThe shipped MsgType map to extend.
extTVenue entries; on a name collision the extension wins.

Returns ExtendMsgTypes<B, T>The merged map, literal types preserved.

invertTagsfunctionsince 0.2.0

function invertTags<const T extends Record<string, number>>(tags: T): InvertTags<T>

Source: packages/fix/src/dictionary/extendTags.ts:94

Build the tag → name reverse of a Tags map, typed as InvertTags: literal lookups for known tags plus the shipped packages' widened name | undefined number index for arbitrary input.

Total and silent by contract: two names sharing one tag resolve last-write-wins (mirroring what a spread-built forward map does); extendDictionary is where such collisions are reported. For a standalone bijection guarantee, assert Object.keys(invertTags(m)).length === Object.keys(m).length in a test, as the shipped packages' names.test.ts does.

ParameterTypeDescription
tagsTThe name → tag map to invert.

Returns InvertTags<T>The tag → name reverse, typed as InvertTags.

invertMsgTypesfunctionsince 0.2.0

function invertMsgTypes<const T extends Record<string, string>>(msgTypes: T): InvertMsgTypes<T>

Source: packages/fix/src/dictionary/extendTags.ts:127

Build the msgType → name reverse of a MsgType map — the string-valued mirror of invertTags.

ParameterTypeDescription
msgTypesTThe name → msgType map to invert.

Returns InvertMsgTypes<T>The msgType → name reverse, typed as InvertMsgTypes.

ExtendTagstypesince 0.2.0

type ExtendTags<B extends Record<string, number>, T extends Record<string, number>> = Readonly<Omit<B, keyof T> & T>

Source: packages/fix/src/dictionary/extendTags.ts:27

The result of extendTags: a key-remapped merge where extension keys cleanly REPLACE base keys. Deliberately not a bare intersection — with B & T, a name present in both with different tags would collapse to 55 & 9955 = never; here it types as the extension's literal, matching the runtime spread.

ExtendMsgTypestypesince 0.2.0

type ExtendMsgTypes<B extends Record<string, string>, T extends Record<string, string>> = Readonly<Omit<B, keyof T> & T>

Source: packages/fix/src/dictionary/extendTags.ts:43

ExtendTags for MsgType maps (name → msgType strings).

InvertTagstypesince 0.2.0

type InvertTags<T extends Record<string, number>> = {    readonly [K in keyof T as T[K] & number]: K & string;} & {    readonly [tag: number]: (keyof T & string) | undefined;}

Source: packages/fix/src/dictionary/extendTags.ts:38

The result of invertTags: the precise tag → name mapped inversion (so TagNames[1007] hovers as its literal name) intersected with the widened index signature the shipped packages use (so TagNames[someNumber] is name | undefined, never a type error).

InvertMsgTypestypesince 0.2.0

type InvertMsgTypes<T extends Record<string, string>> = {    readonly [K in keyof T as T[K] & string]: K & string;} & {    readonly [msgType: string]: (keyof T & string) | undefined;}

Source: packages/fix/src/dictionary/extendTags.ts:49

InvertTags for MsgType maps: msgType → name with a string index.

GuardWideningtypesince 0.2.0

type GuardWidening<E extends DictionaryExtension> = (TagsOf<E>[keyof TagsOf<E>] extends never ? unknown : number extends TagsOf<E>[keyof TagsOf<E>] ? WidenedExtensionError : unknown) & (MsgTypesOf<E>[keyof MsgTypesOf<E>] extends never ? unknown : string extends MsgTypesOf<E>[keyof MsgTypesOf<E>] ? WidenedExtensionError : unknown)

Source: packages/fix/src/dictionary/extension.ts:180

Compile-time guard: resolves to unknown (no-op) when the extension's tag and msgType literals survived inference, and to WidenedExtensionError when they widened to number/string — which happens when the declaration was pre-typed as DictionaryExtension before the call. Without this, the flagship literal hovers would silently degrade; with it, the degradation is a readable type error.

WidenedExtensionErrorinterfacesince 0.2.0

interface WidenedExtensionError

Source: packages/fix/src/dictionary/extension.ts:168

The compile error surfaced by GuardWidening when an extension's literals were widened before reaching defineExtension. Not meant to be used directly.

  • readonly 'ERROR: extension literals were widened': 'declare the extension inline at the defineExtension call site (or as const) so tags stay literal types like 1007'

    The remedy, carried in the property NAME so it reads in full inside editor hovers.