@homaiohq/napi-quickfix 0.0.0-stage → 0.1.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 (131) hide show
  1. package/CMakeLists.txt +131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +421 -2
  4. package/THIRD_PARTY_LICENSES +48 -0
  5. package/cpp/acceptor_wrap.cpp +234 -0
  6. package/cpp/acceptor_wrap.h +57 -0
  7. package/cpp/addon.cpp +83 -0
  8. package/cpp/application_bridge.cpp +382 -0
  9. package/cpp/application_bridge.h +151 -0
  10. package/cpp/data_dictionary_wrap.cpp +79 -0
  11. package/cpp/data_dictionary_wrap.h +37 -0
  12. package/cpp/engine_common.cpp +5 -0
  13. package/cpp/engine_common.h +19 -0
  14. package/cpp/engine_workers.h +86 -0
  15. package/cpp/enums.cpp +66 -0
  16. package/cpp/errors.h +142 -0
  17. package/cpp/initiator_wrap.cpp +255 -0
  18. package/cpp/initiator_wrap.h +69 -0
  19. package/cpp/message_wrap.cpp +195 -0
  20. package/cpp/message_wrap.h +53 -0
  21. package/cpp/session_id_wrap.cpp +91 -0
  22. package/cpp/session_id_wrap.h +46 -0
  23. package/cpp/session_settings_wrap.cpp +98 -0
  24. package/cpp/session_settings_wrap.h +40 -0
  25. package/cpp/session_static.cpp +93 -0
  26. package/dist/cjs/acceptor.d.ts +30 -0
  27. package/dist/cjs/acceptor.d.ts.map +1 -0
  28. package/dist/cjs/acceptor.js +39 -0
  29. package/dist/cjs/acceptor.js.map +1 -0
  30. package/dist/cjs/application.d.ts +116 -0
  31. package/dist/cjs/application.d.ts.map +1 -0
  32. package/dist/cjs/application.js +63 -0
  33. package/dist/cjs/application.js.map +1 -0
  34. package/dist/cjs/data-dictionary.d.ts +49 -0
  35. package/dist/cjs/data-dictionary.d.ts.map +1 -0
  36. package/dist/cjs/data-dictionary.js +77 -0
  37. package/dist/cjs/data-dictionary.js.map +1 -0
  38. package/dist/cjs/engine.d.ts +114 -0
  39. package/dist/cjs/engine.d.ts.map +1 -0
  40. package/dist/cjs/engine.js +138 -0
  41. package/dist/cjs/engine.js.map +1 -0
  42. package/dist/cjs/enums.d.ts +22 -0
  43. package/dist/cjs/enums.d.ts.map +1 -0
  44. package/dist/cjs/enums.js +49 -0
  45. package/dist/cjs/enums.js.map +1 -0
  46. package/dist/cjs/generated/fields.d.ts +6123 -0
  47. package/dist/cjs/generated/fields.d.ts.map +1 -0
  48. package/dist/cjs/generated/fields.js +6128 -0
  49. package/dist/cjs/generated/fields.js.map +1 -0
  50. package/dist/cjs/index.d.ts +47 -0
  51. package/dist/cjs/index.d.ts.map +1 -0
  52. package/dist/cjs/index.js +73 -0
  53. package/dist/cjs/index.js.map +1 -0
  54. package/dist/cjs/initiator.d.ts +30 -0
  55. package/dist/cjs/initiator.d.ts.map +1 -0
  56. package/dist/cjs/initiator.js +39 -0
  57. package/dist/cjs/initiator.js.map +1 -0
  58. package/dist/cjs/load-native.cjs +5 -0
  59. package/dist/cjs/message.d.ts +114 -0
  60. package/dist/cjs/message.d.ts.map +1 -0
  61. package/dist/cjs/message.js +162 -0
  62. package/dist/cjs/message.js.map +1 -0
  63. package/dist/cjs/native.d.ts +163 -0
  64. package/dist/cjs/native.d.ts.map +1 -0
  65. package/dist/cjs/native.js +23 -0
  66. package/dist/cjs/native.js.map +1 -0
  67. package/dist/cjs/package.json +1 -0
  68. package/dist/cjs/session-id.d.ts +40 -0
  69. package/dist/cjs/session-id.d.ts.map +1 -0
  70. package/dist/cjs/session-id.js +67 -0
  71. package/dist/cjs/session-id.js.map +1 -0
  72. package/dist/cjs/session-settings.d.ts +47 -0
  73. package/dist/cjs/session-settings.d.ts.map +1 -0
  74. package/dist/cjs/session-settings.js +76 -0
  75. package/dist/cjs/session-settings.js.map +1 -0
  76. package/dist/esm/acceptor.d.ts +30 -0
  77. package/dist/esm/acceptor.d.ts.map +1 -0
  78. package/dist/esm/acceptor.js +35 -0
  79. package/dist/esm/acceptor.js.map +1 -0
  80. package/dist/esm/application.d.ts +116 -0
  81. package/dist/esm/application.d.ts.map +1 -0
  82. package/dist/esm/application.js +58 -0
  83. package/dist/esm/application.js.map +1 -0
  84. package/dist/esm/data-dictionary.d.ts +49 -0
  85. package/dist/esm/data-dictionary.d.ts.map +1 -0
  86. package/dist/esm/data-dictionary.js +73 -0
  87. package/dist/esm/data-dictionary.js.map +1 -0
  88. package/dist/esm/engine.d.ts +114 -0
  89. package/dist/esm/engine.d.ts.map +1 -0
  90. package/dist/esm/engine.js +134 -0
  91. package/dist/esm/engine.js.map +1 -0
  92. package/dist/esm/enums.d.ts +22 -0
  93. package/dist/esm/enums.d.ts.map +1 -0
  94. package/dist/esm/enums.js +45 -0
  95. package/dist/esm/enums.js.map +1 -0
  96. package/dist/esm/generated/fields.d.ts +6123 -0
  97. package/dist/esm/generated/fields.d.ts.map +1 -0
  98. package/dist/esm/generated/fields.js +6125 -0
  99. package/dist/esm/generated/fields.js.map +1 -0
  100. package/dist/esm/index.d.ts +47 -0
  101. package/dist/esm/index.d.ts.map +1 -0
  102. package/dist/esm/index.js +52 -0
  103. package/dist/esm/index.js.map +1 -0
  104. package/dist/esm/initiator.d.ts +30 -0
  105. package/dist/esm/initiator.d.ts.map +1 -0
  106. package/dist/esm/initiator.js +35 -0
  107. package/dist/esm/initiator.js.map +1 -0
  108. package/dist/esm/load-native.cjs +5 -0
  109. package/dist/esm/message.d.ts +114 -0
  110. package/dist/esm/message.d.ts.map +1 -0
  111. package/dist/esm/message.js +156 -0
  112. package/dist/esm/message.js.map +1 -0
  113. package/dist/esm/native.d.ts +163 -0
  114. package/dist/esm/native.d.ts.map +1 -0
  115. package/dist/esm/native.js +17 -0
  116. package/dist/esm/native.js.map +1 -0
  117. package/dist/esm/session-id.d.ts +40 -0
  118. package/dist/esm/session-id.d.ts.map +1 -0
  119. package/dist/esm/session-id.js +63 -0
  120. package/dist/esm/session-id.js.map +1 -0
  121. package/dist/esm/session-settings.d.ts +47 -0
  122. package/dist/esm/session-settings.d.ts.map +1 -0
  123. package/dist/esm/session-settings.js +72 -0
  124. package/dist/esm/session-settings.js.map +1 -0
  125. package/package.json +83 -4
  126. package/prebuilds/darwin-arm64/node.napi.node +0 -0
  127. package/prebuilds/darwin-x64/node.napi.node +0 -0
  128. package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
  129. package/prebuilds/linux-x64/node.napi.musl.node +0 -0
  130. package/prebuilds/win32-x64/node.napi.node +0 -0
  131. package/src/load-native.cjs +5 -0
package/CMakeLists.txt ADDED
@@ -0,0 +1,131 @@
1
+ cmake_minimum_required(VERSION 3.15)
2
+
3
+ project(napi_quickfix CXX)
4
+
5
+ set(CMAKE_CXX_STANDARD 17)
6
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
7
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
8
+
9
+ include(FetchContent)
10
+
11
+ # ---------------------------------------------------------------------------
12
+ # QuickFIX (build-time dependency, nothing vendored). Pinned to v1.16.0.
13
+ # ---------------------------------------------------------------------------
14
+ # Force QuickFIX build options BEFORE MakeAvailable so add_subdirectory picks
15
+ # them up from the cache.
16
+ set(QUICKFIX_SHARED_LIBS OFF CACHE BOOL "" FORCE)
17
+ set(QUICKFIX_EXAMPLES OFF CACHE BOOL "" FORCE)
18
+ set(QUICKFIX_TESTS OFF CACHE BOOL "" FORCE)
19
+ set(HAVE_SSL OFF CACHE BOOL "" FORCE)
20
+ set(HAVE_MYSQL OFF CACHE BOOL "" FORCE)
21
+ set(HAVE_POSTGRESQL OFF CACHE BOOL "" FORCE)
22
+
23
+ FetchContent_Declare(
24
+ quickfix
25
+ GIT_REPOSITORY https://github.com/quickfix/quickfix.git
26
+ GIT_TAG v1.16.0
27
+ GIT_SHALLOW TRUE
28
+ )
29
+ FetchContent_MakeAvailable(quickfix)
30
+
31
+ # Give musl the same clock as glibc. QuickFIX picks its clock in
32
+ # src/C++/FieldTypes.cpp:
33
+ # #if defined(_POSIX_SOURCE) || defined(HAVE_GETTIMEOFDAY) -> gettimeofday, us
34
+ # #elif defined(HAVE_FTIME) -> ftime, ms only
35
+ # glibc's <features.h> defines _POSIX_SOURCE whenever _GNU_SOURCE is set (and g++
36
+ # always sets it), so glibc takes the gettimeofday branch for free. musl never
37
+ # defines _POSIX_SOURCE, and QuickFIX -- although it probes gettimeofday in
38
+ # cmake/QuickfixPlatformSettings.cmake -- never propagates HAVE_GETTIMEOFDAY to the
39
+ # compiler (it is absent from cmake_config.h.in). musl does get HAVE_FTIME, so it
40
+ # lands on the obsolete ftime() branch and is capped at MILLISECOND resolution.
41
+ # That is invisible at the default TimestampPrecision=3, but with
42
+ # TimestampPrecision=6 a musl build emits .NNN000 where glibc emits true
43
+ # microseconds. Forcing the probe result on removes that divergence.
44
+ # No-op on glibc, which already takes the first branch.
45
+ #
46
+ # UNIX-only on purpose: MSVC has no gettimeofday() to point HAVE_GETTIMEOFDAY at, and
47
+ # upstream QuickFIX does propagate HAVE_FTIME on Windows (it probes for it in
48
+ # src/C++/CMakeLists.txt), so Windows is millisecond-capped by design. The loopback
49
+ # test's microsecond-tail assertion is POSIX-gated to match.
50
+ if(UNIX AND TARGET quickfix)
51
+ target_compile_definitions(quickfix PRIVATE HAVE_GETTIMEOFDAY)
52
+ endif()
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Include-tree shim (risk #1): QuickFIX ships headers flat in src/C++/*.h but
56
+ # code includes them as "quickfix/Message.h". Assemble a quickfix/ include tree
57
+ # at configure time (Windows-safe: file copies, no symlinks).
58
+ # ---------------------------------------------------------------------------
59
+ set(QF_INCLUDE_ROOT "${CMAKE_BINARY_DIR}/qf_include")
60
+ file(GLOB QF_HEADERS "${quickfix_SOURCE_DIR}/src/C++/*.h")
61
+ foreach(_qf_hdr ${QF_HEADERS})
62
+ get_filename_component(_qf_name "${_qf_hdr}" NAME)
63
+ configure_file("${_qf_hdr}" "${QF_INCLUDE_ROOT}/quickfix/${_qf_name}" COPYONLY)
64
+ endforeach()
65
+
66
+ # Copy any generated config.h (may live in the binary dir after configure).
67
+ file(GLOB_RECURSE QF_GENERATED_CONFIG "${quickfix_BINARY_DIR}/*config.h")
68
+ foreach(_qf_cfg ${QF_GENERATED_CONFIG})
69
+ get_filename_component(_qf_cfg_name "${_qf_cfg}" NAME)
70
+ configure_file("${_qf_cfg}" "${QF_INCLUDE_ROOT}/quickfix/${_qf_cfg_name}" COPYONLY)
71
+ endforeach()
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # node-addon-api include dir (resolved via node).
75
+ # ---------------------------------------------------------------------------
76
+ execute_process(
77
+ COMMAND node -p "require('node-addon-api').include_dir"
78
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
79
+ OUTPUT_VARIABLE NODE_ADDON_API_DIR
80
+ OUTPUT_STRIP_TRAILING_WHITESPACE
81
+ )
82
+ string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
83
+ string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # The native addon.
87
+ # ---------------------------------------------------------------------------
88
+ file(GLOB ADDON_SOURCES "${CMAKE_SOURCE_DIR}/cpp/*.cpp")
89
+
90
+ add_library(napi_quickfix SHARED ${ADDON_SOURCES} ${CMAKE_JS_SRC})
91
+ set_target_properties(napi_quickfix PROPERTIES PREFIX "" SUFFIX ".node")
92
+
93
+ target_include_directories(napi_quickfix PRIVATE
94
+ ${CMAKE_JS_INC}
95
+ ${QF_INCLUDE_ROOT}
96
+ ${quickfix_SOURCE_DIR}/src/C++
97
+ ${NODE_ADDON_API_DIR}
98
+ )
99
+
100
+ target_compile_definitions(napi_quickfix PRIVATE
101
+ NAPI_VERSION=9
102
+ NAPI_CPP_EXCEPTIONS
103
+ NODE_ADDON_API_DISABLE_DEPRECATED
104
+ )
105
+
106
+ target_link_libraries(napi_quickfix PRIVATE ${CMAKE_JS_LIB} quickfix)
107
+
108
+ if(UNIX AND NOT APPLE)
109
+ find_package(Threads REQUIRED)
110
+ target_link_libraries(napi_quickfix PRIVATE Threads::Threads)
111
+ # Statically absorb libstdc++/libgcc so a prebuild does not pin consumers to the
112
+ # build image's GLIBCXX version. This matters most for musl: docker-node only keeps
113
+ # the newest Alpine bases, so node:22-alpine's GCC keeps advancing (already 15.2 on
114
+ # Alpine 3.24) and a consumer on an older node:22-alpineX.Y would otherwise hit
115
+ # "GLIBCXX_3.4.3x not found". Safe here because the addon is a leaf: C++ exceptions
116
+ # and RTTI never cross out of the .node (Node's addon ABI is C, and QuickFIX is
117
+ # statically linked into this same object), and Node dlopen()s addons RTLD_LOCAL, so
118
+ # the absorbed symbols stay private to this module.
119
+ target_link_options(napi_quickfix PRIVATE -static-libstdc++ -static-libgcc)
120
+ endif()
121
+
122
+ if(APPLE)
123
+ find_package(Threads REQUIRED)
124
+ target_link_libraries(napi_quickfix PRIVATE Threads::Threads)
125
+ target_link_options(napi_quickfix PRIVATE -undefined dynamic_lookup)
126
+ endif()
127
+
128
+ if(WIN32)
129
+ target_link_libraries(napi_quickfix PRIVATE ws2_32)
130
+ target_compile_definitions(napi_quickfix PRIVATE NOMINMAX _WIN32_WINNT=0x0601)
131
+ endif()
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Homaio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,422 @@
1
- # Temporary Holding Version
1
+ # @homaiohq/napi-quickfix
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/homaiohq/napi-quickfix/actions/workflows/ci.yml/badge.svg)](https://github.com/homaiohq/napi-quickfix/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/@homaiohq/napi-quickfix.svg)](https://www.npmjs.com/package/@homaiohq/napi-quickfix)
5
+ [![Node.js](https://img.shields.io/node/v/@homaiohq/napi-quickfix.svg)](https://nodejs.org)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
7
+
8
+ A modern **Node-API** (N-API / `node-addon-api`) C++ binding for the
9
+ [QuickFIX](https://github.com/quickfix/quickfix) FIX-protocol engine. It exposes
10
+ both the *pure* message layer (build, parse, and validate FIX messages) and the
11
+ full *session engine* (socket initiator/acceptor with `FIX::Application`
12
+ callbacks bridged to JavaScript) — with prebuilt binaries so most consumers need
13
+ **no C++ compiler**, dual **ESM + CommonJS** entry points, and TypeScript types
14
+ included.
15
+
16
+ ## Features
17
+
18
+ - **Modern Node-API** — built on `node-addon-api` (N-API v9), not the legacy
19
+ NAN/node-gyp stack. One prebuild per platform serves every supported Node LTS.
20
+ - **No compiler needed** — prebuilt native binaries ship in the npm package (via
21
+ `prebuildify` + `node-gyp-build`); a source build via CMake is only a fallback.
22
+ - **Dual ESM + CJS** — a proper `exports` map with `import` and `require` entry
23
+ points.
24
+ - **TypeScript types included** — hand-written `.d.ts` for the full public API.
25
+ - **Full FIX surface** — the session engine (`Initiator`/`Acceptor` +
26
+ synchronous application handlers) *and* the pure layer (`Message`,
27
+ `SessionSettings`, `DataDictionary`, `SessionID`).
28
+ - **Ergonomic, safe API** — chainable message building with automatic
29
+ header/trailer field routing, typed rejections, and C++ exceptions surfaced as
30
+ JavaScript `Error`s.
31
+
32
+ ## Install
33
+
34
+ ```sh
35
+ yarn add @homaiohq/napi-quickfix
36
+ # or
37
+ npm install @homaiohq/napi-quickfix
38
+ # or
39
+ pnpm add @homaiohq/napi-quickfix
40
+ ```
41
+
42
+ Prebuilt binaries are published for **Linux x64** (both **glibc** and
43
+ **musl/Alpine**), **macOS** x64 + arm64, and **Windows** x64. On these platforms
44
+ installation requires no toolchain.
45
+
46
+ The two Linux binaries are libc-tagged and picked automatically: `node-gyp-build`
47
+ selects the musl build when `/etc/alpine-release` exists. On a musl distro that
48
+ is *not* Alpine, set `LIBC=musl` to force it.
49
+
50
+ If a matching prebuild is not available for your platform/arch, the install
51
+ falls back to building from source, which requires:
52
+
53
+ - **CMake** (>= 3.12)
54
+ - A **C++17** compiler (GCC / Clang / MSVC)
55
+
56
+ On Alpine/musl that is `apk add build-base cmake git` — `build-base` supplies
57
+ g++/make, and `git` is needed for the QuickFIX `FetchContent` clone.
58
+
59
+ QuickFIX itself is fetched at build time via CMake `FetchContent` (pinned to
60
+ `v1.16.0`) — nothing is vendored or committed to this repository.
61
+
62
+ Releases are published from GitHub Actions via npm Trusted Publishing with
63
+ [provenance](https://docs.npmjs.com/generating-provenance-statements), so you can
64
+ check that an installed version was built from this repository:
65
+
66
+ ```sh
67
+ npm audit signatures
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ ### ESM
73
+
74
+ ```ts
75
+ import {
76
+ Message,
77
+ SessionSettings,
78
+ Initiator,
79
+ FixReject,
80
+ sendToTarget,
81
+ FIELD,
82
+ MsgType,
83
+ } from '@homaiohq/napi-quickfix';
84
+
85
+ // Build a message. Header/trailer fields (e.g. MsgType) auto-route to the right
86
+ // section, so setField is all you usually need. setField is chainable.
87
+ const order = new Message()
88
+ .setField(FIELD.MsgType, MsgType.NewOrderSingle)
89
+ .setField(FIELD.ClOrdID, 'order-1')
90
+ .setField(FIELD.Symbol, 'AAPL')
91
+ .setField(FIELD.OrderQty, 100);
92
+
93
+ console.log(order.getMsgType()); // 'D'
94
+ console.log(order.toPretty());
95
+
96
+ // Parse a raw FIX wire string back into a Message.
97
+ const parsed = Message.parse(order.toString());
98
+
99
+ // Load session settings from an in-memory config.
100
+ const settings = SessionSettings.fromString(`
101
+ [DEFAULT]
102
+ ConnectionType=initiator
103
+ SocketConnectHost=127.0.0.1
104
+ SocketConnectPort=5001
105
+ HeartBtInt=30
106
+ UseDataDictionary=N
107
+ StartTime=00:00:00
108
+ EndTime=00:00:00
109
+
110
+ [SESSION]
111
+ BeginString=FIX.4.4
112
+ SenderCompID=CLIENT
113
+ TargetCompID=BROKER
114
+ `);
115
+
116
+ // Create the initiator with synchronous application handlers.
117
+ const initiator = new Initiator({
118
+ settings,
119
+ store: 'memory',
120
+ log: 'none',
121
+ handlers: {
122
+ onLogon(sessionID) {
123
+ console.log('logged on:', sessionID.toString());
124
+ // sessionID.toString() -> "FIX.4.4:CLIENT->BROKER"
125
+ sendToTarget(order, sessionID);
126
+ },
127
+ toAdmin(msg) {
128
+ // Handlers run synchronously and may MUTATE the outbound message.
129
+ if (msg.getMsgType() === MsgType.Logon) {
130
+ msg.setField(FIELD.Username, 'user');
131
+ msg.setField(FIELD.Password, 'secret');
132
+ }
133
+ },
134
+ fromApp(msg) {
135
+ // Reject an inbound message by THROWING a typed rejection.
136
+ if (msg.getMsgType() === 'X') {
137
+ throw new FixReject('UnsupportedMessageType');
138
+ }
139
+ console.log('received:', msg.getMsgType());
140
+ },
141
+ },
142
+ });
143
+
144
+ // Observe-only events (cannot mutate/reject) are also emitted.
145
+ initiator.on('logout', (id) => console.log('logged out:', id.toString()));
146
+
147
+ initiator.start();
148
+
149
+ // ... later, shut down cleanly (drain gracefully).
150
+ process.on('SIGINT', () => {
151
+ initiator.stop();
152
+ });
153
+ ```
154
+
155
+ ### CommonJS
156
+
157
+ ```js
158
+ const {
159
+ Message,
160
+ SessionSettings,
161
+ Initiator,
162
+ FIELD,
163
+ MsgType,
164
+ } = require('@homaiohq/napi-quickfix');
165
+
166
+ const order = new Message()
167
+ .setField(FIELD.MsgType, MsgType.NewOrderSingle)
168
+ .setField(FIELD.ClOrdID, 'order-1')
169
+ .setField(FIELD.Symbol, 'AAPL');
170
+
171
+ const settings = SessionSettings.fromString(`
172
+ [DEFAULT]
173
+ ConnectionType=initiator
174
+ SocketConnectHost=127.0.0.1
175
+ SocketConnectPort=5001
176
+ HeartBtInt=30
177
+ UseDataDictionary=N
178
+ StartTime=00:00:00
179
+ EndTime=00:00:00
180
+
181
+ [SESSION]
182
+ BeginString=FIX.4.4
183
+ SenderCompID=CLIENT
184
+ TargetCompID=BROKER
185
+ `);
186
+
187
+ const initiator = new Initiator({
188
+ settings,
189
+ store: 'memory',
190
+ log: 'none',
191
+ handlers: {
192
+ onLogon: (id) => console.log('logged on:', id.toString()),
193
+ },
194
+ });
195
+
196
+ initiator.start();
197
+ // initiator.stop() when done.
198
+ ```
199
+
200
+ ## API overview
201
+
202
+ The full type surface is documented in the shipped TypeScript declarations
203
+ (`dist/esm/index.d.ts`); the reference below is a concise summary. All exports
204
+ come from `@homaiohq/napi-quickfix`.
205
+
206
+ ### Message
207
+
208
+ ```ts
209
+ new Message(raw?: string, opts?: { validate?: boolean });
210
+ Message.parse(raw: string, opts?: { validate?: boolean }): Message;
211
+
212
+ message.setField(tag: number, value: string | number): this; // chainable, auto-routes header/trailer fields
213
+ message.getField(tag: number): string;
214
+ message.setHeaderField(tag, value): this; message.getHeaderField(tag): string;
215
+ message.setTrailerField(tag, value): this; message.getTrailerField(tag): string;
216
+ message.getMsgType(): string;
217
+ message.toString(): string; // raw SOH-delimited wire string
218
+ message.toPretty(): string; // human-readable
219
+ message.toJSON(): { msgType?: string; raw: string };
220
+
221
+ createMessage(fields?: Record<number, string | number>): Message;
222
+ parseMessage(raw: string, opts?: { validate?: boolean }): Message;
223
+ ```
224
+
225
+ Field values are accepted as `string | number` (numbers are stringified) and
226
+ always returned as `string`. Header/trailer fields such as `MsgType`,
227
+ `BeginString`, and `CheckSum` are **automatically routed** to the correct
228
+ section by `setField`, so you rarely need `setHeaderField`/`setTrailerField`
229
+ directly.
230
+
231
+ ### SessionID
232
+
233
+ ```ts
234
+ new SessionID(beginString: string, senderCompID: string, targetCompID: string, qualifier?: string);
235
+ sessionID.beginString; sessionID.senderCompID;
236
+ sessionID.targetCompID; sessionID.sessionQualifier;
237
+ sessionID.toString(); // e.g. "FIX.4.4:CLIENT->BROKER"
238
+ ```
239
+
240
+ ### SessionSettings
241
+
242
+ ```ts
243
+ SessionSettings.fromString(cfg: string): SessionSettings;
244
+ SessionSettings.fromFile(path: string): SessionSettings;
245
+ settings.getSessions(): SessionID[];
246
+ ```
247
+
248
+ ### DataDictionary
249
+
250
+ ```ts
251
+ DataDictionary.fromString(xml: string): DataDictionary;
252
+ DataDictionary.fromFile(xmlPath: string): DataDictionary;
253
+ dictionary.validate(msg: Message): void; // throws QuickFixError if invalid
254
+ ```
255
+
256
+ ### Initiator / Acceptor (the session engine)
257
+
258
+ Both extend Node's `EventEmitter` and share the same options and lifecycle:
259
+
260
+ ```ts
261
+ new Initiator({ settings, handlers?, store?: 'file' | 'memory', log?: 'screen' | 'file' | 'none' });
262
+ new Acceptor({ settings, handlers?, store?, log? });
263
+
264
+ engine.start(): void;
265
+ engine.stop(force?: boolean): void; // graceful drain by default; force to abort
266
+ engine.isLoggedOn(): boolean;
267
+ engine.ref(): void; // keep the event loop alive while running (default)
268
+ engine.unref(): void; // let a short script / test exit
269
+ ```
270
+
271
+ ### Handlers vs. events
272
+
273
+ This is the key distinction to understand:
274
+
275
+ - **`handlers`** (passed in the constructor options) run **synchronously** on the
276
+ engine's callback path. They are the *only* place you can affect a message:
277
+ - `toAdmin` / `toApp` may **mutate** the outbound message in place (e.g. add
278
+ credentials to a Logon in `toAdmin`).
279
+ - `toApp` may **throw** `new FixReject('DoNotSend')` to suppress sending.
280
+ - `fromAdmin` / `fromApp` may **throw** a `FixReject` to reject an inbound
281
+ message (e.g. `'RejectLogon'`, `'UnsupportedMessageType'`).
282
+ - `onCreate` / `onLogon` / `onLogout` are fire-and-forget notifications.
283
+
284
+ - **Events** (`engine.on('logon', ...)`, etc.) are **observe-only**. The engine
285
+ emits `'create' | 'logon' | 'logout' | 'toAdmin' | 'fromAdmin' | 'toApp' | 'fromApp'`
286
+ *after* the corresponding handler runs. Event listeners **cannot** mutate a
287
+ message or reject it — use `handlers` for that.
288
+
289
+ ### Rejections
290
+
291
+ Throw a `FixReject` from a handler to reject:
292
+
293
+ ```ts
294
+ import { FixReject, fixReject } from '@homaiohq/napi-quickfix';
295
+
296
+ throw new FixReject('DoNotSend');
297
+ throw new FixReject('RejectLogon', 'bad credentials');
298
+ throw fixReject('UnsupportedMessageType'); // convenience factory
299
+ ```
300
+
301
+ Recognized kinds: `'DoNotSend'`, `'RejectLogon'`, `'UnsupportedMessageType'`,
302
+ `'IncorrectDataFormat'`, `'IncorrectTagValue'`, `'FieldNotFound'`.
303
+
304
+ ### Module functions and constants
305
+
306
+ ```ts
307
+ sendToTarget(message: Message, sessionID: SessionID): boolean;
308
+ version(): string; // engine / addon version string
309
+
310
+ FIELD // field-name -> tag number, e.g. FIELD.MsgType === 35, FIELD.Password === 554
311
+ MsgType // MsgType values, e.g. MsgType.Logon === 'A'
312
+ Side // Side values, e.g. Side.Buy === '1'
313
+ enums // the full frozen tree of FIX constants
314
+ ```
315
+
316
+ `FIELD` is generated from the bundled QuickFIX's `FixFieldNumbers.h`, so it has every
317
+ `FIX::FIELD::*` name the engine knows (6000+). Values are literal types: `FIELD.Password`
318
+ is typed `554`, and a misspelt field name is a compile error. `FieldName` is the union of
319
+ all field names.
320
+
321
+ The table is also exposed as a subpath export that does **not** load the native addon,
322
+ for tooling that only needs tag numbers (log parsers, test helpers, bundles for platforms
323
+ without a prebuild):
324
+
325
+ ```ts
326
+ import { FIELD } from '@homaiohq/napi-quickfix/fields';
327
+ ```
328
+
329
+ ### Errors
330
+
331
+ Errors thrown by native calls are `Error`s with `name === 'QuickFixError'`,
332
+ carrying a `fixError` discriminator (a stable string such as `'ConfigError'`,
333
+ `'FieldNotFound'`, `'InvalidMessage'`, `'IncorrectTagValue'`) plus a `detail`
334
+ string, so you can branch on the failure kind:
335
+
336
+ ```ts
337
+ try {
338
+ dictionary.validate(msg);
339
+ } catch (err) {
340
+ if (err.name === 'QuickFixError' && err.fixError === 'FieldNotFound') {
341
+ // handle a missing required field
342
+ }
343
+ }
344
+ ```
345
+
346
+ ## Building from source / Contributing
347
+
348
+ Development uses **Yarn (Berry 4)** with the `node-modules` linker, via corepack.
349
+
350
+ ```sh
351
+ corepack enable
352
+ yarn install
353
+ yarn build # cmake-js native build + dual (ESM + CJS) TypeScript build
354
+ yarn test # node --test
355
+ ```
356
+
357
+ QuickFIX is fetched at build time via CMake `FetchContent` (pinned to
358
+ `v1.16.0`) — nothing is vendored or committed. The native build therefore needs
359
+ CMake (>= 3.12) and a C++17 compiler; the QuickFIX sources are downloaded
360
+ automatically on first configure.
361
+
362
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for more detail, and
363
+ [ARCHITECTURE.md](./ARCHITECTURE.md) for a diagrammed walkthrough of how the
364
+ TypeScript API, the C++ Node-API addon, and the QuickFIX engine fit together
365
+ (layers, build pipeline, the threading/async model, and CI).
366
+
367
+ ## Compatibility
368
+
369
+ | | |
370
+ | --- | --- |
371
+ | **Node.js** | LTS **22**, **24** and **26** (current) |
372
+ | **N-API version** | 9 |
373
+ | **Platforms** | Linux (glibc + musl/Alpine), macOS, Windows |
374
+ | **Prebuilt targets** | `linux-x64` (glibc, musl), `darwin-x64`, `darwin-arm64`, `win32-x64` |
375
+ | **libc (Linux)** | glibc **and** musl — separate libc-tagged prebuilds; musl is x64-only |
376
+ | **QuickFIX** | v1.16.0 (statically linked into prebuilds) |
377
+ | **Timestamp resolution** | microseconds on Linux (glibc + musl) and macOS; **milliseconds on Windows** |
378
+
379
+ Because prebuilds are N-API-tagged (not ABI-tagged), a single binary per
380
+ platform serves all supported Node LTS versions.
381
+
382
+ On Windows, QuickFIX has no microsecond clock available (it falls back to
383
+ `ftime()`), so sub-second `SendingTime` digits beyond milliseconds are always
384
+ zero — a `TimestampPrecision=6` session emits `.NNN000`. Setting a higher
385
+ precision is still valid and interoperable; only the extra digits are padding.
386
+
387
+ ## Versioning
388
+
389
+ This package follows [Semantic Versioning](https://semver.org/) and is currently
390
+ in the **`0.x`** (pre-1.0) phase. While in `0.x`, **minor releases may include
391
+ breaking changes** — pre-1.0 has no separate major channel — so review the
392
+ [CHANGELOG](./CHANGELOG.md) before upgrading across a minor bump. Note that npm's
393
+ caret range locks the minor for `0.x`: `^0.1.0` resolves to `>=0.1.0 <0.2.0`.
394
+
395
+ The package version tracks *this binding's* API and behavior, not the bundled
396
+ QuickFIX release (statically linked, currently `v1.16.0` — see the table above).
397
+ See [VERSIONING.md](./VERSIONING.md) for the full policy.
398
+
399
+ ## License
400
+
401
+ This package (`@homaiohq/napi-quickfix`) is licensed under the **MIT License** —
402
+ see [LICENSE](./LICENSE). Copyright (c) 2026 Homaio.
403
+
404
+ ### Third-party licenses
405
+
406
+ The published prebuilt binaries **statically link** the QuickFIX C++ engine,
407
+ which is distributed under the **QuickFIX Software License, Version 1.0** (a
408
+ permissive, BSD-style license). Its full text and the required attribution are
409
+ reproduced in [THIRD_PARTY_LICENSES](./THIRD_PARTY_LICENSES).
410
+
411
+ As required by that license:
412
+
413
+ > This product includes software developed by quickfixengine.org
414
+ > (http://www.quickfixengine.org/).
415
+
416
+ ### Acknowledgements / Naming
417
+
418
+ This is an **independent, unaffiliated** Node.js binding. It is not produced,
419
+ sponsored, or endorsed by quickfixengine.org or the QuickFIX project. The name
420
+ "QuickFIX" is used here **nominatively** — solely to describe the C++ library
421
+ that this package wraps — and does not imply any affiliation or endorsement. All
422
+ QuickFIX trademarks and names remain the property of their respective owners.
@@ -0,0 +1,48 @@
1
+ THIRD-PARTY LICENSES
2
+ ====================
3
+
4
+ This file lists the licenses of third-party software that is bundled with, or
5
+ statically linked into, the distributed artifacts of @homaiohq/napi-quickfix.
6
+
7
+ The npm package ships prebuilt native binaries (`prebuilds/`) that statically
8
+ link the QuickFIX C++ FIX-protocol engine. QuickFIX itself is NOT vendored in
9
+ this source repository — it is fetched at build time from its upstream Git tag
10
+ via CMake FetchContent — but because its compiled code is embedded in the
11
+ published binaries, its license and the acknowledgement below are reproduced
12
+ here as required by that license.
13
+
14
+ --------------------------------------------------------------------------------
15
+
16
+ QuickFIX (https://github.com/quickfix/quickfix)
17
+ -----------------------------------------------
18
+
19
+ Version linked: v1.16.0
20
+ License: QuickFIX Software License, Version 1.0
21
+ License source: https://raw.githubusercontent.com/quickfix/quickfix/v1.16.0/LICENSE
22
+
23
+ This product includes software developed by quickfixengine.org
24
+ (http://www.quickfixengine.org/).
25
+
26
+ The full, verbatim text of the QuickFIX Software License, Version 1.0 follows:
27
+
28
+ --------------------------------------------------------------------------------
29
+
30
+ # QuickFIX Software License, Version 1.0
31
+
32
+ The complete text of this license is as follows:
33
+
34
+ Copyright (c) 2001-2020 Oren Miller
35
+
36
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
37
+
38
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
39
+
40
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
41
+
42
+ 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by quickfixengine.org (http://www.quickfixengine.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear.
43
+
44
+ 4. The names "QuickFIX" and "quickfixengine.org" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact ask@quickfixengine.org
45
+
46
+ 5. Products derived from this software may not be called "QuickFIX", nor may "QuickFIX" appear in their name, without prior written permission of quickfixengine.org
47
+
48
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL QUICKFIXENGINE.ORG OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.