@homaiohq/napi-quickfix 0.1.0 → 0.3.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.
Files changed (145) hide show
  1. package/CMakeLists.txt +5 -1
  2. package/README.md +344 -18
  3. package/cpp/acceptor_wrap.cpp +253 -19
  4. package/cpp/acceptor_wrap.h +60 -1
  5. package/cpp/addon.cpp +10 -6
  6. package/cpp/application_bridge.cpp +114 -30
  7. package/cpp/application_bridge.h +57 -4
  8. package/cpp/data_dictionary_wrap.cpp +89 -3
  9. package/cpp/data_dictionary_wrap.h +27 -5
  10. package/cpp/engine_common.h +108 -0
  11. package/cpp/engine_workers.h +99 -18
  12. package/cpp/field_map_ops.h +492 -0
  13. package/cpp/group_wrap.cpp +221 -0
  14. package/cpp/group_wrap.h +76 -0
  15. package/cpp/initiator_wrap.cpp +262 -25
  16. package/cpp/initiator_wrap.h +37 -2
  17. package/cpp/message_wrap.cpp +171 -78
  18. package/cpp/message_wrap.h +47 -5
  19. package/cpp/session_op_gate.h +201 -0
  20. package/cpp/session_static.cpp +139 -12
  21. package/cpp/session_wrap.cpp +438 -0
  22. package/cpp/session_wrap.h +144 -0
  23. package/dist/cjs/acceptor.d.ts +0 -1
  24. package/dist/cjs/acceptor.js +0 -1
  25. package/dist/cjs/application.d.ts +0 -1
  26. package/dist/cjs/application.js +0 -1
  27. package/dist/cjs/data-dictionary.d.ts +21 -3
  28. package/dist/cjs/data-dictionary.js +32 -4
  29. package/dist/cjs/engine.d.ts +42 -4
  30. package/dist/cjs/engine.js +50 -5
  31. package/dist/cjs/enums.d.ts +48 -14
  32. package/dist/cjs/enums.js +20 -25
  33. package/dist/cjs/field-value.d.ts +12 -0
  34. package/dist/cjs/field-value.js +40 -0
  35. package/dist/cjs/generated/fields.d.ts +0 -1
  36. package/dist/cjs/generated/fields.js +0 -1
  37. package/dist/cjs/generated/values.d.ts +8548 -0
  38. package/dist/cjs/generated/values.js +8580 -0
  39. package/dist/cjs/group.d.ts +126 -0
  40. package/dist/cjs/group.js +185 -0
  41. package/dist/cjs/index.d.ts +28 -5
  42. package/dist/cjs/index.js +30 -28
  43. package/dist/cjs/initiator.d.ts +0 -1
  44. package/dist/cjs/initiator.js +0 -1
  45. package/dist/cjs/message.d.ts +129 -5
  46. package/dist/cjs/message.js +146 -9
  47. package/dist/cjs/native.d.ts +179 -17
  48. package/dist/cjs/native.js +0 -1
  49. package/dist/cjs/session-id.d.ts +12 -1
  50. package/dist/cjs/session-id.js +21 -1
  51. package/dist/cjs/session-settings.d.ts +0 -1
  52. package/dist/cjs/session-settings.js +0 -1
  53. package/dist/cjs/session.d.ts +213 -0
  54. package/dist/cjs/session.js +337 -0
  55. package/dist/cjs/tls-tunnel.d.ts +143 -0
  56. package/dist/cjs/tls-tunnel.js +261 -0
  57. package/dist/esm/acceptor.d.ts +0 -1
  58. package/dist/esm/acceptor.js +0 -1
  59. package/dist/esm/application.d.ts +0 -1
  60. package/dist/esm/application.js +0 -1
  61. package/dist/esm/data-dictionary.d.ts +21 -3
  62. package/dist/esm/data-dictionary.js +32 -4
  63. package/dist/esm/engine.d.ts +42 -4
  64. package/dist/esm/engine.js +51 -6
  65. package/dist/esm/enums.d.ts +48 -14
  66. package/dist/esm/enums.js +14 -24
  67. package/dist/esm/field-value.d.ts +12 -0
  68. package/dist/esm/field-value.js +37 -0
  69. package/dist/esm/generated/fields.d.ts +0 -1
  70. package/dist/esm/generated/fields.js +0 -1
  71. package/dist/esm/generated/values.d.ts +8548 -0
  72. package/dist/esm/generated/values.js +8564 -0
  73. package/dist/esm/group.d.ts +126 -0
  74. package/dist/esm/group.js +181 -0
  75. package/dist/esm/index.d.ts +28 -5
  76. package/dist/esm/index.js +18 -26
  77. package/dist/esm/initiator.d.ts +0 -1
  78. package/dist/esm/initiator.js +0 -1
  79. package/dist/esm/message.d.ts +129 -5
  80. package/dist/esm/message.js +146 -9
  81. package/dist/esm/native.d.ts +179 -17
  82. package/dist/esm/native.js +0 -1
  83. package/dist/esm/session-id.d.ts +12 -1
  84. package/dist/esm/session-id.js +20 -1
  85. package/dist/esm/session-settings.d.ts +0 -1
  86. package/dist/esm/session-settings.js +0 -1
  87. package/dist/esm/session.d.ts +213 -0
  88. package/dist/esm/session.js +329 -0
  89. package/dist/esm/tls-tunnel.d.ts +143 -0
  90. package/dist/esm/tls-tunnel.js +256 -0
  91. package/package.json +13 -1
  92. package/prebuilds/darwin-arm64/node.napi.node +0 -0
  93. package/prebuilds/darwin-x64/node.napi.node +0 -0
  94. package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
  95. package/prebuilds/linux-x64/node.napi.musl.node +0 -0
  96. package/prebuilds/win32-x64/node.napi.node +0 -0
  97. package/cpp/enums.cpp +0 -66
  98. package/dist/cjs/acceptor.d.ts.map +0 -1
  99. package/dist/cjs/acceptor.js.map +0 -1
  100. package/dist/cjs/application.d.ts.map +0 -1
  101. package/dist/cjs/application.js.map +0 -1
  102. package/dist/cjs/data-dictionary.d.ts.map +0 -1
  103. package/dist/cjs/data-dictionary.js.map +0 -1
  104. package/dist/cjs/engine.d.ts.map +0 -1
  105. package/dist/cjs/engine.js.map +0 -1
  106. package/dist/cjs/enums.d.ts.map +0 -1
  107. package/dist/cjs/enums.js.map +0 -1
  108. package/dist/cjs/generated/fields.d.ts.map +0 -1
  109. package/dist/cjs/generated/fields.js.map +0 -1
  110. package/dist/cjs/index.d.ts.map +0 -1
  111. package/dist/cjs/index.js.map +0 -1
  112. package/dist/cjs/initiator.d.ts.map +0 -1
  113. package/dist/cjs/initiator.js.map +0 -1
  114. package/dist/cjs/message.d.ts.map +0 -1
  115. package/dist/cjs/message.js.map +0 -1
  116. package/dist/cjs/native.d.ts.map +0 -1
  117. package/dist/cjs/native.js.map +0 -1
  118. package/dist/cjs/session-id.d.ts.map +0 -1
  119. package/dist/cjs/session-id.js.map +0 -1
  120. package/dist/cjs/session-settings.d.ts.map +0 -1
  121. package/dist/cjs/session-settings.js.map +0 -1
  122. package/dist/esm/acceptor.d.ts.map +0 -1
  123. package/dist/esm/acceptor.js.map +0 -1
  124. package/dist/esm/application.d.ts.map +0 -1
  125. package/dist/esm/application.js.map +0 -1
  126. package/dist/esm/data-dictionary.d.ts.map +0 -1
  127. package/dist/esm/data-dictionary.js.map +0 -1
  128. package/dist/esm/engine.d.ts.map +0 -1
  129. package/dist/esm/engine.js.map +0 -1
  130. package/dist/esm/enums.d.ts.map +0 -1
  131. package/dist/esm/enums.js.map +0 -1
  132. package/dist/esm/generated/fields.d.ts.map +0 -1
  133. package/dist/esm/generated/fields.js.map +0 -1
  134. package/dist/esm/index.d.ts.map +0 -1
  135. package/dist/esm/index.js.map +0 -1
  136. package/dist/esm/initiator.d.ts.map +0 -1
  137. package/dist/esm/initiator.js.map +0 -1
  138. package/dist/esm/message.d.ts.map +0 -1
  139. package/dist/esm/message.js.map +0 -1
  140. package/dist/esm/native.d.ts.map +0 -1
  141. package/dist/esm/native.js.map +0 -1
  142. package/dist/esm/session-id.d.ts.map +0 -1
  143. package/dist/esm/session-id.js.map +0 -1
  144. package/dist/esm/session-settings.d.ts.map +0 -1
  145. package/dist/esm/session-settings.js.map +0 -1
@@ -2,6 +2,8 @@
2
2
  * Ergonomic wrapper around the native `FIX::Message`.
3
3
  */
4
4
  import { type NativeMessage } from './native.js';
5
+ import { Group } from './group.js';
6
+ import type { DataDictionary } from './data-dictionary.js';
5
7
  /** Options controlling how a raw FIX string is parsed into a {@link Message}. */
6
8
  export interface MessageParseOptions {
7
9
  /**
@@ -11,6 +13,26 @@ export interface MessageParseOptions {
11
13
  * {@link DataDictionary} instead.
12
14
  */
13
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;
14
36
  }
15
37
  /** A structured, best-effort view of a parsed message returned by {@link Message.toJSON}. */
16
38
  export interface MessageJSON {
@@ -23,7 +45,9 @@ export interface MessageJSON {
23
45
  * A FIX message.
24
46
  *
25
47
  * Field values are accepted as `string | number` on input (numbers are
26
- * stringified, since FIX is string-on-the-wire) and always returned as `string`.
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`.
27
51
  *
28
52
  * @example
29
53
  * ```ts
@@ -36,6 +60,12 @@ export interface MessageJSON {
36
60
  *
37
61
  * const parsed = Message.parse(msg.toString());
38
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'
39
69
  * ```
40
70
  */
41
71
  export declare class Message {
@@ -55,8 +85,18 @@ export declare class Message {
55
85
  /**
56
86
  * Parse a raw FIX wire string into a {@link Message}.
57
87
  *
88
+ * Pass a `dictionary` to parse repeating groups structurally; without one
89
+ * the parse is flat (see {@link MessageParseOptions}).
90
+ *
58
91
  * @param raw Raw FIX wire string (SOH-delimited).
59
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
+ * ```
60
100
  */
61
101
  static parse(raw: string, opts?: MessageParseOptions): Message;
62
102
  /** @internal Wrap an existing native message handle (used by the engine bridge). */
@@ -64,12 +104,97 @@ export declare class Message {
64
104
  /** @internal The native handle backing this message (for the engine / sendToTarget / validation). */
65
105
  get nativeHandle(): NativeMessage;
66
106
  /**
67
- * Read a body field by tag.
68
- * @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
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.
69
114
  */
70
115
  getField(tag: number): string;
71
- /** Set a body field. Returns `this` for chaining. */
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
+ */
72
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;
73
198
  /**
74
199
  * Read a header field by tag.
75
200
  * @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
@@ -111,4 +236,3 @@ export declare function createMessage(fields?: Record<number, string | number>):
111
236
  * Parse a raw FIX wire string into a {@link Message}. Alias of {@link Message.parse}.
112
237
  */
113
238
  export declare function parseMessage(raw: string, opts?: MessageParseOptions): Message;
114
- //# sourceMappingURL=message.d.ts.map
@@ -7,11 +7,15 @@ exports.parseMessage = parseMessage;
7
7
  * Ergonomic wrapper around the native `FIX::Message`.
8
8
  */
9
9
  const native_js_1 = require("./native.js");
10
+ const group_js_1 = require("./group.js");
11
+ const field_value_js_1 = require("./field-value.js");
10
12
  /**
11
13
  * A FIX message.
12
14
  *
13
15
  * Field values are accepted as `string | number` on input (numbers are
14
- * stringified, since FIX is string-on-the-wire) and always returned as `string`.
16
+ * stringified, since FIX is string-on-the-wire, without exponent notation:
17
+ * `1e-7` is written `0.0000001`) and always returned as `string`. `NaN` and
18
+ * `±Infinity` throw a `RangeError`.
15
19
  *
16
20
  * @example
17
21
  * ```ts
@@ -24,6 +28,12 @@ const native_js_1 = require("./native.js");
24
28
  *
25
29
  * const parsed = Message.parse(msg.toString());
26
30
  * console.log(parsed.getMsgType());
31
+ *
32
+ * // Repeating groups: build entries with Group, read them back by 1-based index.
33
+ * msg.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'TRADER-1'));
34
+ * const dd = DataDictionary.fromFile('FIX44.xml');
35
+ * const withGroups = Message.parse(msg.toString(), { dictionary: dd });
36
+ * withGroups.getGroup(1, FIELD.NoPartyIDs).getField(FIELD.PartyID); // 'TRADER-1'
27
37
  * ```
28
38
  */
29
39
  class Message {
@@ -45,7 +55,9 @@ class Message {
45
55
  this.#native = new native_js_1.native.MessageWrap();
46
56
  }
47
57
  else if (typeof raw === 'string') {
48
- this.#native = new native_js_1.native.MessageWrap(raw, opts?.validate ?? false);
58
+ const sessionDD = opts?.sessionDictionary ?? opts?.dictionary;
59
+ const appDD = opts?.applicationDictionary ?? opts?.dictionary;
60
+ this.#native = new native_js_1.native.MessageWrap(raw, opts?.validate ?? false, sessionDD?.nativeHandle ?? null, appDD?.nativeHandle ?? null);
49
61
  }
50
62
  else {
51
63
  // Adopt an existing native handle (bridge path).
@@ -55,8 +67,18 @@ class Message {
55
67
  /**
56
68
  * Parse a raw FIX wire string into a {@link Message}.
57
69
  *
70
+ * Pass a `dictionary` to parse repeating groups structurally; without one
71
+ * the parse is flat (see {@link MessageParseOptions}).
72
+ *
58
73
  * @param raw Raw FIX wire string (SOH-delimited).
59
74
  * @param opts Optional parse options.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const dd = DataDictionary.fromFile('./spec/FIX44.xml');
79
+ * const msg = Message.parse(raw, { dictionary: dd });
80
+ * msg.getGroup(1, FIELD.NoPartyIDs).getField(FIELD.PartyID);
81
+ * ```
60
82
  */
61
83
  static parse(raw, opts) {
62
84
  return new Message(raw, opts);
@@ -70,17 +92,133 @@ class Message {
70
92
  return this.#native;
71
93
  }
72
94
  /**
73
- * Read a body field by tag.
74
- * @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
95
+ * Read a field by tag. Standard header/trailer tags (`MsgType`,
96
+ * `SenderCompID`, `CheckSum`, ...) are read from their section, and so are
97
+ * the header/trailer fields of the session dictionary the message was
98
+ * parsed with (`sessionDictionary` / `dictionary`) or delivered with (a
99
+ * message handed to an engine handler). Any other tag is read from the body,
100
+ * or from the header or trailer if that is where it is.
101
+ * @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) if absent.
75
102
  */
76
103
  getField(tag) {
77
104
  return this.#native.getField(tag);
78
105
  }
79
- /** Set a body field. Returns `this` for chaining. */
106
+ /**
107
+ * Set a field. Standard header/trailer tags and the session dictionary's
108
+ * header/trailer fields auto-route to their section (see
109
+ * {@link Message.getField}). Any other tag is updated in place if the header
110
+ * or trailer already holds it; otherwise it goes to the body. On a message
111
+ * without a session dictionary (built with `new Message()`), put a new
112
+ * custom header field in the header with {@link Message.setHeaderField}.
113
+ * Returns `this` for chaining.
114
+ */
80
115
  setField(tag, value) {
81
- this.#native.setField(tag, String(value));
116
+ this.#native.setField(tag, (0, field_value_js_1.formatFieldValue)(value));
117
+ return this;
118
+ }
119
+ /** Whether a field is present (same auto-routing as {@link Message.getField}). */
120
+ isSetField(tag) {
121
+ return this.#native.isSetField(tag);
122
+ }
123
+ /** Remove a field (no-op if absent; same auto-routing). Returns `this` for chaining. */
124
+ removeField(tag) {
125
+ this.#native.removeField(tag);
126
+ return this;
127
+ }
128
+ /** Read a field by tag, or `undefined` if absent (same auto-routing). */
129
+ getFieldIfSet(tag) {
130
+ return this.#native.getFieldIfSet(tag);
131
+ }
132
+ /** Whether the header, body and trailer have no fields of their own. */
133
+ isEmpty() {
134
+ return this.#native.isEmpty();
135
+ }
136
+ /** The number of body fields, including every field of every group entry. */
137
+ totalFields() {
138
+ return this.#native.totalFields();
139
+ }
140
+ /** Remove every header, body and trailer field and group. Returns `this` for chaining. */
141
+ clear() {
142
+ this.#native.clear();
143
+ return this;
144
+ }
145
+ /** The body's own fields, in wire order, as `[tag, value]` pairs (group entries excluded). */
146
+ fields() {
147
+ return this.#native.fields();
148
+ }
149
+ /** The header fields, in wire order, as `[tag, value]` pairs. */
150
+ headerFields() {
151
+ return this.#native.headerFields();
152
+ }
153
+ /** The trailer fields, in wire order, as `[tag, value]` pairs. */
154
+ trailerFields() {
155
+ return this.#native.trailerFields();
156
+ }
157
+ /** Iterates the body's own fields as `[tag, value]` pairs (see {@link Message.fields}). */
158
+ [Symbol.iterator]() {
159
+ return this.fields()[Symbol.iterator]();
160
+ }
161
+ /**
162
+ * Append a repeating-group entry under its count tag (`group.field`). A copy
163
+ * is stored and the count field is updated. Header groups (e.g. `NoHops`)
164
+ * auto-route to the header. Returns `this` for chaining.
165
+ *
166
+ * @throws `TypeError` when `group.delim` differs from the delimiter of the
167
+ * entries already stored under `group.field`.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * order.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'X'));
172
+ * ```
173
+ */
174
+ addGroup(group) {
175
+ this.#native.addGroup(group.nativeHandle);
176
+ return this;
177
+ }
178
+ /**
179
+ * A **snapshot copy** of the `index`-th entry (1-based) of the repeating
180
+ * group `tag`. Mutating the returned {@link Group} does not change this
181
+ * message; write it back with {@link Message.replaceGroup}.
182
+ *
183
+ * Groups exist only on messages built with {@link Message.addGroup} or
184
+ * parsed with a dictionary, which includes every message the engine hands
185
+ * to your handlers when the session has `UseDataDictionary=Y` (the
186
+ * default); a flat parse, or `UseDataDictionary=N`, leaves none.
187
+ *
188
+ * @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when the tag
189
+ * has no entries or `index` is out of range.
190
+ */
191
+ getGroup(index, tag) {
192
+ return group_js_1.Group.fromNative(this.#native.getGroup(index, tag));
193
+ }
194
+ /**
195
+ * Overwrite the `index`-th entry (1-based) under `group.field` with a copy of
196
+ * `group`. Returns `this` for chaining.
197
+ * @throws A `QuickFixError` (`fixErrorName: 'FieldNotFound'`) when that
198
+ * entry does not exist.
199
+ * @throws `TypeError` when `group.delim` differs from the stored entry's
200
+ * delimiter.
201
+ */
202
+ replaceGroup(index, group) {
203
+ this.#native.replaceGroup(index, group.nativeHandle);
204
+ return this;
205
+ }
206
+ removeGroup(a, b) {
207
+ if (b === undefined) {
208
+ this.#native.removeGroup(a);
209
+ }
210
+ else {
211
+ this.#native.removeGroup(a, b);
212
+ }
82
213
  return this;
83
214
  }
215
+ hasGroup(a, b) {
216
+ return b === undefined ? this.#native.hasGroup(a) : this.#native.hasGroup(a, b);
217
+ }
218
+ /** The number of entries of the repeating group `tag` (0 if none). */
219
+ groupCount(tag) {
220
+ return this.#native.groupCount(tag);
221
+ }
84
222
  /**
85
223
  * Read a header field by tag.
86
224
  * @throws A `QuickFixError` (`fixError: 'FieldNotFound'`) if absent.
@@ -90,7 +228,7 @@ class Message {
90
228
  }
91
229
  /** Set a header field. Returns `this` for chaining. */
92
230
  setHeaderField(tag, value) {
93
- this.#native.setHeaderField(tag, String(value));
231
+ this.#native.setHeaderField(tag, (0, field_value_js_1.formatFieldValue)(value));
94
232
  return this;
95
233
  }
96
234
  /**
@@ -102,7 +240,7 @@ class Message {
102
240
  }
103
241
  /** Set a trailer field. Returns `this` for chaining. */
104
242
  setTrailerField(tag, value) {
105
- this.#native.setTrailerField(tag, String(value));
243
+ this.#native.setTrailerField(tag, (0, field_value_js_1.formatFieldValue)(value));
106
244
  return this;
107
245
  }
108
246
  /** The message type (tag 35), e.g. `'A'` for Logon. */
@@ -159,4 +297,3 @@ function createMessage(fields) {
159
297
  function parseMessage(raw, opts) {
160
298
  return Message.parse(raw, opts);
161
299
  }
162
- //# sourceMappingURL=message.js.map
@@ -1,14 +1,44 @@
1
1
  /**
2
- * Native `FIX::Message` wrapper. FIX is string-on-the-wire, so every field
3
- * value crosses the boundary as a string.
2
+ * The field and repeating-group operations every `FIX::FieldMap` exposes.
3
+ * Shared by the message body and by group entries. Group indices are 1-based,
4
+ * as in QuickFIX; `getGroup` returns a snapshot copy.
4
5
  */
5
- export interface NativeMessage {
6
+ export interface NativeFieldMap {
6
7
  getField(tag: number): string;
7
8
  setField(tag: number, value: string): void;
9
+ isSetField(tag: number): boolean;
10
+ removeField(tag: number): void;
11
+ getFieldIfSet(tag: number): string | undefined;
12
+ /** Own fields plus every field of every nested group entry. */
13
+ totalFields(): number;
14
+ isEmpty(): boolean;
15
+ clear(): void;
16
+ /** Own fields, in wire order, as `[tag, value]` pairs. */
17
+ fields(): [number, string][];
18
+ addGroup(group: NativeGroup): void;
19
+ /** @throws A {@link QuickFixError} (`FieldNotFound`) when absent / out of range. */
20
+ getGroup(index: number, tag: number): NativeGroup;
21
+ /** @throws A {@link QuickFixError} (`FieldNotFound`) when the slot does not exist. */
22
+ replaceGroup(index: number, group: NativeGroup): void;
23
+ removeGroup(tag: number): void;
24
+ removeGroup(index: number, tag: number): void;
25
+ hasGroup(tag: number): boolean;
26
+ hasGroup(index: number, tag: number): boolean;
27
+ groupCount(tag: number): number;
28
+ }
29
+ /**
30
+ * Native `FIX::Message` wrapper. FIX is string-on-the-wire, so every field
31
+ * value crosses the boundary as a string. The {@link NativeFieldMap} methods
32
+ * auto-route well-known header/trailer tags to the right section; `fields()`,
33
+ * `isEmpty()` and `totalFields()` read the body, `clear()` empties everything.
34
+ */
35
+ export interface NativeMessage extends NativeFieldMap {
8
36
  getHeaderField(tag: number): string;
9
37
  setHeaderField(tag: number, value: string): void;
10
38
  getTrailerField(tag: number): string;
11
39
  setTrailerField(tag: number, value: string): void;
40
+ headerFields(): [number, string][];
41
+ trailerFields(): [number, string][];
12
42
  getMsgType(): string;
13
43
  toString(): string;
14
44
  toPretty(): string;
@@ -16,7 +46,29 @@ export interface NativeMessage {
16
46
  /** Constructor shape for the native `MessageWrap` class. */
17
47
  export interface NativeMessageConstructor {
18
48
  new (): NativeMessage;
19
- new (raw: string, validate?: boolean): NativeMessage;
49
+ /**
50
+ * Parse `raw`. With a dictionary the parse is structural (repeating groups
51
+ * become nested groups); without one it is flat. Admin messages always use
52
+ * the session dictionary; application messages use the application one.
53
+ */
54
+ new (raw: string, validate?: boolean, sessionDictionary?: NativeDataDictionary | null, applicationDictionary?: NativeDataDictionary | null): NativeMessage;
55
+ }
56
+ /** Native `FIX::Group` wrapper: one entry of a repeating group. */
57
+ export interface NativeGroup extends NativeFieldMap {
58
+ /** The group's count tag (e.g. 453 NoPartyIDs). */
59
+ field(): number;
60
+ /** The first tag of every entry (e.g. 448 PartyID). */
61
+ delim(): number;
62
+ /** The entry's fields (and nested groups) as a SOH-delimited string, in wire order. */
63
+ toString(): string;
64
+ }
65
+ /** Constructor shape for the native `GroupWrap` class. */
66
+ export interface NativeGroupConstructor {
67
+ /**
68
+ * @param order Optional full field order of an entry, delimiter first, no
69
+ * zeros. Without it, fields sort delimiter-first then by tag number.
70
+ */
71
+ new (field: number, delim: number, order?: readonly number[] | null): NativeGroup;
20
72
  }
21
73
  /** Native `FIX::SessionID` wrapper. */
22
74
  export interface NativeSessionID {
@@ -77,8 +129,19 @@ export interface QuickFixError extends Error {
77
129
  }
78
130
  /** Native `FIX::DataDictionary` wrapper. */
79
131
  export interface NativeDataDictionary {
80
- /** Throws a {@link QuickFixError} if the message is invalid. */
81
- validate(message: NativeMessage): void;
132
+ /**
133
+ * Throws a {@link QuickFixError} if the message is invalid. With `bodyOnly`
134
+ * the BeginString version check and the tag/value validation of the
135
+ * header/trailer fields are skipped; required header/trailer fields are
136
+ * still checked.
137
+ */
138
+ validate(message: NativeMessage, bodyOnly?: boolean): void;
139
+ /** The BeginString the spec declares (e.g. `'FIX.4.4'`), `''` if none. */
140
+ getVersion(): string;
141
+ getFieldName(tag: number): string | undefined;
142
+ getFieldTag(name: string): number | undefined;
143
+ isField(tag: number): boolean;
144
+ isMsgType(msgType: string): boolean;
82
145
  }
83
146
  /**
84
147
  * Constructor shape for the native `DataDictionaryWrap` class. `fromString` /
@@ -111,18 +174,105 @@ export interface NativeEngineOptions {
111
174
  store?: 'file' | 'memory';
112
175
  log?: 'screen' | 'file' | 'none';
113
176
  }
177
+ /**
178
+ * Native handle on a live `FIX::Session`.
179
+ *
180
+ * The handle stores only the `FIX::SessionID`; every method re-resolves the
181
+ * engine-owned session via `FIX::Session::lookupSession` and throws a
182
+ * {@link QuickFixError} with `fixErrorName: 'SessionNotFound'` once the owning
183
+ * engine has been stopped.
184
+ *
185
+ * Every member is synchronous: none takes the session mutex QuickFIX holds
186
+ * across application callbacks, so they cannot deadlock the main thread
187
+ * (`logon()`/`logout()` write a plain flag the network thread reads without a
188
+ * lock, as QuickFIX's own `Initiator::stop()` does).
189
+ * `FIX::Session::disconnect()`/`reset()` are not exposed; they are only safe
190
+ * on the engine's own network thread (see `cpp/session_wrap.cpp`).
191
+ */
192
+ export interface NativeSession {
193
+ getSessionID(): NativeSessionID;
194
+ isLoggedOn(): boolean;
195
+ isEnabled(): boolean;
196
+ sentLogon(): boolean;
197
+ sentLogout(): boolean;
198
+ receivedLogon(): boolean;
199
+ isInitiator(): boolean;
200
+ isAcceptor(): boolean;
201
+ /** `nowMs` is an epoch-millisecond timestamp; omitted means "now". */
202
+ isSessionTime(nowMs?: number): boolean;
203
+ isLogonTime(nowMs?: number): boolean;
204
+ getExpectedSenderNum(): number;
205
+ getExpectedTargetNum(): number;
206
+ setNextSenderMsgSeqNum(seqNum: number): void;
207
+ setNextTargetMsgSeqNum(seqNum: number): void;
208
+ getResetOnLogon(): boolean;
209
+ setResetOnLogon(value: boolean): void;
210
+ getResetOnLogout(): boolean;
211
+ setResetOnLogout(value: boolean): void;
212
+ getResetOnDisconnect(): boolean;
213
+ setResetOnDisconnect(value: boolean): void;
214
+ getRefreshOnLogon(): boolean;
215
+ setRefreshOnLogon(value: boolean): void;
216
+ getCheckCompId(): boolean;
217
+ setCheckCompId(value: boolean): void;
218
+ getCheckLatency(): boolean;
219
+ setCheckLatency(value: boolean): void;
220
+ getPersistMessages(): boolean;
221
+ setPersistMessages(value: boolean): void;
222
+ getSendRedundantResendRequests(): boolean;
223
+ setSendRedundantResendRequests(value: boolean): void;
224
+ getValidateLengthAndChecksum(): boolean;
225
+ setValidateLengthAndChecksum(value: boolean): void;
226
+ getSendNextExpectedMsgSeqNum(): boolean;
227
+ setSendNextExpectedMsgSeqNum(value: boolean): void;
228
+ getIsNonStopSession(): boolean;
229
+ setIsNonStopSession(value: boolean): void;
230
+ getLogonTimeout(): number;
231
+ setLogonTimeout(seconds: number): void;
232
+ getLogoutTimeout(): number;
233
+ setLogoutTimeout(seconds: number): void;
234
+ getMaxLatency(): number;
235
+ setMaxLatency(seconds: number): void;
236
+ getTimestampPrecision(): number;
237
+ /** Throws a `RangeError` unless `precision` is an integer in `0..9`. */
238
+ setTimestampPrecision(precision: number): void;
239
+ logon(): void;
240
+ logout(reason?: string): void;
241
+ /** May throw a `QuickFixError` (`IOException`) from the message store. */
242
+ refresh(): void;
243
+ }
244
+ /**
245
+ * Constructor shape for the native `SessionWrap` class. Internal: JS obtains a
246
+ * session through {@link NativeModule.lookupSession} or
247
+ * {@link NativeEngine.getSession}, never by constructing one.
248
+ */
249
+ export interface NativeSessionConstructor {
250
+ new (sessionID: NativeSessionID): NativeSession;
251
+ }
114
252
  /**
115
253
  * Native engine (SocketInitiator / SocketAcceptor) wrapper.
116
254
  *
117
255
  * `start`/`stop` run OFF the main thread and resolve a Promise when the engine
118
256
  * has finished (re)configuring its sockets, so the Node.js event loop stays free
119
257
  * to service the application callbacks the engine fires. `isLoggedOn`/`ref`/
120
- * `unref` remain synchronous.
258
+ * `unref`/`getSessions`/`getSession` remain synchronous.
259
+ *
260
+ * Once `stop()` settles the engine is destroyed, which also destroys its
261
+ * `FIX::Session` objects: `getSession` then returns `undefined` and any
262
+ * previously obtained {@link NativeSession} throws `SessionNotFound`.
121
263
  */
122
264
  export interface NativeEngine {
123
265
  start(): Promise<void>;
124
266
  stop(force?: boolean): Promise<void>;
125
- isLoggedOn(): boolean;
267
+ /**
268
+ * No argument: whether ANY of this engine's sessions is logged on. With a
269
+ * SessionID: whether THAT session belongs to this engine and is logged on.
270
+ */
271
+ isLoggedOn(sessionID?: NativeSessionID): boolean;
272
+ /** The SessionIDs this engine was configured with (stable across stop()). */
273
+ getSessions(): NativeSessionID[];
274
+ /** `undefined` if the id is not one of this engine's sessions or stop() has settled. */
275
+ getSession(sessionID: NativeSessionID): NativeSession | undefined;
126
276
  ref(): void;
127
277
  unref(): void;
128
278
  }
@@ -131,22 +281,35 @@ export interface NativeEngineConstructor {
131
281
  new (options: NativeEngineOptions): NativeEngine;
132
282
  }
133
283
  /**
134
- * The nested FIX-constants object exported by the addon, e.g.
135
- * `enums.MsgType.Logon`, `enums.Side.Buy`. Field tags (`FIELD`) are not exported
136
- * by the addon; they are generated on the TS side (`src/generated/fields.ts`).
284
+ * The full surface of the native addon exported by `load-native.cjs`.
285
+ *
286
+ * FIX constants are not exported by the addon: field tags (`FIELD`) and value
287
+ * groups (`MsgType`, `Side`, ...) are generated on the TS side from the
288
+ * QuickFIX headers (`src/generated/fields.ts`, `src/generated/values.ts`).
137
289
  */
138
- export type NativeEnums = Readonly<Record<string, Readonly<Record<string, number | string>>>>;
139
- /** The full surface of the native addon exported by `load-native.cjs`. */
140
290
  export interface NativeModule {
141
291
  version(): string;
142
292
  quickfixParseMsgType(raw: string): string;
143
- /** Runs off the main thread; resolves `true` if accepted for sending. */
144
- sendToTarget(message: NativeMessage, sessionID: NativeSessionID): Promise<boolean>;
145
- enums: NativeEnums;
293
+ /**
294
+ * Runs off the main thread; resolves `true` if accepted for sending.
295
+ *
296
+ * Second argument: an explicit SessionID, or a session qualifier (possibly
297
+ * omitted/empty), in which case QuickFIX resolves the session from the
298
+ * message's own header (BeginString / SenderCompID / TargetCompID).
299
+ */
300
+ sendToTarget(message: NativeMessage, sessionIDOrQualifier?: NativeSessionID | string): Promise<boolean>;
301
+ /** `undefined` if no session with that id exists in this process. */
302
+ lookupSession(sessionID: NativeSessionID): NativeSession | undefined;
303
+ doesSessionExist(sessionID: NativeSessionID): boolean;
304
+ /** Every session registered in this process (all engines). */
305
+ getSessions(): NativeSessionID[];
306
+ numSessions(): number;
146
307
  MessageWrap: NativeMessageConstructor;
308
+ GroupWrap: NativeGroupConstructor;
147
309
  SessionIDWrap: NativeSessionIDConstructor;
148
310
  SessionSettingsWrap: NativeSessionSettingsConstructor;
149
311
  DataDictionaryWrap: NativeDataDictionaryConstructor;
312
+ SessionWrap: NativeSessionConstructor;
150
313
  InitiatorWrap: NativeEngineConstructor;
151
314
  AcceptorWrap: NativeEngineConstructor;
152
315
  sessionSettingsFromString?(cfg: string): NativeSessionSettings;
@@ -160,4 +323,3 @@ export interface NativeModule {
160
323
  * assert it to the contract here — the single localized cast in the codebase.
161
324
  */
162
325
  export declare const native: NativeModule;
163
- //# sourceMappingURL=native.d.ts.map
@@ -20,4 +20,3 @@ const load_native_cjs_1 = __importDefault(require("./load-native.cjs"));
20
20
  * assert it to the contract here — the single localized cast in the codebase.
21
21
  */
22
22
  exports.native = load_native_cjs_1.default;
23
- //# sourceMappingURL=native.js.map
@@ -37,4 +37,15 @@ export declare class SessionID {
37
37
  /** The canonical string form, e.g. `FIX.4.4:CLIENT->BROKER`. */
38
38
  toString(): string;
39
39
  }
40
- //# sourceMappingURL=session-id.d.ts.map
40
+ /**
41
+ * @internal The native handle behind a {@link SessionID} argument.
42
+ *
43
+ * Duck-typed rather than `instanceof SessionID`: this package ships ESM and
44
+ * CJS builds, and a `SessionID` created by the other build is a different class
45
+ * with the same shape. Both builds load the same native addon, so its
46
+ * `SessionIDWrap` class is the one thing they share, and that is what the
47
+ * handle is checked against. Anything else -- `null`, `{}`, a `Session` -- is
48
+ * a `TypeError` naming the argument, rather than silently degrading to an
49
+ * `undefined` handle that the callee would read as "no session id given".
50
+ */
51
+ export declare function toNativeSessionID(value: unknown, name: string): NativeSessionID;