@homaiohq/napi-quickfix 0.0.0-stage → 0.2.0
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/CMakeLists.txt +135 -0
- package/LICENSE +21 -0
- package/README.md +638 -2
- package/THIRD_PARTY_LICENSES +48 -0
- package/cpp/acceptor_wrap.cpp +468 -0
- package/cpp/acceptor_wrap.h +116 -0
- package/cpp/addon.cpp +87 -0
- package/cpp/application_bridge.cpp +466 -0
- package/cpp/application_bridge.h +204 -0
- package/cpp/data_dictionary_wrap.cpp +165 -0
- package/cpp/data_dictionary_wrap.h +59 -0
- package/cpp/engine_common.cpp +5 -0
- package/cpp/engine_common.h +127 -0
- package/cpp/engine_workers.h +167 -0
- package/cpp/errors.h +142 -0
- package/cpp/field_map_ops.h +492 -0
- package/cpp/group_wrap.cpp +221 -0
- package/cpp/group_wrap.h +76 -0
- package/cpp/initiator_wrap.cpp +492 -0
- package/cpp/initiator_wrap.h +104 -0
- package/cpp/message_wrap.cpp +288 -0
- package/cpp/message_wrap.h +95 -0
- package/cpp/session_id_wrap.cpp +91 -0
- package/cpp/session_id_wrap.h +46 -0
- package/cpp/session_op_gate.h +201 -0
- package/cpp/session_settings_wrap.cpp +98 -0
- package/cpp/session_settings_wrap.h +40 -0
- package/cpp/session_static.cpp +220 -0
- package/cpp/session_wrap.cpp +438 -0
- package/cpp/session_wrap.h +144 -0
- package/dist/cjs/acceptor.d.ts +29 -0
- package/dist/cjs/acceptor.js +38 -0
- package/dist/cjs/application.d.ts +115 -0
- package/dist/cjs/application.js +62 -0
- package/dist/cjs/data-dictionary.d.ts +67 -0
- package/dist/cjs/data-dictionary.js +105 -0
- package/dist/cjs/engine.d.ts +152 -0
- package/dist/cjs/engine.js +183 -0
- package/dist/cjs/enums.d.ts +56 -0
- package/dist/cjs/enums.js +44 -0
- package/dist/cjs/field-value.d.ts +12 -0
- package/dist/cjs/field-value.js +40 -0
- package/dist/cjs/generated/fields.d.ts +6122 -0
- package/dist/cjs/generated/fields.js +6127 -0
- package/dist/cjs/generated/values.d.ts +8548 -0
- package/dist/cjs/generated/values.js +8580 -0
- package/dist/cjs/group.d.ts +126 -0
- package/dist/cjs/group.js +185 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +73 -0
- package/dist/cjs/initiator.d.ts +29 -0
- package/dist/cjs/initiator.js +38 -0
- package/dist/cjs/load-native.cjs +5 -0
- package/dist/cjs/message.d.ts +238 -0
- package/dist/cjs/message.js +299 -0
- package/dist/cjs/native.d.ts +325 -0
- package/dist/cjs/native.js +22 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/session-id.d.ts +51 -0
- package/dist/cjs/session-id.js +87 -0
- package/dist/cjs/session-settings.d.ts +46 -0
- package/dist/cjs/session-settings.js +75 -0
- package/dist/cjs/session.d.ts +213 -0
- package/dist/cjs/session.js +337 -0
- package/dist/esm/acceptor.d.ts +29 -0
- package/dist/esm/acceptor.js +34 -0
- package/dist/esm/application.d.ts +115 -0
- package/dist/esm/application.js +57 -0
- package/dist/esm/data-dictionary.d.ts +67 -0
- package/dist/esm/data-dictionary.js +101 -0
- package/dist/esm/engine.d.ts +152 -0
- package/dist/esm/engine.js +179 -0
- package/dist/esm/enums.d.ts +56 -0
- package/dist/esm/enums.js +35 -0
- package/dist/esm/field-value.d.ts +12 -0
- package/dist/esm/field-value.js +37 -0
- package/dist/esm/generated/fields.d.ts +6122 -0
- package/dist/esm/generated/fields.js +6124 -0
- package/dist/esm/generated/values.d.ts +8548 -0
- package/dist/esm/generated/values.js +8564 -0
- package/dist/esm/group.d.ts +126 -0
- package/dist/esm/group.js +181 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +43 -0
- package/dist/esm/initiator.d.ts +29 -0
- package/dist/esm/initiator.js +34 -0
- package/dist/esm/load-native.cjs +5 -0
- package/dist/esm/message.d.ts +238 -0
- package/dist/esm/message.js +293 -0
- package/dist/esm/native.d.ts +325 -0
- package/dist/esm/native.js +16 -0
- package/dist/esm/session-id.d.ts +51 -0
- package/dist/esm/session-id.js +82 -0
- package/dist/esm/session-settings.d.ts +46 -0
- package/dist/esm/session-settings.js +71 -0
- package/dist/esm/session.d.ts +213 -0
- package/dist/esm/session.js +329 -0
- package/package.json +95 -4
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/src/load-native.cjs +5 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ergonomic wrapper around the native `FIX::Message`.
|
|
3
|
+
*/
|
|
4
|
+
import { type NativeMessage } from './native.js';
|
|
5
|
+
import { Group } from './group.js';
|
|
6
|
+
import type { DataDictionary } from './data-dictionary.js';
|
|
7
|
+
/** Options controlling how a raw FIX string is parsed into a {@link Message}. */
|
|
8
|
+
export interface MessageParseOptions {
|
|
9
|
+
/**
|
|
10
|
+
* When `true`, the engine validates the message structure during parsing
|
|
11
|
+
* (e.g. checksum / body-length). Defaults to `false` (lenient parse), matching
|
|
12
|
+
* QuickFIX behaviour where validation is normally applied via a
|
|
13
|
+
* {@link DataDictionary} instead.
|
|
14
|
+
*/
|
|
15
|
+
validate?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Parse with this dictionary as both the session and application dictionary
|
|
18
|
+
* (the usual choice for FIX 4.x). With a dictionary, repeating groups are
|
|
19
|
+
* parsed into nested {@link Group} entries readable with
|
|
20
|
+
* {@link Message.getGroup}; without one the parse is **flat** and group
|
|
21
|
+
* fields are left as plain repeated body fields.
|
|
22
|
+
*/
|
|
23
|
+
dictionary?: DataDictionary;
|
|
24
|
+
/**
|
|
25
|
+
* The session (transport) dictionary, e.g. `FIXT11.xml`. Governs header,
|
|
26
|
+
* trailer and admin messages. Overrides {@link MessageParseOptions.dictionary}
|
|
27
|
+
* for that role.
|
|
28
|
+
*/
|
|
29
|
+
sessionDictionary?: DataDictionary;
|
|
30
|
+
/**
|
|
31
|
+
* The application dictionary, e.g. `FIX50SP2.xml`. Governs the body of
|
|
32
|
+
* application messages. Overrides {@link MessageParseOptions.dictionary} for
|
|
33
|
+
* that role.
|
|
34
|
+
*/
|
|
35
|
+
applicationDictionary?: DataDictionary;
|
|
36
|
+
}
|
|
37
|
+
/** A structured, best-effort view of a parsed message returned by {@link Message.toJSON}. */
|
|
38
|
+
export interface MessageJSON {
|
|
39
|
+
/** The MsgType (tag 35) if resolvable, otherwise omitted. */
|
|
40
|
+
msgType?: string;
|
|
41
|
+
/** The raw FIX wire string (SOH-delimited). */
|
|
42
|
+
raw: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A FIX message.
|
|
46
|
+
*
|
|
47
|
+
* Field values are accepted as `string | number` on input (numbers are
|
|
48
|
+
* stringified, since FIX is string-on-the-wire, without exponent notation:
|
|
49
|
+
* `1e-7` is written `0.0000001`) and always returned as `string`. `NaN` and
|
|
50
|
+
* `±Infinity` throw a `RangeError`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { Message, FIELD, MsgType } from '@homaiohq/napi-quickfix';
|
|
55
|
+
*
|
|
56
|
+
* const msg = new Message();
|
|
57
|
+
* msg.setField(FIELD.MsgType, MsgType.NewOrderSingle)
|
|
58
|
+
* .setField(FIELD.ClOrdID, 'order-1')
|
|
59
|
+
* .setField(FIELD.OrderQty, 100);
|
|
60
|
+
*
|
|
61
|
+
* const parsed = Message.parse(msg.toString());
|
|
62
|
+
* console.log(parsed.getMsgType());
|
|
63
|
+
*
|
|
64
|
+
* // Repeating groups: build entries with Group, read them back by 1-based index.
|
|
65
|
+
* msg.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'TRADER-1'));
|
|
66
|
+
* const dd = DataDictionary.fromFile('FIX44.xml');
|
|
67
|
+
* const withGroups = Message.parse(msg.toString(), { dictionary: dd });
|
|
68
|
+
* withGroups.getGroup(1, FIELD.NoPartyIDs).getField(FIELD.PartyID); // 'TRADER-1'
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare class Message {
|
|
72
|
+
#private;
|
|
73
|
+
/**
|
|
74
|
+
* Create a message. With no argument, creates an empty message. With a raw
|
|
75
|
+
* FIX string, parses it.
|
|
76
|
+
*
|
|
77
|
+
* Advanced: passing an existing {@link NativeMessage} handle adopts it (used
|
|
78
|
+
* internally by the engine bridge to wrap inbound/outbound native messages).
|
|
79
|
+
*
|
|
80
|
+
* @param raw Optional raw FIX wire string (SOH-delimited) to parse, or an
|
|
81
|
+
* existing native handle to adopt.
|
|
82
|
+
* @param opts Optional parse options.
|
|
83
|
+
*/
|
|
84
|
+
constructor(raw?: string | NativeMessage, opts?: MessageParseOptions);
|
|
85
|
+
/**
|
|
86
|
+
* Parse a raw FIX wire string into a {@link Message}.
|
|
87
|
+
*
|
|
88
|
+
* Pass a `dictionary` to parse repeating groups structurally; without one
|
|
89
|
+
* the parse is flat (see {@link MessageParseOptions}).
|
|
90
|
+
*
|
|
91
|
+
* @param raw Raw FIX wire string (SOH-delimited).
|
|
92
|
+
* @param opts Optional parse options.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const dd = DataDictionary.fromFile('./spec/FIX44.xml');
|
|
97
|
+
* const msg = Message.parse(raw, { dictionary: dd });
|
|
98
|
+
* msg.getGroup(1, FIELD.NoPartyIDs).getField(FIELD.PartyID);
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
static parse(raw: string, opts?: MessageParseOptions): Message;
|
|
102
|
+
/** @internal Wrap an existing native message handle (used by the engine bridge). */
|
|
103
|
+
static fromNative(handle: NativeMessage): Message;
|
|
104
|
+
/** @internal The native handle backing this message (for the engine / sendToTarget / validation). */
|
|
105
|
+
get nativeHandle(): NativeMessage;
|
|
106
|
+
/**
|
|
107
|
+
* Read a field by tag. Standard header/trailer tags (`MsgType`,
|
|
108
|
+
* `SenderCompID`, `CheckSum`, ...) are read from their section, and so are
|
|
109
|
+
* the header/trailer fields of the session dictionary the message was
|
|
110
|
+
* parsed with (`sessionDictionary` / `dictionary`) or delivered with (a
|
|
111
|
+
* message handed to an engine handler). Any other tag is read from the body,
|
|
112
|
+
* or from the header or trailer if that is where it is.
|
|
113
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) if absent.
|
|
114
|
+
*/
|
|
115
|
+
getField(tag: number): string;
|
|
116
|
+
/**
|
|
117
|
+
* Set a field. Standard header/trailer tags and the session dictionary's
|
|
118
|
+
* header/trailer fields auto-route to their section (see
|
|
119
|
+
* {@link Message.getField}). Any other tag is updated in place if the header
|
|
120
|
+
* or trailer already holds it; otherwise it goes to the body. On a message
|
|
121
|
+
* without a session dictionary (built with `new Message()`), put a new
|
|
122
|
+
* custom header field in the header with {@link Message.setHeaderField}.
|
|
123
|
+
* Returns `this` for chaining.
|
|
124
|
+
*/
|
|
125
|
+
setField(tag: number, value: string | number): this;
|
|
126
|
+
/** Whether a field is present (same auto-routing as {@link Message.getField}). */
|
|
127
|
+
isSetField(tag: number): boolean;
|
|
128
|
+
/** Remove a field (no-op if absent; same auto-routing). Returns `this` for chaining. */
|
|
129
|
+
removeField(tag: number): this;
|
|
130
|
+
/** Read a field by tag, or `undefined` if absent (same auto-routing). */
|
|
131
|
+
getFieldIfSet(tag: number): string | undefined;
|
|
132
|
+
/** Whether the header, body and trailer have no fields of their own. */
|
|
133
|
+
isEmpty(): boolean;
|
|
134
|
+
/** The number of body fields, including every field of every group entry. */
|
|
135
|
+
totalFields(): number;
|
|
136
|
+
/** Remove every header, body and trailer field and group. Returns `this` for chaining. */
|
|
137
|
+
clear(): this;
|
|
138
|
+
/** The body's own fields, in wire order, as `[tag, value]` pairs (group entries excluded). */
|
|
139
|
+
fields(): [number, string][];
|
|
140
|
+
/** The header fields, in wire order, as `[tag, value]` pairs. */
|
|
141
|
+
headerFields(): [number, string][];
|
|
142
|
+
/** The trailer fields, in wire order, as `[tag, value]` pairs. */
|
|
143
|
+
trailerFields(): [number, string][];
|
|
144
|
+
/** Iterates the body's own fields as `[tag, value]` pairs (see {@link Message.fields}). */
|
|
145
|
+
[Symbol.iterator](): IterableIterator<[number, string]>;
|
|
146
|
+
/**
|
|
147
|
+
* Append a repeating-group entry under its count tag (`group.field`). A copy
|
|
148
|
+
* is stored and the count field is updated. Header groups (e.g. `NoHops`)
|
|
149
|
+
* auto-route to the header. Returns `this` for chaining.
|
|
150
|
+
*
|
|
151
|
+
* @throws `TypeError` when `group.delim` differs from the delimiter of the
|
|
152
|
+
* entries already stored under `group.field`.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* order.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'X'));
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
addGroup(group: Group): this;
|
|
160
|
+
/**
|
|
161
|
+
* A **snapshot copy** of the `index`-th entry (1-based) of the repeating
|
|
162
|
+
* group `tag`. Mutating the returned {@link Group} does not change this
|
|
163
|
+
* message; write it back with {@link Message.replaceGroup}.
|
|
164
|
+
*
|
|
165
|
+
* Groups exist only on messages built with {@link Message.addGroup} or
|
|
166
|
+
* parsed with a dictionary, which includes every message the engine hands
|
|
167
|
+
* to your handlers when the session has `UseDataDictionary=Y` (the
|
|
168
|
+
* default); a flat parse, or `UseDataDictionary=N`, leaves none.
|
|
169
|
+
*
|
|
170
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when the tag
|
|
171
|
+
* has no entries or `index` is out of range.
|
|
172
|
+
*/
|
|
173
|
+
getGroup(index: number, tag: number): Group;
|
|
174
|
+
/**
|
|
175
|
+
* Overwrite the `index`-th entry (1-based) under `group.field` with a copy of
|
|
176
|
+
* `group`. Returns `this` for chaining.
|
|
177
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when that
|
|
178
|
+
* entry does not exist.
|
|
179
|
+
* @throws `TypeError` when `group.delim` differs from the stored entry's
|
|
180
|
+
* delimiter.
|
|
181
|
+
*/
|
|
182
|
+
replaceGroup(index: number, group: Group): this;
|
|
183
|
+
/**
|
|
184
|
+
* Remove every entry of the repeating group `tag` (and its count field), or
|
|
185
|
+
* only the `index`-th entry (1-based). Returns `this`.
|
|
186
|
+
*
|
|
187
|
+
* Removing every entry of a tag that has none is a no-op.
|
|
188
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when the
|
|
189
|
+
* `index`-th entry does not exist.
|
|
190
|
+
*/
|
|
191
|
+
removeGroup(tag: number): this;
|
|
192
|
+
removeGroup(index: number, tag: number): this;
|
|
193
|
+
/** Whether the repeating group `tag` has any entry, or has an `index`-th entry (1-based). */
|
|
194
|
+
hasGroup(tag: number): boolean;
|
|
195
|
+
hasGroup(index: number, tag: number): boolean;
|
|
196
|
+
/** The number of entries of the repeating group `tag` (0 if none). */
|
|
197
|
+
groupCount(tag: number): number;
|
|
198
|
+
/**
|
|
199
|
+
* Read a header field by tag.
|
|
200
|
+
* @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
|
|
201
|
+
*/
|
|
202
|
+
getHeaderField(tag: number): string;
|
|
203
|
+
/** Set a header field. Returns `this` for chaining. */
|
|
204
|
+
setHeaderField(tag: number, value: string | number): this;
|
|
205
|
+
/**
|
|
206
|
+
* Read a trailer field by tag.
|
|
207
|
+
* @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
|
|
208
|
+
*/
|
|
209
|
+
getTrailerField(tag: number): string;
|
|
210
|
+
/** Set a trailer field. Returns `this` for chaining. */
|
|
211
|
+
setTrailerField(tag: number, value: string | number): this;
|
|
212
|
+
/** The message type (tag 35), e.g. `'A'` for Logon. */
|
|
213
|
+
getMsgType(): string;
|
|
214
|
+
/** The raw FIX wire string (SOH-delimited). */
|
|
215
|
+
toString(): string;
|
|
216
|
+
/** A human-readable, pretty-printed rendering of the message. */
|
|
217
|
+
toPretty(): string;
|
|
218
|
+
/**
|
|
219
|
+
* A best-effort structured view. Always includes `raw`; includes `msgType`
|
|
220
|
+
* when resolvable.
|
|
221
|
+
*/
|
|
222
|
+
toJSON(): MessageJSON;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Create a message, optionally pre-populating body fields.
|
|
226
|
+
*
|
|
227
|
+
* @param fields Map of tag → value to set on the message body.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* const msg = createMessage({ [FIELD.MsgType]: MsgType.Logon, [FIELD.HeartBtInt]: 30 });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export declare function createMessage(fields?: Record<number, string | number>): Message;
|
|
235
|
+
/**
|
|
236
|
+
* Parse a raw FIX wire string into a {@link Message}. Alias of {@link Message.parse}.
|
|
237
|
+
*/
|
|
238
|
+
export declare function parseMessage(raw: string, opts?: MessageParseOptions): Message;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ergonomic wrapper around the native `FIX::Message`.
|
|
3
|
+
*/
|
|
4
|
+
import { native } from './native.js';
|
|
5
|
+
import { Group } from './group.js';
|
|
6
|
+
import { formatFieldValue } from './field-value.js';
|
|
7
|
+
/**
|
|
8
|
+
* A FIX message.
|
|
9
|
+
*
|
|
10
|
+
* Field values are accepted as `string | number` on input (numbers are
|
|
11
|
+
* stringified, since FIX is string-on-the-wire, without exponent notation:
|
|
12
|
+
* `1e-7` is written `0.0000001`) and always returned as `string`. `NaN` and
|
|
13
|
+
* `±Infinity` throw a `RangeError`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { Message, FIELD, MsgType } from '@homaiohq/napi-quickfix';
|
|
18
|
+
*
|
|
19
|
+
* const msg = new Message();
|
|
20
|
+
* msg.setField(FIELD.MsgType, MsgType.NewOrderSingle)
|
|
21
|
+
* .setField(FIELD.ClOrdID, 'order-1')
|
|
22
|
+
* .setField(FIELD.OrderQty, 100);
|
|
23
|
+
*
|
|
24
|
+
* const parsed = Message.parse(msg.toString());
|
|
25
|
+
* console.log(parsed.getMsgType());
|
|
26
|
+
*
|
|
27
|
+
* // Repeating groups: build entries with Group, read them back by 1-based index.
|
|
28
|
+
* msg.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'TRADER-1'));
|
|
29
|
+
* const dd = DataDictionary.fromFile('FIX44.xml');
|
|
30
|
+
* const withGroups = Message.parse(msg.toString(), { dictionary: dd });
|
|
31
|
+
* withGroups.getGroup(1, FIELD.NoPartyIDs).getField(FIELD.PartyID); // 'TRADER-1'
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class Message {
|
|
35
|
+
/** @internal Underlying native handle. */
|
|
36
|
+
#native;
|
|
37
|
+
/**
|
|
38
|
+
* Create a message. With no argument, creates an empty message. With a raw
|
|
39
|
+
* FIX string, parses it.
|
|
40
|
+
*
|
|
41
|
+
* Advanced: passing an existing {@link NativeMessage} handle adopts it (used
|
|
42
|
+
* internally by the engine bridge to wrap inbound/outbound native messages).
|
|
43
|
+
*
|
|
44
|
+
* @param raw Optional raw FIX wire string (SOH-delimited) to parse, or an
|
|
45
|
+
* existing native handle to adopt.
|
|
46
|
+
* @param opts Optional parse options.
|
|
47
|
+
*/
|
|
48
|
+
constructor(raw, opts) {
|
|
49
|
+
if (raw === undefined) {
|
|
50
|
+
this.#native = new native.MessageWrap();
|
|
51
|
+
}
|
|
52
|
+
else if (typeof raw === 'string') {
|
|
53
|
+
const sessionDD = opts?.sessionDictionary ?? opts?.dictionary;
|
|
54
|
+
const appDD = opts?.applicationDictionary ?? opts?.dictionary;
|
|
55
|
+
this.#native = new native.MessageWrap(raw, opts?.validate ?? false, sessionDD?.nativeHandle ?? null, appDD?.nativeHandle ?? null);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Adopt an existing native handle (bridge path).
|
|
59
|
+
this.#native = raw;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse a raw FIX wire string into a {@link Message}.
|
|
64
|
+
*
|
|
65
|
+
* Pass a `dictionary` to parse repeating groups structurally; without one
|
|
66
|
+
* the parse is flat (see {@link MessageParseOptions}).
|
|
67
|
+
*
|
|
68
|
+
* @param raw Raw FIX wire string (SOH-delimited).
|
|
69
|
+
* @param opts Optional parse options.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const dd = DataDictionary.fromFile('./spec/FIX44.xml');
|
|
74
|
+
* const msg = Message.parse(raw, { dictionary: dd });
|
|
75
|
+
* msg.getGroup(1, FIELD.NoPartyIDs).getField(FIELD.PartyID);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
static parse(raw, opts) {
|
|
79
|
+
return new Message(raw, opts);
|
|
80
|
+
}
|
|
81
|
+
/** @internal Wrap an existing native message handle (used by the engine bridge). */
|
|
82
|
+
static fromNative(handle) {
|
|
83
|
+
return new Message(handle);
|
|
84
|
+
}
|
|
85
|
+
/** @internal The native handle backing this message (for the engine / sendToTarget / validation). */
|
|
86
|
+
get nativeHandle() {
|
|
87
|
+
return this.#native;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read a field by tag. Standard header/trailer tags (`MsgType`,
|
|
91
|
+
* `SenderCompID`, `CheckSum`, ...) are read from their section, and so are
|
|
92
|
+
* the header/trailer fields of the session dictionary the message was
|
|
93
|
+
* parsed with (`sessionDictionary` / `dictionary`) or delivered with (a
|
|
94
|
+
* message handed to an engine handler). Any other tag is read from the body,
|
|
95
|
+
* or from the header or trailer if that is where it is.
|
|
96
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) if absent.
|
|
97
|
+
*/
|
|
98
|
+
getField(tag) {
|
|
99
|
+
return this.#native.getField(tag);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Set a field. Standard header/trailer tags and the session dictionary's
|
|
103
|
+
* header/trailer fields auto-route to their section (see
|
|
104
|
+
* {@link Message.getField}). Any other tag is updated in place if the header
|
|
105
|
+
* or trailer already holds it; otherwise it goes to the body. On a message
|
|
106
|
+
* without a session dictionary (built with `new Message()`), put a new
|
|
107
|
+
* custom header field in the header with {@link Message.setHeaderField}.
|
|
108
|
+
* Returns `this` for chaining.
|
|
109
|
+
*/
|
|
110
|
+
setField(tag, value) {
|
|
111
|
+
this.#native.setField(tag, formatFieldValue(value));
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
/** Whether a field is present (same auto-routing as {@link Message.getField}). */
|
|
115
|
+
isSetField(tag) {
|
|
116
|
+
return this.#native.isSetField(tag);
|
|
117
|
+
}
|
|
118
|
+
/** Remove a field (no-op if absent; same auto-routing). Returns `this` for chaining. */
|
|
119
|
+
removeField(tag) {
|
|
120
|
+
this.#native.removeField(tag);
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
/** Read a field by tag, or `undefined` if absent (same auto-routing). */
|
|
124
|
+
getFieldIfSet(tag) {
|
|
125
|
+
return this.#native.getFieldIfSet(tag);
|
|
126
|
+
}
|
|
127
|
+
/** Whether the header, body and trailer have no fields of their own. */
|
|
128
|
+
isEmpty() {
|
|
129
|
+
return this.#native.isEmpty();
|
|
130
|
+
}
|
|
131
|
+
/** The number of body fields, including every field of every group entry. */
|
|
132
|
+
totalFields() {
|
|
133
|
+
return this.#native.totalFields();
|
|
134
|
+
}
|
|
135
|
+
/** Remove every header, body and trailer field and group. Returns `this` for chaining. */
|
|
136
|
+
clear() {
|
|
137
|
+
this.#native.clear();
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
/** The body's own fields, in wire order, as `[tag, value]` pairs (group entries excluded). */
|
|
141
|
+
fields() {
|
|
142
|
+
return this.#native.fields();
|
|
143
|
+
}
|
|
144
|
+
/** The header fields, in wire order, as `[tag, value]` pairs. */
|
|
145
|
+
headerFields() {
|
|
146
|
+
return this.#native.headerFields();
|
|
147
|
+
}
|
|
148
|
+
/** The trailer fields, in wire order, as `[tag, value]` pairs. */
|
|
149
|
+
trailerFields() {
|
|
150
|
+
return this.#native.trailerFields();
|
|
151
|
+
}
|
|
152
|
+
/** Iterates the body's own fields as `[tag, value]` pairs (see {@link Message.fields}). */
|
|
153
|
+
[Symbol.iterator]() {
|
|
154
|
+
return this.fields()[Symbol.iterator]();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Append a repeating-group entry under its count tag (`group.field`). A copy
|
|
158
|
+
* is stored and the count field is updated. Header groups (e.g. `NoHops`)
|
|
159
|
+
* auto-route to the header. Returns `this` for chaining.
|
|
160
|
+
*
|
|
161
|
+
* @throws `TypeError` when `group.delim` differs from the delimiter of the
|
|
162
|
+
* entries already stored under `group.field`.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* order.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'X'));
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
addGroup(group) {
|
|
170
|
+
this.#native.addGroup(group.nativeHandle);
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A **snapshot copy** of the `index`-th entry (1-based) of the repeating
|
|
175
|
+
* group `tag`. Mutating the returned {@link Group} does not change this
|
|
176
|
+
* message; write it back with {@link Message.replaceGroup}.
|
|
177
|
+
*
|
|
178
|
+
* Groups exist only on messages built with {@link Message.addGroup} or
|
|
179
|
+
* parsed with a dictionary, which includes every message the engine hands
|
|
180
|
+
* to your handlers when the session has `UseDataDictionary=Y` (the
|
|
181
|
+
* default); a flat parse, or `UseDataDictionary=N`, leaves none.
|
|
182
|
+
*
|
|
183
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when the tag
|
|
184
|
+
* has no entries or `index` is out of range.
|
|
185
|
+
*/
|
|
186
|
+
getGroup(index, tag) {
|
|
187
|
+
return Group.fromNative(this.#native.getGroup(index, tag));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Overwrite the `index`-th entry (1-based) under `group.field` with a copy of
|
|
191
|
+
* `group`. Returns `this` for chaining.
|
|
192
|
+
* @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when that
|
|
193
|
+
* entry does not exist.
|
|
194
|
+
* @throws `TypeError` when `group.delim` differs from the stored entry's
|
|
195
|
+
* delimiter.
|
|
196
|
+
*/
|
|
197
|
+
replaceGroup(index, group) {
|
|
198
|
+
this.#native.replaceGroup(index, group.nativeHandle);
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
removeGroup(a, b) {
|
|
202
|
+
if (b === undefined) {
|
|
203
|
+
this.#native.removeGroup(a);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.#native.removeGroup(a, b);
|
|
207
|
+
}
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
hasGroup(a, b) {
|
|
211
|
+
return b === undefined ? this.#native.hasGroup(a) : this.#native.hasGroup(a, b);
|
|
212
|
+
}
|
|
213
|
+
/** The number of entries of the repeating group `tag` (0 if none). */
|
|
214
|
+
groupCount(tag) {
|
|
215
|
+
return this.#native.groupCount(tag);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Read a header field by tag.
|
|
219
|
+
* @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
|
|
220
|
+
*/
|
|
221
|
+
getHeaderField(tag) {
|
|
222
|
+
return this.#native.getHeaderField(tag);
|
|
223
|
+
}
|
|
224
|
+
/** Set a header field. Returns `this` for chaining. */
|
|
225
|
+
setHeaderField(tag, value) {
|
|
226
|
+
this.#native.setHeaderField(tag, formatFieldValue(value));
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Read a trailer field by tag.
|
|
231
|
+
* @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
|
|
232
|
+
*/
|
|
233
|
+
getTrailerField(tag) {
|
|
234
|
+
return this.#native.getTrailerField(tag);
|
|
235
|
+
}
|
|
236
|
+
/** Set a trailer field. Returns `this` for chaining. */
|
|
237
|
+
setTrailerField(tag, value) {
|
|
238
|
+
this.#native.setTrailerField(tag, formatFieldValue(value));
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
/** The message type (tag 35), e.g. `'A'` for Logon. */
|
|
242
|
+
getMsgType() {
|
|
243
|
+
return this.#native.getMsgType();
|
|
244
|
+
}
|
|
245
|
+
/** The raw FIX wire string (SOH-delimited). */
|
|
246
|
+
toString() {
|
|
247
|
+
return this.#native.toString();
|
|
248
|
+
}
|
|
249
|
+
/** A human-readable, pretty-printed rendering of the message. */
|
|
250
|
+
toPretty() {
|
|
251
|
+
return this.#native.toPretty();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A best-effort structured view. Always includes `raw`; includes `msgType`
|
|
255
|
+
* when resolvable.
|
|
256
|
+
*/
|
|
257
|
+
toJSON() {
|
|
258
|
+
const raw = this.toString();
|
|
259
|
+
let msgType;
|
|
260
|
+
try {
|
|
261
|
+
msgType = this.getMsgType();
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
msgType = undefined;
|
|
265
|
+
}
|
|
266
|
+
return msgType === undefined ? { raw } : { msgType, raw };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create a message, optionally pre-populating body fields.
|
|
271
|
+
*
|
|
272
|
+
* @param fields Map of tag → value to set on the message body.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const msg = createMessage({ [FIELD.MsgType]: MsgType.Logon, [FIELD.HeartBtInt]: 30 });
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function createMessage(fields) {
|
|
280
|
+
const msg = new Message();
|
|
281
|
+
if (fields) {
|
|
282
|
+
for (const [tag, value] of Object.entries(fields)) {
|
|
283
|
+
msg.setField(Number(tag), value);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return msg;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Parse a raw FIX wire string into a {@link Message}. Alias of {@link Message.parse}.
|
|
290
|
+
*/
|
|
291
|
+
export function parseMessage(raw, opts) {
|
|
292
|
+
return Message.parse(raw, opts);
|
|
293
|
+
}
|