@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
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Ergonomic wrapper around a live native `FIX::Session`.
3
+ */
4
+ import { type NativeSession } from './native.js';
5
+ import { SessionID } from './session-id.js';
6
+ /**
7
+ * A handle on one live FIX session (wraps `FIX::Session`).
8
+ *
9
+ * Obtain one with {@link lookupSession} (any session in the process) or
10
+ * {@link Engine.getSession} (a session of a particular engine); sessions
11
+ * cannot be constructed directly.
12
+ *
13
+ * **Lifetime.** The underlying `FIX::Session` is owned by its engine. This
14
+ * handle stores only the {@link SessionID} and the identity of the engine it
15
+ * was obtained from, and re-resolves the session on every call, so it never
16
+ * dangles: once that engine has been stopped (which destroys its sessions)
17
+ * every method throws a {@link QuickFixError} whose `fixErrorName` is
18
+ * `'SessionNotFound'` -- also after a new engine has been created with the
19
+ * same id. A handle never crosses over to another engine's session; obtain a
20
+ * new one from the new engine.
21
+ *
22
+ * **Everything is synchronous.** State queries, sequence-number access, the
23
+ * runtime option getters/setters, {@link Session.logon}, {@link Session.logout}
24
+ * and {@link Session.refresh} never take the mutex QuickFIX holds while running
25
+ * your application handlers, so they cannot deadlock the main thread, and calls
26
+ * made in program order take effect in program order. ({@link Session.logon} /
27
+ * {@link Session.logout} flip a plain flag the engine's network thread reads
28
+ * without a lock, exactly as QuickFIX's own `Initiator::stop()` does; the
29
+ * engine picks the change up on its next pass.) QuickFIX's
30
+ * `Session::disconnect()` / `reset()` are deliberately not exposed: they are
31
+ * only safe on the engine's own network thread (see ARCHITECTURE.md §5). Use
32
+ * {@link Session.logout} to bring a session down, and the sequence-number
33
+ * setters to reset its numbering -- on a quiet session; see
34
+ * {@link Session.setNextSenderMsgSeqNum}.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const session = initiator.getSession(sessionID)!;
39
+ * if (session.isLoggedOn()) {
40
+ * console.log('next outbound seq', session.getExpectedSenderNum());
41
+ * session.logout('maintenance');
42
+ * }
43
+ * ```
44
+ */
45
+ export declare class Session {
46
+ #private;
47
+ /** @internal Use {@link lookupSession} / {@link Engine.getSession}. */
48
+ private constructor();
49
+ /** @internal Wrap an existing native session handle. */
50
+ static fromNative(handle: NativeSession): Session;
51
+ /** @internal The native handle backing this session. */
52
+ get nativeHandle(): NativeSession;
53
+ /** The id this handle refers to. Always available, even after the session is gone. */
54
+ get sessionID(): SessionID;
55
+ /** `true` once a Logon has been both sent and received. */
56
+ isLoggedOn(): boolean;
57
+ /**
58
+ * Whether the session is enabled. `false` after {@link Session.logout} until
59
+ * {@link Session.logon}; a disabled initiator session is not reconnected.
60
+ */
61
+ isEnabled(): boolean;
62
+ /** Whether this side has sent a Logon on the current connection. */
63
+ sentLogon(): boolean;
64
+ /** Whether this side has sent a Logout on the current connection. */
65
+ sentLogout(): boolean;
66
+ /** Whether a Logon has been received on the current connection. */
67
+ receivedLogon(): boolean;
68
+ /** `true` for a session owned by an {@link Initiator}. */
69
+ isInitiator(): boolean;
70
+ /** `true` for a session owned by an {@link Acceptor}. */
71
+ isAcceptor(): boolean;
72
+ /**
73
+ * Whether `now` (default: the current time) falls inside the session's
74
+ * configured `StartTime`/`EndTime` window. Throws `RangeError` for an
75
+ * invalid `Date`.
76
+ */
77
+ isSessionTime(now?: Date): boolean;
78
+ /**
79
+ * Whether `now` (default: the current time) falls inside the session's
80
+ * configured `LogonTime`/`LogoutTime` window. Throws `RangeError` for an
81
+ * invalid `Date`.
82
+ */
83
+ isLogonTime(now?: Date): boolean;
84
+ /** The MsgSeqNum (tag 34) the next outbound message will carry. */
85
+ getExpectedSenderNum(): number;
86
+ /** The MsgSeqNum (tag 34) expected on the next inbound message. */
87
+ getExpectedTargetNum(): number;
88
+ /**
89
+ * Set the MsgSeqNum the next outbound message will carry (writes to the
90
+ * message store; a file store may throw `IOException`).
91
+ *
92
+ * **Call it on a quiet session.** The setter is not serialised against a
93
+ * message the engine is sending or receiving on this session at the same
94
+ * time: QuickFIX reads the next number into the outbound header, runs your
95
+ * `toApp`/`toAdmin` handler, then increments from the store, all under a
96
+ * lock this call does not (and must not) take. A value set inside that
97
+ * window -- from a handler, or while a {@link sendToTarget} promise or a
98
+ * heartbeat is in flight on this session -- is overwritten by the increment,
99
+ * and the session ends up one off from its counterparty (a Resend Request or
100
+ * a "MsgSeqNum too low" Logout). Set it before {@link Engine.start}, from a
101
+ * `'logout'` event, or when you know nothing is in flight; never from a
102
+ * handler.
103
+ *
104
+ * @throws `RangeError` if `seqNum` is not a positive safe integer (MsgSeqNum is
105
+ * 1-based).
106
+ */
107
+ setNextSenderMsgSeqNum(seqNum: number): void;
108
+ /**
109
+ * Set the MsgSeqNum expected on the next inbound message (writes to the
110
+ * message store; a file store may throw `IOException`).
111
+ *
112
+ * **Call it on a quiet session.** Like {@link Session.setNextSenderMsgSeqNum},
113
+ * it is not serialised against a message the engine is processing on this
114
+ * session: QuickFIX checks the inbound number, runs your `fromApp`/`fromAdmin`
115
+ * handler, then increments the expected number from the store under a lock
116
+ * this call does not take, so a value set meanwhile is overwritten. Set it
117
+ * before {@link Engine.start}, from a `'logout'` event, or when you know
118
+ * nothing is in flight; never from a handler.
119
+ *
120
+ * @throws `RangeError` if `seqNum` is not a positive safe integer (MsgSeqNum is
121
+ * 1-based).
122
+ */
123
+ setNextTargetMsgSeqNum(seqNum: number): void;
124
+ /** `ResetOnLogon` */
125
+ getResetOnLogon(): boolean;
126
+ setResetOnLogon(value: boolean): void;
127
+ /** `ResetOnLogout` */
128
+ getResetOnLogout(): boolean;
129
+ setResetOnLogout(value: boolean): void;
130
+ /** `ResetOnDisconnect` */
131
+ getResetOnDisconnect(): boolean;
132
+ setResetOnDisconnect(value: boolean): void;
133
+ /** `RefreshOnLogon` */
134
+ getRefreshOnLogon(): boolean;
135
+ setRefreshOnLogon(value: boolean): void;
136
+ /** `CheckCompID` */
137
+ getCheckCompId(): boolean;
138
+ setCheckCompId(value: boolean): void;
139
+ /** `CheckLatency` */
140
+ getCheckLatency(): boolean;
141
+ setCheckLatency(value: boolean): void;
142
+ /** `PersistMessages` */
143
+ getPersistMessages(): boolean;
144
+ setPersistMessages(value: boolean): void;
145
+ /** `SendRedundantResendRequests` */
146
+ getSendRedundantResendRequests(): boolean;
147
+ setSendRedundantResendRequests(value: boolean): void;
148
+ /** `ValidateLengthAndChecksum` */
149
+ getValidateLengthAndChecksum(): boolean;
150
+ setValidateLengthAndChecksum(value: boolean): void;
151
+ /** `SendNextExpectedMsgSeqNum` */
152
+ getSendNextExpectedMsgSeqNum(): boolean;
153
+ setSendNextExpectedMsgSeqNum(value: boolean): void;
154
+ /** `NonStopSession` */
155
+ getIsNonStopSession(): boolean;
156
+ setIsNonStopSession(value: boolean): void;
157
+ /** `LogonTimeout`, in seconds. */
158
+ getLogonTimeout(): number;
159
+ /** @throws {RangeError} if `seconds` is not an integer in the int32 range. */
160
+ setLogonTimeout(seconds: number): void;
161
+ /** `LogoutTimeout`, in seconds. */
162
+ getLogoutTimeout(): number;
163
+ /** @throws {RangeError} if `seconds` is not an integer in the int32 range. */
164
+ setLogoutTimeout(seconds: number): void;
165
+ /** `MaxLatency`, in seconds. */
166
+ getMaxLatency(): number;
167
+ /** @throws {RangeError} if `seconds` is not an integer in the int32 range. */
168
+ setMaxLatency(seconds: number): void;
169
+ /** `TimestampPrecision` (number of sub-second digits, `0..9`). */
170
+ getTimestampPrecision(): number;
171
+ /** @throws `RangeError` unless `precision` is an integer in `0..9`. */
172
+ setTimestampPrecision(precision: number): void;
173
+ /**
174
+ * Enable the session. An initiator session reconnects and logs on at its
175
+ * next `ReconnectInterval`; an acceptor session accepts the next inbound
176
+ * Logon. Takes effect immediately ({@link Session.isEnabled} is `true` on
177
+ * return); observe the handshake through the engine's `'logon'` event.
178
+ */
179
+ logon(): void;
180
+ /**
181
+ * Disable the session and initiate a graceful Logout (sent with `reason` as
182
+ * tag 58 on the next engine tick). The session stays disabled — it will not
183
+ * reconnect — until {@link Session.logon}. Takes effect immediately
184
+ * ({@link Session.isEnabled} is `false` on return); observe completion
185
+ * through the engine's `'logout'` event.
186
+ */
187
+ logout(reason?: string): void;
188
+ /**
189
+ * Re-read the session state (sequence numbers etc.) from the message store.
190
+ *
191
+ * @throws {QuickFixError} `IOException` if the store cannot be read.
192
+ */
193
+ refresh(): void;
194
+ }
195
+ /**
196
+ * Look up a live session by id, across every engine in this process.
197
+ *
198
+ * @returns The {@link Session}, or `undefined` if no session with that id
199
+ * exists (never created, or its engine has been stopped).
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * const session = lookupSession(new SessionID('FIX.4.4', 'CLIENT', 'BROKER'));
204
+ * console.log(session?.isLoggedOn());
205
+ * ```
206
+ */
207
+ export declare function lookupSession(sessionID: SessionID): Session | undefined;
208
+ /** Whether a session with this id exists in this process. */
209
+ export declare function doesSessionExist(sessionID: SessionID): boolean;
210
+ /** The ids of every session in this process, across all engines. */
211
+ export declare function getSessions(): SessionID[];
212
+ /** The number of sessions in this process, across all engines. */
213
+ export declare function numSessions(): number;
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Ergonomic wrapper around a live native `FIX::Session`.
3
+ */
4
+ import { native } from './native.js';
5
+ import { SessionID, toNativeSessionID } from './session-id.js';
6
+ /**
7
+ * A handle on one live FIX session (wraps `FIX::Session`).
8
+ *
9
+ * Obtain one with {@link lookupSession} (any session in the process) or
10
+ * {@link Engine.getSession} (a session of a particular engine); sessions
11
+ * cannot be constructed directly.
12
+ *
13
+ * **Lifetime.** The underlying `FIX::Session` is owned by its engine. This
14
+ * handle stores only the {@link SessionID} and the identity of the engine it
15
+ * was obtained from, and re-resolves the session on every call, so it never
16
+ * dangles: once that engine has been stopped (which destroys its sessions)
17
+ * every method throws a {@link QuickFixError} whose `fixErrorName` is
18
+ * `'SessionNotFound'` -- also after a new engine has been created with the
19
+ * same id. A handle never crosses over to another engine's session; obtain a
20
+ * new one from the new engine.
21
+ *
22
+ * **Everything is synchronous.** State queries, sequence-number access, the
23
+ * runtime option getters/setters, {@link Session.logon}, {@link Session.logout}
24
+ * and {@link Session.refresh} never take the mutex QuickFIX holds while running
25
+ * your application handlers, so they cannot deadlock the main thread, and calls
26
+ * made in program order take effect in program order. ({@link Session.logon} /
27
+ * {@link Session.logout} flip a plain flag the engine's network thread reads
28
+ * without a lock, exactly as QuickFIX's own `Initiator::stop()` does; the
29
+ * engine picks the change up on its next pass.) QuickFIX's
30
+ * `Session::disconnect()` / `reset()` are deliberately not exposed: they are
31
+ * only safe on the engine's own network thread (see ARCHITECTURE.md §5). Use
32
+ * {@link Session.logout} to bring a session down, and the sequence-number
33
+ * setters to reset its numbering -- on a quiet session; see
34
+ * {@link Session.setNextSenderMsgSeqNum}.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const session = initiator.getSession(sessionID)!;
39
+ * if (session.isLoggedOn()) {
40
+ * console.log('next outbound seq', session.getExpectedSenderNum());
41
+ * session.logout('maintenance');
42
+ * }
43
+ * ```
44
+ */
45
+ export class Session {
46
+ /** @internal Underlying native handle. */
47
+ #native;
48
+ /** @internal Use {@link lookupSession} / {@link Engine.getSession}. */
49
+ constructor(handle) {
50
+ this.#native = handle;
51
+ }
52
+ /** @internal Wrap an existing native session handle. */
53
+ static fromNative(handle) {
54
+ return new Session(handle);
55
+ }
56
+ /** @internal The native handle backing this session. */
57
+ get nativeHandle() {
58
+ return this.#native;
59
+ }
60
+ /** The id this handle refers to. Always available, even after the session is gone. */
61
+ get sessionID() {
62
+ return SessionID.fromNative(this.#native.getSessionID());
63
+ }
64
+ // --- state -----------------------------------------------------------------
65
+ /** `true` once a Logon has been both sent and received. */
66
+ isLoggedOn() {
67
+ return this.#native.isLoggedOn();
68
+ }
69
+ /**
70
+ * Whether the session is enabled. `false` after {@link Session.logout} until
71
+ * {@link Session.logon}; a disabled initiator session is not reconnected.
72
+ */
73
+ isEnabled() {
74
+ return this.#native.isEnabled();
75
+ }
76
+ /** Whether this side has sent a Logon on the current connection. */
77
+ sentLogon() {
78
+ return this.#native.sentLogon();
79
+ }
80
+ /** Whether this side has sent a Logout on the current connection. */
81
+ sentLogout() {
82
+ return this.#native.sentLogout();
83
+ }
84
+ /** Whether a Logon has been received on the current connection. */
85
+ receivedLogon() {
86
+ return this.#native.receivedLogon();
87
+ }
88
+ /** `true` for a session owned by an {@link Initiator}. */
89
+ isInitiator() {
90
+ return this.#native.isInitiator();
91
+ }
92
+ /** `true` for a session owned by an {@link Acceptor}. */
93
+ isAcceptor() {
94
+ return this.#native.isAcceptor();
95
+ }
96
+ /**
97
+ * Whether `now` (default: the current time) falls inside the session's
98
+ * configured `StartTime`/`EndTime` window. Throws `RangeError` for an
99
+ * invalid `Date`.
100
+ */
101
+ isSessionTime(now) {
102
+ return this.#native.isSessionTime(now?.getTime());
103
+ }
104
+ /**
105
+ * Whether `now` (default: the current time) falls inside the session's
106
+ * configured `LogonTime`/`LogoutTime` window. Throws `RangeError` for an
107
+ * invalid `Date`.
108
+ */
109
+ isLogonTime(now) {
110
+ return this.#native.isLogonTime(now?.getTime());
111
+ }
112
+ // --- sequence numbers --------------------------------------------------------
113
+ /** The MsgSeqNum (tag 34) the next outbound message will carry. */
114
+ getExpectedSenderNum() {
115
+ return this.#native.getExpectedSenderNum();
116
+ }
117
+ /** The MsgSeqNum (tag 34) expected on the next inbound message. */
118
+ getExpectedTargetNum() {
119
+ return this.#native.getExpectedTargetNum();
120
+ }
121
+ /**
122
+ * Set the MsgSeqNum the next outbound message will carry (writes to the
123
+ * message store; a file store may throw `IOException`).
124
+ *
125
+ * **Call it on a quiet session.** The setter is not serialised against a
126
+ * message the engine is sending or receiving on this session at the same
127
+ * time: QuickFIX reads the next number into the outbound header, runs your
128
+ * `toApp`/`toAdmin` handler, then increments from the store, all under a
129
+ * lock this call does not (and must not) take. A value set inside that
130
+ * window -- from a handler, or while a {@link sendToTarget} promise or a
131
+ * heartbeat is in flight on this session -- is overwritten by the increment,
132
+ * and the session ends up one off from its counterparty (a Resend Request or
133
+ * a "MsgSeqNum too low" Logout). Set it before {@link Engine.start}, from a
134
+ * `'logout'` event, or when you know nothing is in flight; never from a
135
+ * handler.
136
+ *
137
+ * @throws `RangeError` if `seqNum` is not a positive safe integer (MsgSeqNum is
138
+ * 1-based).
139
+ */
140
+ setNextSenderMsgSeqNum(seqNum) {
141
+ this.#native.setNextSenderMsgSeqNum(seqNum);
142
+ }
143
+ /**
144
+ * Set the MsgSeqNum expected on the next inbound message (writes to the
145
+ * message store; a file store may throw `IOException`).
146
+ *
147
+ * **Call it on a quiet session.** Like {@link Session.setNextSenderMsgSeqNum},
148
+ * it is not serialised against a message the engine is processing on this
149
+ * session: QuickFIX checks the inbound number, runs your `fromApp`/`fromAdmin`
150
+ * handler, then increments the expected number from the store under a lock
151
+ * this call does not take, so a value set meanwhile is overwritten. Set it
152
+ * before {@link Engine.start}, from a `'logout'` event, or when you know
153
+ * nothing is in flight; never from a handler.
154
+ *
155
+ * @throws `RangeError` if `seqNum` is not a positive safe integer (MsgSeqNum is
156
+ * 1-based).
157
+ */
158
+ setNextTargetMsgSeqNum(seqNum) {
159
+ this.#native.setNextTargetMsgSeqNum(seqNum);
160
+ }
161
+ // --- runtime options -----------------------------------------------------------
162
+ // Each pair mirrors a QuickFIX session setting; the setter changes the LIVE
163
+ // session only (the SessionSettings it was created from are not updated).
164
+ /** `ResetOnLogon` */
165
+ getResetOnLogon() {
166
+ return this.#native.getResetOnLogon();
167
+ }
168
+ setResetOnLogon(value) {
169
+ this.#native.setResetOnLogon(value);
170
+ }
171
+ /** `ResetOnLogout` */
172
+ getResetOnLogout() {
173
+ return this.#native.getResetOnLogout();
174
+ }
175
+ setResetOnLogout(value) {
176
+ this.#native.setResetOnLogout(value);
177
+ }
178
+ /** `ResetOnDisconnect` */
179
+ getResetOnDisconnect() {
180
+ return this.#native.getResetOnDisconnect();
181
+ }
182
+ setResetOnDisconnect(value) {
183
+ this.#native.setResetOnDisconnect(value);
184
+ }
185
+ /** `RefreshOnLogon` */
186
+ getRefreshOnLogon() {
187
+ return this.#native.getRefreshOnLogon();
188
+ }
189
+ setRefreshOnLogon(value) {
190
+ this.#native.setRefreshOnLogon(value);
191
+ }
192
+ /** `CheckCompID` */
193
+ getCheckCompId() {
194
+ return this.#native.getCheckCompId();
195
+ }
196
+ setCheckCompId(value) {
197
+ this.#native.setCheckCompId(value);
198
+ }
199
+ /** `CheckLatency` */
200
+ getCheckLatency() {
201
+ return this.#native.getCheckLatency();
202
+ }
203
+ setCheckLatency(value) {
204
+ this.#native.setCheckLatency(value);
205
+ }
206
+ /** `PersistMessages` */
207
+ getPersistMessages() {
208
+ return this.#native.getPersistMessages();
209
+ }
210
+ setPersistMessages(value) {
211
+ this.#native.setPersistMessages(value);
212
+ }
213
+ /** `SendRedundantResendRequests` */
214
+ getSendRedundantResendRequests() {
215
+ return this.#native.getSendRedundantResendRequests();
216
+ }
217
+ setSendRedundantResendRequests(value) {
218
+ this.#native.setSendRedundantResendRequests(value);
219
+ }
220
+ /** `ValidateLengthAndChecksum` */
221
+ getValidateLengthAndChecksum() {
222
+ return this.#native.getValidateLengthAndChecksum();
223
+ }
224
+ setValidateLengthAndChecksum(value) {
225
+ this.#native.setValidateLengthAndChecksum(value);
226
+ }
227
+ /** `SendNextExpectedMsgSeqNum` */
228
+ getSendNextExpectedMsgSeqNum() {
229
+ return this.#native.getSendNextExpectedMsgSeqNum();
230
+ }
231
+ setSendNextExpectedMsgSeqNum(value) {
232
+ this.#native.setSendNextExpectedMsgSeqNum(value);
233
+ }
234
+ /** `NonStopSession` */
235
+ getIsNonStopSession() {
236
+ return this.#native.getIsNonStopSession();
237
+ }
238
+ setIsNonStopSession(value) {
239
+ this.#native.setIsNonStopSession(value);
240
+ }
241
+ /** `LogonTimeout`, in seconds. */
242
+ getLogonTimeout() {
243
+ return this.#native.getLogonTimeout();
244
+ }
245
+ /** @throws {RangeError} if `seconds` is not an integer in the int32 range. */
246
+ setLogonTimeout(seconds) {
247
+ this.#native.setLogonTimeout(seconds);
248
+ }
249
+ /** `LogoutTimeout`, in seconds. */
250
+ getLogoutTimeout() {
251
+ return this.#native.getLogoutTimeout();
252
+ }
253
+ /** @throws {RangeError} if `seconds` is not an integer in the int32 range. */
254
+ setLogoutTimeout(seconds) {
255
+ this.#native.setLogoutTimeout(seconds);
256
+ }
257
+ /** `MaxLatency`, in seconds. */
258
+ getMaxLatency() {
259
+ return this.#native.getMaxLatency();
260
+ }
261
+ /** @throws {RangeError} if `seconds` is not an integer in the int32 range. */
262
+ setMaxLatency(seconds) {
263
+ this.#native.setMaxLatency(seconds);
264
+ }
265
+ /** `TimestampPrecision` (number of sub-second digits, `0..9`). */
266
+ getTimestampPrecision() {
267
+ return this.#native.getTimestampPrecision();
268
+ }
269
+ /** @throws `RangeError` unless `precision` is an integer in `0..9`. */
270
+ setTimestampPrecision(precision) {
271
+ this.#native.setTimestampPrecision(precision);
272
+ }
273
+ // --- control -----------------------------------------------------------------------
274
+ /**
275
+ * Enable the session. An initiator session reconnects and logs on at its
276
+ * next `ReconnectInterval`; an acceptor session accepts the next inbound
277
+ * Logon. Takes effect immediately ({@link Session.isEnabled} is `true` on
278
+ * return); observe the handshake through the engine's `'logon'` event.
279
+ */
280
+ logon() {
281
+ this.#native.logon();
282
+ }
283
+ /**
284
+ * Disable the session and initiate a graceful Logout (sent with `reason` as
285
+ * tag 58 on the next engine tick). The session stays disabled — it will not
286
+ * reconnect — until {@link Session.logon}. Takes effect immediately
287
+ * ({@link Session.isEnabled} is `false` on return); observe completion
288
+ * through the engine's `'logout'` event.
289
+ */
290
+ logout(reason) {
291
+ this.#native.logout(reason);
292
+ }
293
+ /**
294
+ * Re-read the session state (sequence numbers etc.) from the message store.
295
+ *
296
+ * @throws {QuickFixError} `IOException` if the store cannot be read.
297
+ */
298
+ refresh() {
299
+ this.#native.refresh();
300
+ }
301
+ }
302
+ /**
303
+ * Look up a live session by id, across every engine in this process.
304
+ *
305
+ * @returns The {@link Session}, or `undefined` if no session with that id
306
+ * exists (never created, or its engine has been stopped).
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * const session = lookupSession(new SessionID('FIX.4.4', 'CLIENT', 'BROKER'));
311
+ * console.log(session?.isLoggedOn());
312
+ * ```
313
+ */
314
+ export function lookupSession(sessionID) {
315
+ const handle = native.lookupSession(toNativeSessionID(sessionID, 'sessionID'));
316
+ return handle ? Session.fromNative(handle) : undefined;
317
+ }
318
+ /** Whether a session with this id exists in this process. */
319
+ export function doesSessionExist(sessionID) {
320
+ return native.doesSessionExist(toNativeSessionID(sessionID, 'sessionID'));
321
+ }
322
+ /** The ids of every session in this process, across all engines. */
323
+ export function getSessions() {
324
+ return native.getSessions().map((h) => SessionID.fromNative(h));
325
+ }
326
+ /** The number of sessions in this process, across all engines. */
327
+ export function numSessions() {
328
+ return native.numSessions();
329
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * In-process TLS tunnel for initiators: the stunnel client model.
3
+ *
4
+ * QuickFIX is built without its OpenSSL-based `SSLSocketInitiator` (see
5
+ * CMakeLists.txt), so TLS is terminated in Node instead: the tunnel listens on
6
+ * a loopback port, the plain `FIX::SocketInitiator` connects to it (point
7
+ * `SocketConnectHost`/`SocketConnectPort` at {@link TlsTunnel.host} /
8
+ * {@link TlsTunnel.port}), and every accepted connection is carried to the real
9
+ * counterparty over `tls.connect`.
10
+ */
11
+ import { EventEmitter } from 'node:events';
12
+ import { type Server } from 'node:net';
13
+ import { type CipherNameAndProtocol, type ConnectionOptions, type PeerCertificate } from 'node:tls';
14
+ /** Options for {@link createTlsTunnel}. */
15
+ export interface TlsTunnelOptions {
16
+ /** Host name or IP of the counterparty's TLS endpoint. */
17
+ host: string;
18
+ /** Port of the counterparty's TLS endpoint. */
19
+ port: number;
20
+ /**
21
+ * Passed to `tls.connect()` for every connection (CA, client certificate for
22
+ * mTLS, `minVersion`, ciphers, ...). `servername` (SNI, and the name the
23
+ * server certificate is checked against) defaults to `host` unless `host` is
24
+ * an IP address.
25
+ */
26
+ tls?: Omit<ConnectionOptions, 'host' | 'port' | 'socket' | 'path'>;
27
+ /**
28
+ * Loopback port to listen on. Defaults to `0`, an ephemeral port read back
29
+ * from {@link TlsTunnel.port}. Set it to point a static `.cfg` file at the
30
+ * tunnel, like stunnel's `accept = 127.0.0.1:<port>`.
31
+ */
32
+ localPort?: number;
33
+ }
34
+ /** Details of an established TLS connection, emitted as `'secureConnect'`. */
35
+ export interface TlsTunnelSecureConnectInfo {
36
+ /** Whether the server certificate was verified against the trusted CAs. */
37
+ authorized: boolean;
38
+ /**
39
+ * Why verification failed, as the OpenSSL verification code (e.g.
40
+ * `'UNABLE_TO_VERIFY_LEAF_SIGNATURE'`, the `code` a `'tlsError'` carries
41
+ * otherwise). Only reachable with `rejectUnauthorized: false`.
42
+ */
43
+ authorizationError?: string;
44
+ /** Negotiated protocol, e.g. `'TLSv1.3'`. */
45
+ protocol: string | null;
46
+ /** Negotiated cipher. */
47
+ cipher: CipherNameAndProtocol;
48
+ /** The server's certificate (an empty object if it sent none). */
49
+ peerCertificate: PeerCertificate;
50
+ }
51
+ /**
52
+ * Events emitted by a {@link TlsTunnel}. There is deliberately no `'error'`
53
+ * event: connection failures are reported as `'tlsError'`, so a tunnel without
54
+ * listeners never crashes the process.
55
+ */
56
+ export interface TlsTunnelEvents {
57
+ /** A connection to the counterparty completed its TLS handshake. */
58
+ secureConnect: [info: TlsTunnelSecureConnectInfo];
59
+ /**
60
+ * A connection to the counterparty failed (DNS, TCP, handshake, certificate
61
+ * verification, `tls` options `tls.connect` rejects) or broke. The
62
+ * initiator's loopback connection is closed with it, so QuickFIX reconnects
63
+ * after its `ReconnectInterval`. Also emitted when the loopback server fails
64
+ * to accept a connection (e.g. `EMFILE`).
65
+ */
66
+ tlsError: [error: Error];
67
+ }
68
+ /**
69
+ * A running TLS tunnel, created by {@link createTlsTunnel}.
70
+ *
71
+ * Its handles are unref'd: the tunnel alone never keeps the process alive (a
72
+ * started engine does). Close it after the engine has stopped, so a graceful
73
+ * Logout still reaches the counterparty.
74
+ */
75
+ export declare class TlsTunnel extends EventEmitter {
76
+ #private;
77
+ /** Loopback address the tunnel listens on (`SocketConnectHost`). */
78
+ readonly host = "127.0.0.1";
79
+ /** Loopback port the tunnel listens on (`SocketConnectPort`). */
80
+ readonly port: number;
81
+ /** The counterparty's TLS endpoint. */
82
+ readonly remote: {
83
+ readonly host: string;
84
+ readonly port: number;
85
+ };
86
+ /** @internal Use {@link createTlsTunnel}. */
87
+ constructor(server: Server, port: number, options: TlsTunnelOptions);
88
+ /**
89
+ * Stop listening and destroy every live connection. Idempotent: later calls
90
+ * return the same Promise.
91
+ */
92
+ close(): Promise<void>;
93
+ /** Same as {@link TlsTunnel.close}; enables `await using tunnel = ...`. */
94
+ [Symbol.asyncDispose](): Promise<void>;
95
+ }
96
+ /**
97
+ * Open a TLS tunnel to `options.host`:`options.port` and resolve once it
98
+ * listens on a loopback port.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const tunnel = await createTlsTunnel({
103
+ * host: 'fix.broker.com',
104
+ * port: 9443,
105
+ * tls: { ca: readFileSync('broker-ca.pem') },
106
+ * });
107
+ * tunnel.on('tlsError', (err) => console.error('TLS:', err.message));
108
+ * const settings = SessionSettings.fromString(`
109
+ * [DEFAULT]
110
+ * ConnectionType=initiator
111
+ * SocketConnectHost=${tunnel.host}
112
+ * SocketConnectPort=${tunnel.port}
113
+ * ...`);
114
+ * const initiator = new Initiator({ settings, handlers });
115
+ * await initiator.start();
116
+ * // ... later: engine first, so the Logout still crosses the tunnel
117
+ * await initiator.stop();
118
+ * await tunnel.close();
119
+ * ```
120
+ *
121
+ * @returns A Promise that rejects with a `TypeError` if `host`, `port` or
122
+ * `localPort` is invalid, and with the listen error (e.g. `EADDRINUSE` for a
123
+ * fixed `localPort` already in use) if the loopback port cannot be bound.
124
+ */
125
+ export declare function createTlsTunnel(options: TlsTunnelOptions): Promise<TlsTunnel>;
126
+ export interface TlsTunnel {
127
+ /** Register a listener for a tunnel event (typed per {@link TlsTunnelEvents}). */
128
+ on<E extends keyof TlsTunnelEvents>(event: E, listener: (...args: TlsTunnelEvents[E]) => void): this;
129
+ /** Register a one-shot listener for a tunnel event. */
130
+ once<E extends keyof TlsTunnelEvents>(event: E, listener: (...args: TlsTunnelEvents[E]) => void): this;
131
+ /** Alias of {@link TlsTunnel.on}. */
132
+ addListener<E extends keyof TlsTunnelEvents>(event: E, listener: (...args: TlsTunnelEvents[E]) => void): this;
133
+ /** Remove a previously registered listener. */
134
+ off<E extends keyof TlsTunnelEvents>(event: E, listener: (...args: TlsTunnelEvents[E]) => void): this;
135
+ /** Alias of {@link TlsTunnel.off}. */
136
+ removeListener<E extends keyof TlsTunnelEvents>(event: E, listener: (...args: TlsTunnelEvents[E]) => void): this;
137
+ /** Remove all listeners (optionally for a single event). */
138
+ removeAllListeners<E extends keyof TlsTunnelEvents>(event?: E): this;
139
+ /** Emit a tunnel event with its typed arguments. */
140
+ emit<E extends keyof TlsTunnelEvents>(event: E, ...args: TlsTunnelEvents[E]): boolean;
141
+ /** List the listeners registered for an event. */
142
+ listeners<E extends keyof TlsTunnelEvents>(event: E): Array<(...args: TlsTunnelEvents[E]) => void>;
143
+ }