@homaiohq/napi-quickfix 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CMakeLists.txt +135 -0
  2. package/LICENSE +21 -0
  3. package/README.md +638 -2
  4. package/THIRD_PARTY_LICENSES +48 -0
  5. package/cpp/acceptor_wrap.cpp +468 -0
  6. package/cpp/acceptor_wrap.h +116 -0
  7. package/cpp/addon.cpp +87 -0
  8. package/cpp/application_bridge.cpp +466 -0
  9. package/cpp/application_bridge.h +204 -0
  10. package/cpp/data_dictionary_wrap.cpp +165 -0
  11. package/cpp/data_dictionary_wrap.h +59 -0
  12. package/cpp/engine_common.cpp +5 -0
  13. package/cpp/engine_common.h +127 -0
  14. package/cpp/engine_workers.h +167 -0
  15. package/cpp/errors.h +142 -0
  16. package/cpp/field_map_ops.h +492 -0
  17. package/cpp/group_wrap.cpp +221 -0
  18. package/cpp/group_wrap.h +76 -0
  19. package/cpp/initiator_wrap.cpp +492 -0
  20. package/cpp/initiator_wrap.h +104 -0
  21. package/cpp/message_wrap.cpp +288 -0
  22. package/cpp/message_wrap.h +95 -0
  23. package/cpp/session_id_wrap.cpp +91 -0
  24. package/cpp/session_id_wrap.h +46 -0
  25. package/cpp/session_op_gate.h +201 -0
  26. package/cpp/session_settings_wrap.cpp +98 -0
  27. package/cpp/session_settings_wrap.h +40 -0
  28. package/cpp/session_static.cpp +220 -0
  29. package/cpp/session_wrap.cpp +438 -0
  30. package/cpp/session_wrap.h +144 -0
  31. package/dist/cjs/acceptor.d.ts +29 -0
  32. package/dist/cjs/acceptor.js +38 -0
  33. package/dist/cjs/application.d.ts +115 -0
  34. package/dist/cjs/application.js +62 -0
  35. package/dist/cjs/data-dictionary.d.ts +67 -0
  36. package/dist/cjs/data-dictionary.js +105 -0
  37. package/dist/cjs/engine.d.ts +152 -0
  38. package/dist/cjs/engine.js +183 -0
  39. package/dist/cjs/enums.d.ts +56 -0
  40. package/dist/cjs/enums.js +44 -0
  41. package/dist/cjs/field-value.d.ts +12 -0
  42. package/dist/cjs/field-value.js +40 -0
  43. package/dist/cjs/generated/fields.d.ts +6122 -0
  44. package/dist/cjs/generated/fields.js +6127 -0
  45. package/dist/cjs/generated/values.d.ts +8548 -0
  46. package/dist/cjs/generated/values.js +8580 -0
  47. package/dist/cjs/group.d.ts +126 -0
  48. package/dist/cjs/group.js +185 -0
  49. package/dist/cjs/index.d.ts +68 -0
  50. package/dist/cjs/index.js +73 -0
  51. package/dist/cjs/initiator.d.ts +29 -0
  52. package/dist/cjs/initiator.js +38 -0
  53. package/dist/cjs/load-native.cjs +5 -0
  54. package/dist/cjs/message.d.ts +238 -0
  55. package/dist/cjs/message.js +299 -0
  56. package/dist/cjs/native.d.ts +325 -0
  57. package/dist/cjs/native.js +22 -0
  58. package/dist/cjs/package.json +1 -0
  59. package/dist/cjs/session-id.d.ts +51 -0
  60. package/dist/cjs/session-id.js +87 -0
  61. package/dist/cjs/session-settings.d.ts +46 -0
  62. package/dist/cjs/session-settings.js +75 -0
  63. package/dist/cjs/session.d.ts +213 -0
  64. package/dist/cjs/session.js +337 -0
  65. package/dist/esm/acceptor.d.ts +29 -0
  66. package/dist/esm/acceptor.js +34 -0
  67. package/dist/esm/application.d.ts +115 -0
  68. package/dist/esm/application.js +57 -0
  69. package/dist/esm/data-dictionary.d.ts +67 -0
  70. package/dist/esm/data-dictionary.js +101 -0
  71. package/dist/esm/engine.d.ts +152 -0
  72. package/dist/esm/engine.js +179 -0
  73. package/dist/esm/enums.d.ts +56 -0
  74. package/dist/esm/enums.js +35 -0
  75. package/dist/esm/field-value.d.ts +12 -0
  76. package/dist/esm/field-value.js +37 -0
  77. package/dist/esm/generated/fields.d.ts +6122 -0
  78. package/dist/esm/generated/fields.js +6124 -0
  79. package/dist/esm/generated/values.d.ts +8548 -0
  80. package/dist/esm/generated/values.js +8564 -0
  81. package/dist/esm/group.d.ts +126 -0
  82. package/dist/esm/group.js +181 -0
  83. package/dist/esm/index.d.ts +68 -0
  84. package/dist/esm/index.js +43 -0
  85. package/dist/esm/initiator.d.ts +29 -0
  86. package/dist/esm/initiator.js +34 -0
  87. package/dist/esm/load-native.cjs +5 -0
  88. package/dist/esm/message.d.ts +238 -0
  89. package/dist/esm/message.js +293 -0
  90. package/dist/esm/native.d.ts +325 -0
  91. package/dist/esm/native.js +16 -0
  92. package/dist/esm/session-id.d.ts +51 -0
  93. package/dist/esm/session-id.js +82 -0
  94. package/dist/esm/session-settings.d.ts +46 -0
  95. package/dist/esm/session-settings.js +71 -0
  96. package/dist/esm/session.d.ts +213 -0
  97. package/dist/esm/session.js +329 -0
  98. package/package.json +95 -4
  99. package/prebuilds/darwin-arm64/node.napi.node +0 -0
  100. package/prebuilds/darwin-x64/node.napi.node +0 -0
  101. package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
  102. package/prebuilds/linux-x64/node.napi.musl.node +0 -0
  103. package/prebuilds/win32-x64/node.napi.node +0 -0
  104. package/src/load-native.cjs +5 -0
@@ -0,0 +1,167 @@
1
+ #ifndef NAPI_QUICKFIX_ENGINE_WORKERS_H
2
+ #define NAPI_QUICKFIX_ENGINE_WORKERS_H
3
+
4
+ #include <napi.h>
5
+
6
+ #include <condition_variable>
7
+ #include <functional>
8
+ #include <memory>
9
+ #include <mutex>
10
+ #include <utility>
11
+
12
+ #include "errors.h"
13
+
14
+ namespace napi_quickfix {
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
+
40
+ // A generic AsyncWorker that runs a blocking QuickFIX engine operation
41
+ // (start/stop) on a libuv worker thread and resolves/rejects a JS Promise.
42
+ // (The per-message SendToTargetWorker lives in session_static.cpp.)
43
+ //
44
+ // Why off-thread: start()/stop() synchronously invoke FIX::Application callbacks
45
+ // (toAdmin/toApp during send-on-logon, onLogout during stop) which the
46
+ // ApplicationBridge dispatches back to the JS thread via a TSFN BlockingCall +
47
+ // future. If the operation ran ON the JS thread, the event loop couldn't service
48
+ // the TSFN trampoline and we'd deadlock. Running it on a worker thread keeps the
49
+ // main event loop free to run CallJs.
50
+ //
51
+ // Lifetime: the worker holds a Napi::ObjectReference to the owning wrap object so
52
+ // it (and its bridge / engine / factories) can't be GC'd while the op is in
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.
57
+ class EngineOpWorker : public Napi::AsyncWorker {
58
+ public:
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
+
106
+ EngineOpWorker(Napi::Env env, Napi::Object owner, std::function<void()> op,
107
+ OnSettled onSettled)
108
+ : Napi::AsyncWorker(env),
109
+ deferred_(Napi::Promise::Deferred::New(env)),
110
+ op_(std::move(op)),
111
+ onSettled_(std::move(onSettled)) {
112
+ ownerRef_ = Napi::Persistent(owner);
113
+ }
114
+
115
+ Napi::Promise Promise() { return deferred_.Promise(); }
116
+
117
+ // Runs on a libuv worker thread. NO Napi/JS access allowed here.
118
+ void Execute() override {
119
+ try {
120
+ op_();
121
+ } catch (const std::exception& e) {
122
+ err_.Capture(e);
123
+ } catch (...) {
124
+ err_.has = true;
125
+ err_.message = "unknown native error";
126
+ err_.fixErrorName = "Error";
127
+ }
128
+ }
129
+
130
+ // Runs on the JS thread.
131
+ void OnOK() override {
132
+ Napi::HandleScope scope(Env());
133
+ Dispatch();
134
+ }
135
+
136
+ // Runs on the JS thread (only if AsyncWorker::SetError was used; we don't, so
137
+ // errors flow through OnOK — but provide OnError for completeness/safety).
138
+ void OnError(const Napi::Error& e) override {
139
+ Napi::HandleScope scope(Env());
140
+ err_.has = true;
141
+ err_.message = e.Message();
142
+ err_.fixErrorName = "Error";
143
+ Dispatch();
144
+ }
145
+
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
+
158
+ Napi::Promise::Deferred deferred_;
159
+ Napi::ObjectReference ownerRef_;
160
+ std::function<void()> op_;
161
+ OnSettled onSettled_;
162
+ CapturedError err_;
163
+ };
164
+
165
+ } // namespace napi_quickfix
166
+
167
+ #endif // NAPI_QUICKFIX_ENGINE_WORKERS_H
package/cpp/errors.h ADDED
@@ -0,0 +1,142 @@
1
+ // C++ -> Napi::Error translation for the QuickFIX wrapper.
2
+ //
3
+ // Every wrapper method funnels caught exceptions through here so that JS sees a
4
+ // consistent Error shape:
5
+ // - name: "QuickFixError"
6
+ // - message: the exception's what()
7
+ // - fixError: a discriminator string. For FIX::Exception this is its `.type`
8
+ // (e.g. "Field not found: 35", "Configuration failed", ...) which
9
+ // is stable per concrete type; we ALSO set fixErrorName to the C++
10
+ // class name (e.g. "FieldNotFound") which is the value TS keys on.
11
+ // - detail: the exception's `.detail` when available.
12
+ //
13
+ // NAPI_CPP_EXCEPTIONS is enabled, so throwing a Napi::Error unwinds into a JS
14
+ // throw at the call boundary.
15
+ #ifndef NAPI_QUICKFIX_ERRORS_H
16
+ #define NAPI_QUICKFIX_ERRORS_H
17
+
18
+ #include <napi.h>
19
+
20
+ #include <exception>
21
+ #include <string>
22
+
23
+ #include "quickfix/Exceptions.h"
24
+
25
+ namespace napi_quickfix {
26
+
27
+ // Map a concrete FIX::Exception to a stable class-name discriminator. QuickFIX
28
+ // exceptions don't carry RTTI-friendly names, so we dynamic_cast the well-known
29
+ // concrete types (order: most-specific first is not required since they don't
30
+ // inherit from each other beyond Exception).
31
+ inline const char* FixErrorName(const FIX::Exception& e) {
32
+ if (dynamic_cast<const FIX::ConfigError*>(&e)) return "ConfigError";
33
+ if (dynamic_cast<const FIX::RuntimeError*>(&e)) return "RuntimeError";
34
+ if (dynamic_cast<const FIX::FieldNotFound*>(&e)) return "FieldNotFound";
35
+ if (dynamic_cast<const FIX::FieldConvertError*>(&e)) return "FieldConvertError";
36
+ if (dynamic_cast<const FIX::MessageParseError*>(&e)) return "MessageParseError";
37
+ if (dynamic_cast<const FIX::InvalidMessage*>(&e)) return "InvalidMessage";
38
+ if (dynamic_cast<const FIX::IncorrectDataFormat*>(&e)) return "IncorrectDataFormat";
39
+ if (dynamic_cast<const FIX::IncorrectTagValue*>(&e)) return "IncorrectTagValue";
40
+ if (dynamic_cast<const FIX::IncorrectMessageStructure*>(&e)) return "IncorrectMessageStructure";
41
+ if (dynamic_cast<const FIX::InvalidTagNumber*>(&e)) return "InvalidTagNumber";
42
+ if (dynamic_cast<const FIX::RequiredTagMissing*>(&e)) return "RequiredTagMissing";
43
+ if (dynamic_cast<const FIX::TagNotDefinedForMessage*>(&e)) return "TagNotDefinedForMessage";
44
+ if (dynamic_cast<const FIX::NoTagValue*>(&e)) return "NoTagValue";
45
+ if (dynamic_cast<const FIX::DuplicateFieldNumber*>(&e)) return "DuplicateFieldNumber";
46
+ if (dynamic_cast<const FIX::InvalidMessageType*>(&e)) return "InvalidMessageType";
47
+ if (dynamic_cast<const FIX::UnsupportedMessageType*>(&e)) return "UnsupportedMessageType";
48
+ if (dynamic_cast<const FIX::UnsupportedVersion*>(&e)) return "UnsupportedVersion";
49
+ if (dynamic_cast<const FIX::TagOutOfOrder*>(&e)) return "TagOutOfOrder";
50
+ if (dynamic_cast<const FIX::RepeatedTag*>(&e)) return "RepeatedTag";
51
+ if (dynamic_cast<const FIX::RepeatingGroupCountMismatch*>(&e)) return "RepeatingGroupCountMismatch";
52
+ if (dynamic_cast<const FIX::DoNotSend*>(&e)) return "DoNotSend";
53
+ if (dynamic_cast<const FIX::RejectLogon*>(&e)) return "RejectLogon";
54
+ if (dynamic_cast<const FIX::SessionNotFound*>(&e)) return "SessionNotFound";
55
+ if (dynamic_cast<const FIX::DataDictionaryNotFound*>(&e)) return "DataDictionaryNotFound";
56
+ if (dynamic_cast<const FIX::IOException*>(&e)) return "IOException";
57
+ if (dynamic_cast<const FIX::SocketException*>(&e)) return "SocketException";
58
+ return "FixException";
59
+ }
60
+
61
+ // Build (but do not throw) a Napi::Error from a FIX::Exception.
62
+ inline Napi::Error MakeFixError(Napi::Env env, const FIX::Exception& e) {
63
+ Napi::Error err = Napi::Error::New(env, e.what());
64
+ err.Set("name", Napi::String::New(env, "QuickFixError"));
65
+ err.Set("fixError", Napi::String::New(env, e.type));
66
+ err.Set("fixErrorName", Napi::String::New(env, FixErrorName(e)));
67
+ err.Set("detail", Napi::String::New(env, e.detail));
68
+ return err;
69
+ }
70
+
71
+ // Build (but do not throw) a Napi::Error from a generic std::exception.
72
+ inline Napi::Error MakeStdError(Napi::Env env, const std::exception& e) {
73
+ Napi::Error err = Napi::Error::New(env, e.what());
74
+ err.Set("name", Napi::String::New(env, "QuickFixError"));
75
+ err.Set("fixErrorName", Napi::String::New(env, "Error"));
76
+ return err;
77
+ }
78
+
79
+ // A plain-data capture of an exception's mapped fields, safe to carry across
80
+ // threads (an AsyncWorker's Execute() runs off the JS thread and MUST NOT touch
81
+ // Napi/JS). Populate it in a catch block on the worker thread, then rebuild a
82
+ // Napi::Error from it in OnError/OnOK (which run on the JS thread).
83
+ struct CapturedError {
84
+ bool has = false;
85
+ bool isFix = false;
86
+ std::string message; // what()
87
+ std::string fixError; // FIX::Exception::type (for FIX exceptions)
88
+ std::string fixErrorName; // stable class-name discriminator
89
+ std::string detail; // FIX::Exception::detail
90
+
91
+ void Capture(const std::exception& e) {
92
+ has = true;
93
+ message = e.what();
94
+ if (const auto* fe = dynamic_cast<const FIX::Exception*>(&e)) {
95
+ isFix = true;
96
+ fixError = fe->type;
97
+ fixErrorName = FixErrorName(*fe);
98
+ detail = fe->detail;
99
+ } else {
100
+ isFix = false;
101
+ fixErrorName = "Error";
102
+ }
103
+ }
104
+
105
+ // Build (do not throw) a Napi::Error from the captured fields.
106
+ Napi::Error ToError(Napi::Env env) const {
107
+ Napi::Error err = Napi::Error::New(env, message);
108
+ err.Set("name", Napi::String::New(env, "QuickFixError"));
109
+ if (isFix) {
110
+ err.Set("fixError", Napi::String::New(env, fixError));
111
+ err.Set("fixErrorName", Napi::String::New(env, fixErrorName));
112
+ err.Set("detail", Napi::String::New(env, detail));
113
+ } else {
114
+ err.Set("fixErrorName", Napi::String::New(env, fixErrorName));
115
+ }
116
+ return err;
117
+ }
118
+ };
119
+
120
+ // Translate a caught exception and throw the corresponding Napi::Error. Callers
121
+ // use this inside a catch block, e.g.:
122
+ // try { ... } catch (const std::exception& e) { TranslateAndThrow(env, e); }
123
+ [[noreturn]] inline void TranslateAndThrow(Napi::Env env, const std::exception& e) {
124
+ if (const auto* fe = dynamic_cast<const FIX::Exception*>(&e)) {
125
+ throw MakeFixError(env, *fe);
126
+ }
127
+ throw MakeStdError(env, e);
128
+ }
129
+
130
+ } // namespace napi_quickfix
131
+
132
+ // Wrap a QuickFIX-calling body so any std::exception is converted to a JS throw.
133
+ // Usage:
134
+ // NQ_TRY(env) { ...quickfix calls that may throw... } NQ_CATCH(env)
135
+ // The body may `return` a Napi::Value normally.
136
+ #define NQ_TRY(env) try
137
+ #define NQ_CATCH(env) \
138
+ catch (const std::exception& _nq_e) { \
139
+ ::napi_quickfix::TranslateAndThrow((env), _nq_e); \
140
+ }
141
+
142
+ #endif // NAPI_QUICKFIX_ERRORS_H