@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,144 @@
1
+ #ifndef NAPI_QUICKFIX_SESSION_WRAP_H
2
+ #define NAPI_QUICKFIX_SESSION_WRAP_H
3
+
4
+ #include <napi.h>
5
+
6
+ #include <memory>
7
+
8
+ #include "quickfix/Session.h"
9
+ #include "quickfix/SessionID.h"
10
+ #include "session_op_gate.h"
11
+
12
+ namespace napi_quickfix {
13
+
14
+ // Napi::ObjectWrap giving JS a handle on a live FIX::Session.
15
+ //
16
+ // Lifetime: FIX::Session objects are OWNED BY THE ENGINE (Initiator/Acceptor)
17
+ // and FIX::Session::lookupSession returns a raw, non-owning pointer. This wrap
18
+ // therefore never caches that pointer: it stores the FIX::SessionID by value
19
+ // plus the SessionOpGate of the engine that owned the session when the handle
20
+ // was created, and re-resolves on EVERY call, throwing a QuickFixError with
21
+ // fixErrorName 'SessionNotFound' once the engine has been stopped (which
22
+ // destroys its sessions). The gate is what ties the handle to ONE engine: the
23
+ // process-wide registry lookupSession() reads is keyed by SessionID alone, so
24
+ // after the owner is stopped a new engine may register the same id, and a
25
+ // handle that only re-resolved the id would silently operate on the new
26
+ // engine's session. Resolve() therefore also requires the registered owner of
27
+ // the id (SessionOpGate::Lookup) to still be the gate captured at creation.
28
+ //
29
+ // new SessionWrap(sessionID: SessionIDWrap) -- internal; JS reaches a
30
+ // Session through lookupSession() / engine.getSession(), which bind the
31
+ // gate via NewInstance(). A handle built any other way has no gate and
32
+ // always throws SessionNotFound.
33
+ //
34
+ // Every member is synchronous and safe on the JS thread: none takes
35
+ // FIX::Session::m_mutex, which QuickFIX holds across application callbacks.
36
+ // getSessionID, isLoggedOn, isEnabled, sentLogon, sentLogout, receivedLogon,
37
+ // isInitiator, isAcceptor, isSessionTime(nowMs?), isLogonTime(nowMs?),
38
+ // getExpectedSenderNum, getExpectedTargetNum, setNextSenderMsgSeqNum,
39
+ // setNextTargetMsgSeqNum, logon, logout(reason?), refresh, and the runtime
40
+ // option getters/setters.
41
+ // FIX::Session::disconnect()/reset() are not exposed: they are only safe on
42
+ // the engine's own network thread (see session_wrap.cpp).
43
+ class SessionWrap : public Napi::ObjectWrap<SessionWrap> {
44
+ public:
45
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
46
+
47
+ // Build a JS Session bound to a copy of `id` and to `gate`, the
48
+ // SessionOpGate of the engine owning the session (SessionOpGate::Lookup(id)
49
+ // or the engine wrap's own gate_). Does NOT check that the session exists;
50
+ // callers (lookupSession / engine.getSession) do.
51
+ static Napi::Object NewInstance(Napi::Env env, const FIX::SessionID& id,
52
+ std::shared_ptr<SessionOpGate> gate);
53
+
54
+ explicit SessionWrap(const Napi::CallbackInfo& info);
55
+
56
+ const FIX::SessionID& SessionID() const { return id_; }
57
+
58
+ private:
59
+ static Napi::FunctionReference constructor_;
60
+
61
+ // Resolve the live FIX::Session of the engine this handle was created for,
62
+ // or throw FIX::SessionNotFound (session gone, or the id now belongs to a
63
+ // different engine). Must be called inside NQ_TRY so the exception maps to a
64
+ // QuickFixError.
65
+ FIX::Session& Resolve();
66
+
67
+ Napi::Value GetSessionID(const Napi::CallbackInfo& info);
68
+
69
+ Napi::Value IsLoggedOn(const Napi::CallbackInfo& info);
70
+ Napi::Value IsEnabled(const Napi::CallbackInfo& info);
71
+ Napi::Value SentLogon(const Napi::CallbackInfo& info);
72
+ Napi::Value SentLogout(const Napi::CallbackInfo& info);
73
+ Napi::Value ReceivedLogon(const Napi::CallbackInfo& info);
74
+ Napi::Value IsInitiator(const Napi::CallbackInfo& info);
75
+ Napi::Value IsAcceptor(const Napi::CallbackInfo& info);
76
+ Napi::Value IsSessionTime(const Napi::CallbackInfo& info);
77
+ Napi::Value IsLogonTime(const Napi::CallbackInfo& info);
78
+
79
+ Napi::Value GetExpectedSenderNum(const Napi::CallbackInfo& info);
80
+ Napi::Value GetExpectedTargetNum(const Napi::CallbackInfo& info);
81
+ Napi::Value SetNextSenderMsgSeqNum(const Napi::CallbackInfo& info);
82
+ Napi::Value SetNextTargetMsgSeqNum(const Napi::CallbackInfo& info);
83
+
84
+ // Runtime options (plain members on FIX::Session; no locking, no callbacks).
85
+ Napi::Value GetResetOnLogon(const Napi::CallbackInfo& info);
86
+ Napi::Value SetResetOnLogon(const Napi::CallbackInfo& info);
87
+ Napi::Value GetResetOnLogout(const Napi::CallbackInfo& info);
88
+ Napi::Value SetResetOnLogout(const Napi::CallbackInfo& info);
89
+ Napi::Value GetResetOnDisconnect(const Napi::CallbackInfo& info);
90
+ Napi::Value SetResetOnDisconnect(const Napi::CallbackInfo& info);
91
+ Napi::Value GetRefreshOnLogon(const Napi::CallbackInfo& info);
92
+ Napi::Value SetRefreshOnLogon(const Napi::CallbackInfo& info);
93
+ Napi::Value GetCheckCompId(const Napi::CallbackInfo& info);
94
+ Napi::Value SetCheckCompId(const Napi::CallbackInfo& info);
95
+ Napi::Value GetCheckLatency(const Napi::CallbackInfo& info);
96
+ Napi::Value SetCheckLatency(const Napi::CallbackInfo& info);
97
+ Napi::Value GetPersistMessages(const Napi::CallbackInfo& info);
98
+ Napi::Value SetPersistMessages(const Napi::CallbackInfo& info);
99
+ Napi::Value GetSendRedundantResendRequests(const Napi::CallbackInfo& info);
100
+ Napi::Value SetSendRedundantResendRequests(const Napi::CallbackInfo& info);
101
+ Napi::Value GetValidateLengthAndChecksum(const Napi::CallbackInfo& info);
102
+ Napi::Value SetValidateLengthAndChecksum(const Napi::CallbackInfo& info);
103
+ Napi::Value GetSendNextExpectedMsgSeqNum(const Napi::CallbackInfo& info);
104
+ Napi::Value SetSendNextExpectedMsgSeqNum(const Napi::CallbackInfo& info);
105
+ Napi::Value GetIsNonStopSession(const Napi::CallbackInfo& info);
106
+ Napi::Value SetIsNonStopSession(const Napi::CallbackInfo& info);
107
+ Napi::Value GetLogonTimeout(const Napi::CallbackInfo& info);
108
+ Napi::Value SetLogonTimeout(const Napi::CallbackInfo& info);
109
+ Napi::Value GetLogoutTimeout(const Napi::CallbackInfo& info);
110
+ Napi::Value SetLogoutTimeout(const Napi::CallbackInfo& info);
111
+ Napi::Value GetMaxLatency(const Napi::CallbackInfo& info);
112
+ Napi::Value SetMaxLatency(const Napi::CallbackInfo& info);
113
+ Napi::Value GetTimestampPrecision(const Napi::CallbackInfo& info);
114
+ Napi::Value SetTimestampPrecision(const Napi::CallbackInfo& info);
115
+
116
+ // Control. All sync: they only touch SessionState under its own mutex,
117
+ // which QuickFIX never holds across a callback.
118
+ Napi::Value Logon(const Napi::CallbackInfo& info);
119
+ Napi::Value Logout(const Napi::CallbackInfo& info);
120
+ Napi::Value Refresh(const Napi::CallbackInfo& info);
121
+
122
+ // Shared helpers for the one-liner getters/setters above.
123
+ template <typename Fn>
124
+ Napi::Value BoolGet(const Napi::CallbackInfo& info, Fn fn);
125
+ template <typename Fn>
126
+ Napi::Value IntGet(const Napi::CallbackInfo& info, Fn fn);
127
+ template <typename Fn>
128
+ Napi::Value BoolSet(const Napi::CallbackInfo& info, Fn fn, const char* name);
129
+ template <typename Fn>
130
+ Napi::Value IntSet(const Napi::CallbackInfo& info, Fn fn, const char* name);
131
+ Napi::Value TimeCheck(const Napi::CallbackInfo& info, bool logonTime);
132
+ Napi::Value SeqNumSet(const Napi::CallbackInfo& info, bool sender,
133
+ const char* name);
134
+
135
+ FIX::SessionID id_;
136
+ // The owning engine's gate at creation; held by shared_ptr so the object
137
+ // stays alive (and its address unique) as long as the handle does. nullptr
138
+ // for a handle not created through NewInstance().
139
+ std::shared_ptr<SessionOpGate> gate_;
140
+ };
141
+
142
+ } // namespace napi_quickfix
143
+
144
+ #endif // NAPI_QUICKFIX_SESSION_WRAP_H
@@ -27,4 +27,3 @@ export declare class Acceptor extends Engine {
27
27
  /** @param options Engine options. */
28
28
  constructor(options: EngineOptions);
29
29
  }
30
- //# sourceMappingURL=acceptor.d.ts.map
@@ -36,4 +36,3 @@ class Acceptor extends engine_js_1.Engine {
36
36
  }
37
37
  }
38
38
  exports.Acceptor = Acceptor;
39
- //# sourceMappingURL=acceptor.js.map
@@ -113,4 +113,3 @@ export declare class FixReject extends Error {
113
113
  * ```
114
114
  */
115
115
  export declare function fixReject(kind: FixRejectKind, message?: string, detail?: string): FixReject;
116
- //# sourceMappingURL=application.d.ts.map
@@ -60,4 +60,3 @@ exports.FixReject = FixReject;
60
60
  function fixReject(kind, message, detail) {
61
61
  return new FixReject(kind, message, detail);
62
62
  }
63
- //# sourceMappingURL=application.js.map
@@ -13,6 +13,8 @@ import type { Message } from './message.js';
13
13
  * ```ts
14
14
  * const dd = DataDictionary.fromFile('./spec/FIX44.xml');
15
15
  * dd.validate(msg); // throws QuickFixError if invalid
16
+ * dd.getFieldName(35); // 'MsgType'
17
+ * Message.parse(raw, { dictionary: dd }); // structural parse (repeating groups)
16
18
  * ```
17
19
  */
18
20
  export declare class DataDictionary {
@@ -41,9 +43,25 @@ export declare class DataDictionary {
41
43
  * Validate a message against this dictionary.
42
44
  *
43
45
  * @param msg The message to validate.
46
+ * @param bodyOnly When `true`, validate only as the *application* dictionary
47
+ * (QuickFIX's `validate(msg, true)`): the BeginString version check and the
48
+ * tag/value validation of the header and trailer fields are skipped. It is
49
+ * not a check of a draft body: `BeginString` and `MsgType` must be set, and
50
+ * every field the dictionary requires, header and trailer included (e.g.
51
+ * `BodyLength`, `CheckSum`), must be present. Defaults to `false`.
44
52
  * @throws A `QuickFixError` on any validation failure (e.g.
45
- * `fixError: 'InvalidMessage'` / `'FieldNotFound'` / `'IncorrectTagValue'`).
53
+ * `fixErrorName: 'InvalidMessage'` / `'RequiredTagMissing'` /
54
+ * `'IncorrectTagValue'` / `'UnsupportedVersion'`).
46
55
  */
47
- validate(msg: Message): void;
56
+ validate(msg: Message, bodyOnly?: boolean): void;
57
+ /** The BeginString the spec declares (e.g. `'FIX.4.4'`), or `''` if it has none. */
58
+ getVersion(): string;
59
+ /** The name of a field tag (e.g. `35` → `'MsgType'`), or `undefined` if the spec does not define it. */
60
+ getFieldName(tag: number): string | undefined;
61
+ /** The tag of a field name (e.g. `'MsgType'` → `35`), or `undefined` if the spec does not define it. */
62
+ getFieldTag(name: string): number | undefined;
63
+ /** Whether the spec defines a field with this tag. */
64
+ isField(tag: number): boolean;
65
+ /** Whether the spec defines a message with this MsgType (e.g. `'D'`). */
66
+ isMsgType(msgType: string): boolean;
48
67
  }
49
- //# sourceMappingURL=data-dictionary.d.ts.map
@@ -15,6 +15,8 @@ const native_js_1 = require("./native.js");
15
15
  * ```ts
16
16
  * const dd = DataDictionary.fromFile('./spec/FIX44.xml');
17
17
  * dd.validate(msg); // throws QuickFixError if invalid
18
+ * dd.getFieldName(35); // 'MsgType'
19
+ * Message.parse(raw, { dictionary: dd }); // structural parse (repeating groups)
18
20
  * ```
19
21
  */
20
22
  class DataDictionary {
@@ -66,12 +68,38 @@ class DataDictionary {
66
68
  * Validate a message against this dictionary.
67
69
  *
68
70
  * @param msg The message to validate.
71
+ * @param bodyOnly When `true`, validate only as the *application* dictionary
72
+ * (QuickFIX's `validate(msg, true)`): the BeginString version check and the
73
+ * tag/value validation of the header and trailer fields are skipped. It is
74
+ * not a check of a draft body: `BeginString` and `MsgType` must be set, and
75
+ * every field the dictionary requires, header and trailer included (e.g.
76
+ * `BodyLength`, `CheckSum`), must be present. Defaults to `false`.
69
77
  * @throws A `QuickFixError` on any validation failure (e.g.
70
- * `fixError: 'InvalidMessage'` / `'FieldNotFound'` / `'IncorrectTagValue'`).
78
+ * `fixErrorName: 'InvalidMessage'` / `'RequiredTagMissing'` /
79
+ * `'IncorrectTagValue'` / `'UnsupportedVersion'`).
71
80
  */
72
- validate(msg) {
73
- this.#native.validate(msg.nativeHandle);
81
+ validate(msg, bodyOnly = false) {
82
+ this.#native.validate(msg.nativeHandle, bodyOnly);
83
+ }
84
+ /** The BeginString the spec declares (e.g. `'FIX.4.4'`), or `''` if it has none. */
85
+ getVersion() {
86
+ return this.#native.getVersion();
87
+ }
88
+ /** The name of a field tag (e.g. `35` → `'MsgType'`), or `undefined` if the spec does not define it. */
89
+ getFieldName(tag) {
90
+ return this.#native.getFieldName(tag);
91
+ }
92
+ /** The tag of a field name (e.g. `'MsgType'` → `35`), or `undefined` if the spec does not define it. */
93
+ getFieldTag(name) {
94
+ return this.#native.getFieldTag(name);
95
+ }
96
+ /** Whether the spec defines a field with this tag. */
97
+ isField(tag) {
98
+ return this.#native.isField(tag);
99
+ }
100
+ /** Whether the spec defines a message with this MsgType (e.g. `'D'`). */
101
+ isMsgType(msgType) {
102
+ return this.#native.isMsgType(msgType);
74
103
  }
75
104
  }
76
105
  exports.DataDictionary = DataDictionary;
77
- //# sourceMappingURL=data-dictionary.js.map
@@ -8,6 +8,7 @@ import { EventEmitter } from 'node:events';
8
8
  import { type NativeEngineConstructor } from './native.js';
9
9
  import { Message } from './message.js';
10
10
  import { SessionID } from './session-id.js';
11
+ import { Session } from './session.js';
11
12
  import type { ApplicationHandlers } from './application.js';
12
13
  import type { SessionSettings } from './session-settings.js';
13
14
  /** Options for constructing an {@link Initiator} or {@link Acceptor}. */
@@ -73,7 +74,11 @@ export declare abstract class Engine extends EventEmitter {
73
74
  * Stop the engine.
74
75
  *
75
76
  * Runs off the main thread and resolves once shutdown completes, keeping the
76
- * event loop free for any in-flight callbacks.
77
+ * event loop free for any in-flight callbacks. Once it settles the engine's
78
+ * sessions are destroyed. A `stop()` called while another is still in flight
79
+ * (even from a `'logout'` listener that stop fired) returns the same Promise,
80
+ * so it too settles only once the sessions are gone; the first call's
81
+ * `force` applies to both.
77
82
  *
78
83
  * @param force When `true`, aborts immediately instead of draining gracefully.
79
84
  *
@@ -83,8 +88,42 @@ export declare abstract class Engine extends EventEmitter {
83
88
  * ```
84
89
  */
85
90
  stop(force?: boolean): Promise<void>;
86
- /** Whether any session is currently logged on. */
87
- isLoggedOn(): boolean;
91
+ /**
92
+ * Whether a session is logged on.
93
+ *
94
+ * With no argument: `true` if **any** of this engine's sessions is logged on.
95
+ * With a {@link SessionID}: `true` only if that session belongs to this engine
96
+ * and is logged on (`false`, not an error, for a foreign id). Always `false`
97
+ * before {@link Engine.start} and once {@link Engine.stop} has settled; while
98
+ * a graceful stop is still in progress it reports the live state.
99
+ *
100
+ * @throws {TypeError} if `sessionID` is given but is not a {@link SessionID}
101
+ * (it does not fall back to the engine-wide form).
102
+ */
103
+ isLoggedOn(sessionID?: SessionID): boolean;
104
+ /**
105
+ * The ids of the sessions this engine was configured with (every `[SESSION]`
106
+ * in its settings). Stable for the engine's lifetime, including after
107
+ * {@link Engine.stop}.
108
+ */
109
+ getSessions(): SessionID[];
110
+ /**
111
+ * A handle on one of this engine's sessions.
112
+ *
113
+ * @returns The {@link Session}, or `undefined` if `sessionID` is not one of
114
+ * this engine's sessions or {@link Engine.stop} has settled (that destroys
115
+ * its sessions). While a stop is still in progress the sessions are alive,
116
+ * so a `'logout'` listener can still read e.g. the final sequence numbers.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * initiator.on('logon', (id) => {
121
+ * const session = initiator.getSession(id)!;
122
+ * console.log('next seq', session.getExpectedSenderNum());
123
+ * });
124
+ * ```
125
+ */
126
+ getSession(sessionID: SessionID): Session | undefined;
88
127
  /**
89
128
  * Keep the Node.js event loop alive while the engine is running (default once
90
129
  * started). Pair with {@link Engine.unref} to let a short script exit.
@@ -111,4 +150,3 @@ export interface Engine {
111
150
  /** List the listeners registered for an event. */
112
151
  listeners<E extends keyof EngineEvents>(event: E): Array<(...args: EngineEvents[E]) => void>;
113
152
  }
114
- //# sourceMappingURL=engine.d.ts.map
@@ -10,6 +10,7 @@ exports.Engine = void 0;
10
10
  const node_events_1 = require("node:events");
11
11
  const message_js_1 = require("./message.js");
12
12
  const session_id_js_1 = require("./session-id.js");
13
+ const session_js_1 = require("./session.js");
13
14
  /**
14
15
  * Base class for the FIX session engines.
15
16
  *
@@ -106,7 +107,11 @@ class Engine extends node_events_1.EventEmitter {
106
107
  * Stop the engine.
107
108
  *
108
109
  * Runs off the main thread and resolves once shutdown completes, keeping the
109
- * event loop free for any in-flight callbacks.
110
+ * event loop free for any in-flight callbacks. Once it settles the engine's
111
+ * sessions are destroyed. A `stop()` called while another is still in flight
112
+ * (even from a `'logout'` listener that stop fired) returns the same Promise,
113
+ * so it too settles only once the sessions are gone; the first call's
114
+ * `force` applies to both.
110
115
  *
111
116
  * @param force When `true`, aborts immediately instead of draining gracefully.
112
117
  *
@@ -118,9 +123,50 @@ class Engine extends node_events_1.EventEmitter {
118
123
  stop(force) {
119
124
  return this.#native.stop(force);
120
125
  }
121
- /** Whether any session is currently logged on. */
122
- isLoggedOn() {
123
- return this.#native.isLoggedOn();
126
+ /**
127
+ * Whether a session is logged on.
128
+ *
129
+ * With no argument: `true` if **any** of this engine's sessions is logged on.
130
+ * With a {@link SessionID}: `true` only if that session belongs to this engine
131
+ * and is logged on (`false`, not an error, for a foreign id). Always `false`
132
+ * before {@link Engine.start} and once {@link Engine.stop} has settled; while
133
+ * a graceful stop is still in progress it reports the live state.
134
+ *
135
+ * @throws {TypeError} if `sessionID` is given but is not a {@link SessionID}
136
+ * (it does not fall back to the engine-wide form).
137
+ */
138
+ isLoggedOn(sessionID) {
139
+ return sessionID === undefined
140
+ ? this.#native.isLoggedOn()
141
+ : this.#native.isLoggedOn((0, session_id_js_1.toNativeSessionID)(sessionID, 'sessionID'));
142
+ }
143
+ /**
144
+ * The ids of the sessions this engine was configured with (every `[SESSION]`
145
+ * in its settings). Stable for the engine's lifetime, including after
146
+ * {@link Engine.stop}.
147
+ */
148
+ getSessions() {
149
+ return this.#native.getSessions().map((h) => session_id_js_1.SessionID.fromNative(h));
150
+ }
151
+ /**
152
+ * A handle on one of this engine's sessions.
153
+ *
154
+ * @returns The {@link Session}, or `undefined` if `sessionID` is not one of
155
+ * this engine's sessions or {@link Engine.stop} has settled (that destroys
156
+ * its sessions). While a stop is still in progress the sessions are alive,
157
+ * so a `'logout'` listener can still read e.g. the final sequence numbers.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * initiator.on('logon', (id) => {
162
+ * const session = initiator.getSession(id)!;
163
+ * console.log('next seq', session.getExpectedSenderNum());
164
+ * });
165
+ * ```
166
+ */
167
+ getSession(sessionID) {
168
+ const handle = this.#native.getSession((0, session_id_js_1.toNativeSessionID)(sessionID, 'sessionID'));
169
+ return handle ? session_js_1.Session.fromNative(handle) : undefined;
124
170
  }
125
171
  /**
126
172
  * Keep the Node.js event loop alive while the engine is running (default once
@@ -135,4 +181,3 @@ class Engine extends node_events_1.EventEmitter {
135
181
  }
136
182
  }
137
183
  exports.Engine = Engine;
138
- //# sourceMappingURL=engine.js.map
@@ -1,22 +1,56 @@
1
+ /**
2
+ * FIX constants.
3
+ *
4
+ * - {@link FIELD} — field-name → tag-number (e.g. `FIELD.MsgType === 35`).
5
+ * Generated from QuickFIX's `FixFieldNumbers.h` (see `src/generated/fields.ts`),
6
+ * so it carries every `FIX::FIELD::*` name the bundled engine knows.
7
+ * - value groups such as {@link MsgType} and {@link Side} (e.g.
8
+ * `MsgType.Logon === 'A'`, `Side.Buy === '1'`) — one per field, generated from
9
+ * QuickFIX's `FixValues.h` (see `src/generated/values.ts`), so every
10
+ * `FIX::<Field>_<VALUE>` constant is here. {@link VALUES} holds all of them
11
+ * keyed by field name, and each group is also importable from
12
+ * `@homaiohq/napi-quickfix/values` without loading the native addon.
13
+ *
14
+ * Everything carries literal types and is frozen, so a misspelt name is a
15
+ * compile error and consumers cannot mutate the shared constant tables.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { FIELD, MsgType, enums } from '@homaiohq/napi-quickfix';
20
+ * msg.setField(FIELD.MsgType, MsgType.Logon);
21
+ * const side = enums.Side.Buy;
22
+ * ```
23
+ */
24
+ import { FIELD } from './generated/fields.js';
25
+ import { type ValueGroups } from './generated/values.js';
1
26
  export { FIELD, type FieldName } from './generated/fields.js';
2
- /** A single group of FIX constants (name → value). */
27
+ export { VALUES, MsgType, Side, OrdType, TimeInForce, type ValueGroups, type ValueGroupName, } from './generated/values.js';
28
+ /**
29
+ * A single group of FIX constants widened to a string-indexed map (name → value).
30
+ *
31
+ * Every value group and {@link FIELD} is assignable to it, so use it where a group
32
+ * is looked up by a `string` key the compiler cannot narrow (a name read from
33
+ * configuration, say) instead of the literally-typed group itself:
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const group: EnumGroup = enums[name as ValueGroupName];
38
+ * const code = group[valueName];
39
+ * ```
40
+ */
3
41
  export type EnumGroup = Readonly<Record<string, number | string>>;
4
- /** The full, frozen tree of FIX constants. */
5
- export type Enums = Readonly<Record<string, EnumGroup>>;
42
+ /**
43
+ * The full, frozen tree of FIX constants: every value group of {@link ValueGroups}
44
+ * plus the {@link FIELD} tag table.
45
+ */
46
+ export type Enums = ValueGroups & {
47
+ readonly FIELD: typeof FIELD;
48
+ };
6
49
  /**
7
50
  * The complete tree of FIX constants (frozen), e.g. `enums.FIELD.MsgType`,
8
51
  * `enums.MsgType.Logon`, `enums.Side.Buy`.
9
52
  *
10
- * `enums.FIELD` is the same table as {@link FIELD}; the value groups come from
11
- * the native engine.
53
+ * `enums.FIELD` is the same table as {@link FIELD}; every other member is the
54
+ * same object as the corresponding {@link VALUES} group.
12
55
  */
13
56
  export declare const enums: Enums;
14
- /** A group of numeric FIX constants (name → number). */
15
- export type NumericEnumGroup = Readonly<Record<string, number>>;
16
- /** A group of string-valued FIX constants (name → string), e.g. {@link MsgType}. */
17
- export type StringEnumGroup = Readonly<Record<string, string>>;
18
- /** MsgType values (strings), e.g. `MsgType.Logon === 'A'`. */
19
- export declare const MsgType: StringEnumGroup;
20
- /** Side values (strings), e.g. `Side.Buy === '1'`. */
21
- export declare const Side: StringEnumGroup;
22
- //# sourceMappingURL=enums.d.ts.map
package/dist/cjs/enums.js CHANGED
@@ -1,18 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Side = exports.MsgType = exports.enums = exports.FIELD = void 0;
3
+ exports.enums = exports.TimeInForce = exports.OrdType = exports.Side = exports.MsgType = exports.VALUES = exports.FIELD = void 0;
4
4
  /**
5
5
  * FIX constants.
6
6
  *
7
7
  * - {@link FIELD} — field-name → tag-number (e.g. `FIELD.MsgType === 35`).
8
8
  * Generated from QuickFIX's `FixFieldNumbers.h` (see `src/generated/fields.ts`),
9
- * so it carries every `FIX::FIELD::*` name the bundled engine knows, with
10
- * literal types.
11
- * - value groups exported by the native engine such as {@link MsgType} and
12
- * {@link Side} (e.g. `MsgType.Logon === 'A'`, `Side.Buy === '1'`).
9
+ * so it carries every `FIX::FIELD::*` name the bundled engine knows.
10
+ * - value groups such as {@link MsgType} and {@link Side} (e.g.
11
+ * `MsgType.Logon === 'A'`, `Side.Buy === '1'`) — one per field, generated from
12
+ * QuickFIX's `FixValues.h` (see `src/generated/values.ts`), so every
13
+ * `FIX::<Field>_<VALUE>` constant is here. {@link VALUES} holds all of them
14
+ * keyed by field name, and each group is also importable from
15
+ * `@homaiohq/napi-quickfix/values` without loading the native addon.
13
16
  *
14
- * Everything is frozen at the TS layer so consumers cannot mutate the shared
15
- * constant tables.
17
+ * Everything carries literal types and is frozen, so a misspelt name is a
18
+ * compile error and consumers cannot mutate the shared constant tables.
16
19
  *
17
20
  * @example
18
21
  * ```ts
@@ -22,28 +25,20 @@ exports.Side = exports.MsgType = exports.enums = exports.FIELD = void 0;
22
25
  * ```
23
26
  */
24
27
  const fields_js_1 = require("./generated/fields.js");
25
- const native_js_1 = require("./native.js");
28
+ const values_js_1 = require("./generated/values.js");
26
29
  var fields_js_2 = require("./generated/fields.js");
27
30
  Object.defineProperty(exports, "FIELD", { enumerable: true, get: function () { return fields_js_2.FIELD; } });
28
- function deepFreeze(source) {
29
- const out = {};
30
- for (const key of Object.keys(source)) {
31
- out[key] = Object.freeze({ ...source[key] });
32
- }
33
- // FIELD is already frozen (generated); reuse it rather than copying 6000+ keys.
34
- out.FIELD = fields_js_1.FIELD;
35
- return Object.freeze(out);
36
- }
31
+ var values_js_2 = require("./generated/values.js");
32
+ Object.defineProperty(exports, "VALUES", { enumerable: true, get: function () { return values_js_2.VALUES; } });
33
+ Object.defineProperty(exports, "MsgType", { enumerable: true, get: function () { return values_js_2.MsgType; } });
34
+ Object.defineProperty(exports, "Side", { enumerable: true, get: function () { return values_js_2.Side; } });
35
+ Object.defineProperty(exports, "OrdType", { enumerable: true, get: function () { return values_js_2.OrdType; } });
36
+ Object.defineProperty(exports, "TimeInForce", { enumerable: true, get: function () { return values_js_2.TimeInForce; } });
37
37
  /**
38
38
  * The complete tree of FIX constants (frozen), e.g. `enums.FIELD.MsgType`,
39
39
  * `enums.MsgType.Logon`, `enums.Side.Buy`.
40
40
  *
41
- * `enums.FIELD` is the same table as {@link FIELD}; the value groups come from
42
- * the native engine.
41
+ * `enums.FIELD` is the same table as {@link FIELD}; every other member is the
42
+ * same object as the corresponding {@link VALUES} group.
43
43
  */
44
- exports.enums = deepFreeze((native_js_1.native.enums ?? {}));
45
- /** MsgType values (strings), e.g. `MsgType.Logon === 'A'`. */
46
- exports.MsgType = (exports.enums.MsgType ?? Object.freeze({}));
47
- /** Side values (strings), e.g. `Side.Buy === '1'`. */
48
- exports.Side = (exports.enums.Side ?? Object.freeze({}));
49
- //# sourceMappingURL=enums.js.map
44
+ exports.enums = Object.freeze({ ...values_js_1.VALUES, FIELD: fields_js_1.FIELD });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Wire formatting of the `string | number` field values `Message` and `Group`
3
+ * accept.
4
+ */
5
+ /**
6
+ * A field value as FIX text. Strings pass through. A number keeps its shortest
7
+ * round-trip digits but is written without exponent notation, which FIX numeric
8
+ * fields do not allow (`1e-7` → `'0.0000001'`, `1e21` →
9
+ * `'1000000000000000000000'`, where `String` gives `'1e-7'` and `'1e+21'`).
10
+ * @throws `RangeError` for `NaN` and `±Infinity`, which have no FIX form.
11
+ */
12
+ export declare function formatFieldValue(value: string | number): string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * Wire formatting of the `string | number` field values `Message` and `Group`
4
+ * accept.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.formatFieldValue = formatFieldValue;
8
+ /**
9
+ * A field value as FIX text. Strings pass through. A number keeps its shortest
10
+ * round-trip digits but is written without exponent notation, which FIX numeric
11
+ * fields do not allow (`1e-7` → `'0.0000001'`, `1e21` →
12
+ * `'1000000000000000000000'`, where `String` gives `'1e-7'` and `'1e+21'`).
13
+ * @throws `RangeError` for `NaN` and `±Infinity`, which have no FIX form.
14
+ */
15
+ function formatFieldValue(value) {
16
+ if (typeof value !== 'number') {
17
+ return String(value);
18
+ }
19
+ if (!Number.isFinite(value)) {
20
+ throw new RangeError(`field value must be a finite number, got ${value}`);
21
+ }
22
+ const text = String(value);
23
+ const e = text.indexOf('e');
24
+ if (e === -1) {
25
+ return text;
26
+ }
27
+ // `[-]d[.ddd]e±n`: move the decimal point of the mantissa n places.
28
+ const sign = value < 0 ? '-' : '';
29
+ const mantissa = text.slice(sign.length, e);
30
+ const point = mantissa.indexOf('.');
31
+ const digits = mantissa.replace('.', '');
32
+ const at = (point === -1 ? mantissa.length : point) + Number(text.slice(e + 1));
33
+ if (at <= 0) {
34
+ return `${sign}0.${'0'.repeat(-at)}${digits}`;
35
+ }
36
+ if (at >= digits.length) {
37
+ return `${sign}${digits}${'0'.repeat(at - digits.length)}`;
38
+ }
39
+ return `${sign}${digits.slice(0, at)}.${digits.slice(at)}`;
40
+ }
@@ -6120,4 +6120,3 @@ export declare const FIELD: Readonly<{
6120
6120
  }>;
6121
6121
  /** Every field name known to the bundled QuickFIX, e.g. `"MsgType"`. */
6122
6122
  export type FieldName = keyof typeof FIELD;
6123
- //# sourceMappingURL=fields.d.ts.map
@@ -6125,4 +6125,3 @@ exports.FIELD = Object.freeze({
6125
6125
  BatchTotalMessages: 50001,
6126
6126
  BatchProcessMode: 50002,
6127
6127
  });
6128
- //# sourceMappingURL=fields.js.map