@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
@@ -1,10 +1,74 @@
1
1
  #ifndef NAPI_QUICKFIX_ENGINE_COMMON_H
2
2
  #define NAPI_QUICKFIX_ENGINE_COMMON_H
3
3
 
4
+ #include <memory>
5
+ #include <set>
6
+ #include <string>
7
+ #include <utility>
8
+
9
+ #include "quickfix/Exceptions.h"
4
10
  #include "quickfix/Log.h"
11
+ #include "quickfix/Session.h"
12
+ #include "quickfix/SessionID.h"
13
+ #include "quickfix/SessionSettings.h"
5
14
 
6
15
  namespace napi_quickfix {
7
16
 
17
+ // Throw ConfigError("Duplicate Session ...") if any [SESSION] of `settings`
18
+ // that an engine of `connectionType` ("initiator" / "acceptor") would create
19
+ // already has a live FIX::Session in this process. QuickFIX only rejects
20
+ // duplicates within ONE settings object (SessionSettings::set); across engines
21
+ // its Session constructor ignores addSession()'s result, so a second engine
22
+ // would silently shadow the first in the process-wide registry that
23
+ // lookupSession()/sendToTarget() read, and the per-engine SessionOpGate
24
+ // registry would couple operations with the wrong engine. Call it on the JS
25
+ // thread right before constructing the engine: engines are only created and
26
+ // destroyed on that thread, so the check cannot race another engine.
27
+ inline void RejectDuplicateSessions(const FIX::SessionSettings& settings,
28
+ const std::string& connectionType) {
29
+ for (const FIX::SessionID& id : settings.getSessions()) {
30
+ const FIX::Dictionary& dict = settings.get(id);
31
+ // Sessions of the other type are skipped by the engine (and a missing
32
+ // ConnectionType is its own ConfigError, raised by the engine itself).
33
+ if (!dict.has("ConnectionType") ||
34
+ dict.getString("ConnectionType") != connectionType) {
35
+ continue;
36
+ }
37
+ if (FIX::Session::doesSessionExist(id)) {
38
+ throw FIX::ConfigError("Duplicate Session " + id.toString());
39
+ }
40
+ }
41
+ }
42
+
43
+ // Delete the FIX::Session objects a FAILED engine constructor left behind.
44
+ // FIX::Initiator/Acceptor::initialize() creates one FIX::Session per [SESSION]
45
+ // of its connection type, registering each in the process-wide registry, and
46
+ // if a later [SESSION] throws ConfigError the constructor unwinds WITHOUT the
47
+ // engine destructor that would delete them: they stay registered with no
48
+ // owner, so lookupSession() keeps resolving them and every later engine with
49
+ // one of those ids is rejected as a duplicate for the rest of the process.
50
+ // Call it right after the engine constructor threw, on the JS thread, with
51
+ // the same `settings`/`connectionType` RejectDuplicateSessions() was called
52
+ // with just before: that check guarantees every session of ours that exists
53
+ // now was created by the failed constructor, and each one is fully
54
+ // constructed: the only callback FIX::Session's constructor makes after
55
+ // registering itself is onCreate, which the bridge dispatches fire-and-forget
56
+ // (a throwing JS handler never unwinds the constructor). ~Session unregisters
57
+ // it and destroys its store/log through the factories the wrap still owns
58
+ // (public virtual destructor; the engine destructor does exactly this
59
+ // `delete`).
60
+ inline void DestroyOrphanedSessions(const FIX::SessionSettings& settings,
61
+ const std::string& connectionType) {
62
+ for (const FIX::SessionID& id : settings.getSessions()) {
63
+ const FIX::Dictionary& dict = settings.get(id);
64
+ if (!dict.has("ConnectionType") ||
65
+ dict.getString("ConnectionType") != connectionType) {
66
+ continue;
67
+ }
68
+ delete FIX::Session::lookupSession(id); // nullptr-safe
69
+ }
70
+ }
71
+
8
72
  // A LogFactory that produces no-op NullLog instances, for log:'none'. QuickFIX
9
73
  // ships NullLog but no public NullLogFactory, so we provide one.
10
74
  class NullLogFactory : public FIX::LogFactory {
@@ -14,6 +78,50 @@ class NullLogFactory : public FIX::LogFactory {
14
78
  void destroy(FIX::Log* log) override { delete log; }
15
79
  };
16
80
 
81
+ // The wrap's LogFactory: forwards to the real one (File/Screen/Null) and
82
+ // remembers the ENGINE-level Log that FIX::Initiator/Acceptor's constructor
83
+ // requests with create() -- no SessionID -- before it runs initialize(). When
84
+ // initialize() throws ConfigError the constructor unwinds without the engine
85
+ // destructor that would hand that Log back (`m_pLogFactory->destroy(m_pLog)`),
86
+ // and QuickFIX's own factories do not reclaim it either (~FileLogFactory does
87
+ // not delete its m_globalLog): with log:'file' every failed construction left
88
+ // two open streams behind. The wrap calls DestroyOrphanedEngineLog() next to
89
+ // DestroyOrphanedSessions() when the engine constructor threw. The engine
90
+ // keeps a raw pointer to its LogFactory for its whole life (per-session logs,
91
+ // and the destroy() in its destructor), so this object must outlive it: it is
92
+ // the wrap's logFactory_ member, declared before the engine.
93
+ class TrackingLogFactory : public FIX::LogFactory {
94
+ public:
95
+ explicit TrackingLogFactory(std::unique_ptr<FIX::LogFactory> inner)
96
+ : inner_(std::move(inner)) {}
97
+
98
+ FIX::Log* create() override {
99
+ FIX::Log* log = inner_->create(); // may throw: nothing recorded then
100
+ engineLog_ = log;
101
+ return log;
102
+ }
103
+ FIX::Log* create(const FIX::SessionID& id) override {
104
+ return inner_->create(id);
105
+ }
106
+ void destroy(FIX::Log* log) override {
107
+ if (log == engineLog_) engineLog_ = nullptr;
108
+ inner_->destroy(log);
109
+ }
110
+
111
+ // Destroy the engine-level Log if the engine constructor requested one and
112
+ // then threw, so no engine will ever destroy() it. No-op otherwise.
113
+ void DestroyOrphanedEngineLog() {
114
+ if (engineLog_ == nullptr) return;
115
+ FIX::Log* log = engineLog_;
116
+ engineLog_ = nullptr;
117
+ inner_->destroy(log);
118
+ }
119
+
120
+ private:
121
+ std::unique_ptr<FIX::LogFactory> inner_;
122
+ FIX::Log* engineLog_ = nullptr;
123
+ };
124
+
17
125
  } // namespace napi_quickfix
18
126
 
19
127
  #endif // NAPI_QUICKFIX_ENGINE_COMMON_H
@@ -3,15 +3,43 @@
3
3
 
4
4
  #include <napi.h>
5
5
 
6
+ #include <condition_variable>
6
7
  #include <functional>
8
+ #include <memory>
9
+ #include <mutex>
7
10
  #include <utility>
8
11
 
9
12
  #include "errors.h"
10
13
 
11
14
  namespace napi_quickfix {
12
15
 
16
+ // One-shot completion signal from a libuv worker to the JS thread. The stop
17
+ // worker signals it once its operation (FIX stop() + gate freeze) has
18
+ // returned, success or not; the wrap's Teardown() waits on it rather than
19
+ // destroying the engine under an operation that may still be running it.
20
+ class StopCompletion {
21
+ public:
22
+ void Signal() {
23
+ {
24
+ std::lock_guard<std::mutex> lock(mtx_);
25
+ done_ = true;
26
+ }
27
+ cv_.notify_all();
28
+ }
29
+ void Wait() {
30
+ std::unique_lock<std::mutex> lock(mtx_);
31
+ cv_.wait(lock, [this] { return done_; });
32
+ }
33
+
34
+ private:
35
+ std::mutex mtx_;
36
+ std::condition_variable cv_;
37
+ bool done_ = false;
38
+ };
39
+
13
40
  // A generic AsyncWorker that runs a blocking QuickFIX engine operation
14
41
  // (start/stop) on a libuv worker thread and resolves/rejects a JS Promise.
42
+ // (The per-message SendToTargetWorker lives in session_static.cpp.)
15
43
  //
16
44
  // Why off-thread: start()/stop() synchronously invoke FIX::Application callbacks
17
45
  // (toAdmin/toApp during send-on-logon, onLogout during stop) which the
@@ -22,12 +50,61 @@ namespace napi_quickfix {
22
50
  //
23
51
  // Lifetime: the worker holds a Napi::ObjectReference to the owning wrap object so
24
52
  // it (and its bridge / engine / factories) can't be GC'd while the op is in
25
- // flight. The ref is released in OnOK/OnError (both run on the JS thread).
53
+ // flight. The reference is released when the promise is settled -- through
54
+ // the Settlement handed to onSettled, which the wrap may settle later than
55
+ // OnOK (on the JS thread) when the promise has to wait for deferred work,
56
+ // e.g. an engine destruction queued behind the bridge's pending callbacks.
26
57
  class EngineOpWorker : public Napi::AsyncWorker {
27
58
  public:
28
- // onSettled receives `true` if the op succeeded, `false` if it threw.
59
+ // The promise plus the owner reference, moved out of the worker (which
60
+ // deletes itself once OnOK/OnError returns) so settling can outlive it.
61
+ class Settlement {
62
+ public:
63
+ Settlement(Napi::Promise::Deferred deferred, Napi::ObjectReference ownerRef,
64
+ CapturedError err)
65
+ : deferred_(std::move(deferred)),
66
+ ownerRef_(std::move(ownerRef)),
67
+ err_(std::move(err)) {}
68
+
69
+ // Resolve, or reject with the error the op threw, and release the owner
70
+ // reference. JS thread only; idempotent.
71
+ void Settle() {
72
+ if (done_) return;
73
+ done_ = true;
74
+ Napi::Env env = deferred_.Env();
75
+ Napi::HandleScope scope(env);
76
+ if (err_.has) {
77
+ deferred_.Reject(err_.ToError(env).Value());
78
+ } else {
79
+ deferred_.Resolve(env.Undefined());
80
+ }
81
+ ownerRef_.Reset();
82
+ }
83
+
84
+ // The environment is going away before the promise could be settled:
85
+ // leave the promise pending and the reference to the environment's own
86
+ // cleanup, touching nothing that may already be torn down.
87
+ void Abandon() {
88
+ if (done_) return;
89
+ done_ = true;
90
+ ownerRef_.SuppressDestruct();
91
+ }
92
+
93
+ private:
94
+ Napi::Promise::Deferred deferred_;
95
+ Napi::ObjectReference ownerRef_;
96
+ CapturedError err_;
97
+ bool done_ = false;
98
+ };
99
+
100
+ // onSettled(ok, settlement) runs on the JS thread once op() has returned
101
+ // (ok) or thrown (!ok). It must eventually Settle() (or Abandon()) the
102
+ // settlement -- right away, or later on the JS thread.
103
+ using OnSettled =
104
+ std::function<void(bool, const std::shared_ptr<Settlement>&)>;
105
+
29
106
  EngineOpWorker(Napi::Env env, Napi::Object owner, std::function<void()> op,
30
- std::function<void(bool)> onSettled)
107
+ OnSettled onSettled)
31
108
  : Napi::AsyncWorker(env),
32
109
  deferred_(Napi::Promise::Deferred::New(env)),
33
110
  op_(std::move(op)),
@@ -52,32 +129,36 @@ class EngineOpWorker : public Napi::AsyncWorker {
52
129
 
53
130
  // Runs on the JS thread.
54
131
  void OnOK() override {
55
- Napi::Env env = Env();
56
- Napi::HandleScope scope(env);
57
- if (onSettled_) onSettled_(!err_.has);
58
- if (err_.has) {
59
- deferred_.Reject(err_.ToError(env).Value());
60
- } else {
61
- deferred_.Resolve(env.Undefined());
62
- }
63
- ownerRef_.Reset();
132
+ Napi::HandleScope scope(Env());
133
+ Dispatch();
64
134
  }
65
135
 
66
136
  // Runs on the JS thread (only if AsyncWorker::SetError was used; we don't, so
67
137
  // errors flow through OnOK — but provide OnError for completeness/safety).
68
138
  void OnError(const Napi::Error& e) override {
69
- Napi::Env env = Env();
70
- Napi::HandleScope scope(env);
71
- if (onSettled_) onSettled_(false);
72
- deferred_.Reject(e.Value());
73
- ownerRef_.Reset();
139
+ Napi::HandleScope scope(Env());
140
+ err_.has = true;
141
+ err_.message = e.Message();
142
+ err_.fixErrorName = "Error";
143
+ Dispatch();
74
144
  }
75
145
 
76
146
  private:
147
+ void Dispatch() {
148
+ const bool ok = !err_.has;
149
+ auto settlement = std::make_shared<Settlement>(
150
+ std::move(deferred_), std::move(ownerRef_), std::move(err_));
151
+ if (onSettled_) {
152
+ onSettled_(ok, settlement);
153
+ } else {
154
+ settlement->Settle();
155
+ }
156
+ }
157
+
77
158
  Napi::Promise::Deferred deferred_;
78
159
  Napi::ObjectReference ownerRef_;
79
160
  std::function<void()> op_;
80
- std::function<void(bool)> onSettled_;
161
+ OnSettled onSettled_;
81
162
  CapturedError err_;
82
163
  };
83
164