@homaiohq/napi-quickfix 0.1.0 → 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.
- package/CMakeLists.txt +5 -1
- package/README.md +235 -18
- package/cpp/acceptor_wrap.cpp +253 -19
- package/cpp/acceptor_wrap.h +60 -1
- package/cpp/addon.cpp +10 -6
- package/cpp/application_bridge.cpp +114 -30
- package/cpp/application_bridge.h +57 -4
- package/cpp/data_dictionary_wrap.cpp +89 -3
- package/cpp/data_dictionary_wrap.h +27 -5
- package/cpp/engine_common.h +108 -0
- package/cpp/engine_workers.h +99 -18
- package/cpp/field_map_ops.h +492 -0
- package/cpp/group_wrap.cpp +221 -0
- package/cpp/group_wrap.h +76 -0
- package/cpp/initiator_wrap.cpp +262 -25
- package/cpp/initiator_wrap.h +37 -2
- package/cpp/message_wrap.cpp +171 -78
- package/cpp/message_wrap.h +47 -5
- package/cpp/session_op_gate.h +201 -0
- package/cpp/session_static.cpp +139 -12
- package/cpp/session_wrap.cpp +438 -0
- package/cpp/session_wrap.h +144 -0
- package/dist/cjs/acceptor.d.ts +0 -1
- package/dist/cjs/acceptor.js +0 -1
- package/dist/cjs/application.d.ts +0 -1
- package/dist/cjs/application.js +0 -1
- package/dist/cjs/data-dictionary.d.ts +21 -3
- package/dist/cjs/data-dictionary.js +32 -4
- package/dist/cjs/engine.d.ts +42 -4
- package/dist/cjs/engine.js +50 -5
- package/dist/cjs/enums.d.ts +48 -14
- package/dist/cjs/enums.js +20 -25
- package/dist/cjs/field-value.d.ts +12 -0
- package/dist/cjs/field-value.js +40 -0
- package/dist/cjs/generated/fields.d.ts +0 -1
- package/dist/cjs/generated/fields.js +0 -1
- package/dist/cjs/generated/values.d.ts +8548 -0
- package/dist/cjs/generated/values.js +8580 -0
- package/dist/cjs/group.d.ts +126 -0
- package/dist/cjs/group.js +185 -0
- package/dist/cjs/index.d.ts +26 -5
- package/dist/cjs/index.js +28 -28
- package/dist/cjs/initiator.d.ts +0 -1
- package/dist/cjs/initiator.js +0 -1
- package/dist/cjs/message.d.ts +129 -5
- package/dist/cjs/message.js +146 -9
- package/dist/cjs/native.d.ts +179 -17
- package/dist/cjs/native.js +0 -1
- package/dist/cjs/session-id.d.ts +12 -1
- package/dist/cjs/session-id.js +21 -1
- package/dist/cjs/session-settings.d.ts +0 -1
- package/dist/cjs/session-settings.js +0 -1
- package/dist/cjs/session.d.ts +213 -0
- package/dist/cjs/session.js +337 -0
- package/dist/esm/acceptor.d.ts +0 -1
- package/dist/esm/acceptor.js +0 -1
- package/dist/esm/application.d.ts +0 -1
- package/dist/esm/application.js +0 -1
- package/dist/esm/data-dictionary.d.ts +21 -3
- package/dist/esm/data-dictionary.js +32 -4
- package/dist/esm/engine.d.ts +42 -4
- package/dist/esm/engine.js +51 -6
- package/dist/esm/enums.d.ts +48 -14
- package/dist/esm/enums.js +14 -24
- package/dist/esm/field-value.d.ts +12 -0
- package/dist/esm/field-value.js +37 -0
- package/dist/esm/generated/fields.d.ts +0 -1
- package/dist/esm/generated/fields.js +0 -1
- package/dist/esm/generated/values.d.ts +8548 -0
- package/dist/esm/generated/values.js +8564 -0
- package/dist/esm/group.d.ts +126 -0
- package/dist/esm/group.js +181 -0
- package/dist/esm/index.d.ts +26 -5
- package/dist/esm/index.js +17 -26
- package/dist/esm/initiator.d.ts +0 -1
- package/dist/esm/initiator.js +0 -1
- package/dist/esm/message.d.ts +129 -5
- package/dist/esm/message.js +146 -9
- package/dist/esm/native.d.ts +179 -17
- package/dist/esm/native.js +0 -1
- package/dist/esm/session-id.d.ts +12 -1
- package/dist/esm/session-id.js +20 -1
- package/dist/esm/session-settings.d.ts +0 -1
- package/dist/esm/session-settings.js +0 -1
- package/dist/esm/session.d.ts +213 -0
- package/dist/esm/session.js +329 -0
- package/package.json +13 -1
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/cpp/enums.cpp +0 -66
- package/dist/cjs/acceptor.d.ts.map +0 -1
- package/dist/cjs/acceptor.js.map +0 -1
- package/dist/cjs/application.d.ts.map +0 -1
- package/dist/cjs/application.js.map +0 -1
- package/dist/cjs/data-dictionary.d.ts.map +0 -1
- package/dist/cjs/data-dictionary.js.map +0 -1
- package/dist/cjs/engine.d.ts.map +0 -1
- package/dist/cjs/engine.js.map +0 -1
- package/dist/cjs/enums.d.ts.map +0 -1
- package/dist/cjs/enums.js.map +0 -1
- package/dist/cjs/generated/fields.d.ts.map +0 -1
- package/dist/cjs/generated/fields.js.map +0 -1
- package/dist/cjs/index.d.ts.map +0 -1
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/initiator.d.ts.map +0 -1
- package/dist/cjs/initiator.js.map +0 -1
- package/dist/cjs/message.d.ts.map +0 -1
- package/dist/cjs/message.js.map +0 -1
- package/dist/cjs/native.d.ts.map +0 -1
- package/dist/cjs/native.js.map +0 -1
- package/dist/cjs/session-id.d.ts.map +0 -1
- package/dist/cjs/session-id.js.map +0 -1
- package/dist/cjs/session-settings.d.ts.map +0 -1
- package/dist/cjs/session-settings.js.map +0 -1
- package/dist/esm/acceptor.d.ts.map +0 -1
- package/dist/esm/acceptor.js.map +0 -1
- package/dist/esm/application.d.ts.map +0 -1
- package/dist/esm/application.js.map +0 -1
- package/dist/esm/data-dictionary.d.ts.map +0 -1
- package/dist/esm/data-dictionary.js.map +0 -1
- package/dist/esm/engine.d.ts.map +0 -1
- package/dist/esm/engine.js.map +0 -1
- package/dist/esm/enums.d.ts.map +0 -1
- package/dist/esm/enums.js.map +0 -1
- package/dist/esm/generated/fields.d.ts.map +0 -1
- package/dist/esm/generated/fields.js.map +0 -1
- package/dist/esm/index.d.ts.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/initiator.d.ts.map +0 -1
- package/dist/esm/initiator.js.map +0 -1
- package/dist/esm/message.d.ts.map +0 -1
- package/dist/esm/message.js.map +0 -1
- package/dist/esm/native.d.ts.map +0 -1
- package/dist/esm/native.js.map +0 -1
- package/dist/esm/session-id.d.ts.map +0 -1
- package/dist/esm/session-id.js.map +0 -1
- package/dist/esm/session-settings.d.ts.map +0 -1
- package/dist/esm/session-settings.js.map +0 -1
package/CMakeLists.txt
CHANGED
|
@@ -85,7 +85,11 @@ string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
|
|
|
85
85
|
# ---------------------------------------------------------------------------
|
|
86
86
|
# The native addon.
|
|
87
87
|
# ---------------------------------------------------------------------------
|
|
88
|
-
|
|
88
|
+
# CONFIGURE_DEPENDS re-globs on every build, so adding or deleting a cpp/*.cpp
|
|
89
|
+
# file is picked up by an already-configured tree. cmake-js only re-runs the
|
|
90
|
+
# configure step when CMakeCache.txt is missing, so without it a deleted source
|
|
91
|
+
# stays in the build graph until `yarn clean`.
|
|
92
|
+
file(GLOB ADDON_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/cpp/*.cpp")
|
|
89
93
|
|
|
90
94
|
add_library(napi_quickfix SHARED ${ADDON_SOURCES} ${CMAKE_JS_SRC})
|
|
91
95
|
set_target_properties(napi_quickfix PROPERTIES PREFIX "" SUFFIX ".node")
|
package/README.md
CHANGED
|
@@ -23,11 +23,17 @@ included.
|
|
|
23
23
|
points.
|
|
24
24
|
- **TypeScript types included** — hand-written `.d.ts` for the full public API.
|
|
25
25
|
- **Full FIX surface** — the session engine (`Initiator`/`Acceptor` +
|
|
26
|
-
synchronous application handlers)
|
|
27
|
-
|
|
26
|
+
synchronous application handlers), per-session control (`Session`: sequence
|
|
27
|
+
numbers, logon/logout, runtime options) *and* the pure layer (`Message` with
|
|
28
|
+
repeating `Group`s and dictionary-aware parsing, `SessionSettings`,
|
|
29
|
+
`DataDictionary`, `SessionID`).
|
|
28
30
|
- **Ergonomic, safe API** — chainable message building with automatic
|
|
29
31
|
header/trailer field routing, typed rejections, and C++ exceptions surfaced as
|
|
30
32
|
JavaScript `Error`s.
|
|
33
|
+
- **Every FIX constant, typed** — `FIELD` (tag numbers) and one value group per
|
|
34
|
+
field (`MsgType`, `Side`, `ExecType`, `OrdStatus`, ...) are
|
|
35
|
+
generated from the bundled QuickFIX headers with literal types, frozen, and
|
|
36
|
+
importable without loading the native addon.
|
|
31
37
|
|
|
32
38
|
## Install
|
|
33
39
|
|
|
@@ -206,20 +212,41 @@ come from `@homaiohq/napi-quickfix`.
|
|
|
206
212
|
### Message
|
|
207
213
|
|
|
208
214
|
```ts
|
|
209
|
-
new Message(raw?: string, opts?:
|
|
210
|
-
Message.parse(raw: string, opts?:
|
|
215
|
+
new Message(raw?: string, opts?: MessageParseOptions);
|
|
216
|
+
Message.parse(raw: string, opts?: MessageParseOptions): Message;
|
|
217
|
+
// MessageParseOptions: { validate?: boolean; dictionary?: DataDictionary;
|
|
218
|
+
// sessionDictionary?: DataDictionary; applicationDictionary?: DataDictionary }
|
|
211
219
|
|
|
212
220
|
message.setField(tag: number, value: string | number): this; // chainable, auto-routes header/trailer fields
|
|
213
|
-
message.getField(tag: number): string;
|
|
221
|
+
message.getField(tag: number): string; // throws QuickFixError (FieldNotFound) if absent
|
|
222
|
+
// Standard header/trailer tags route to their section. Any other tag is a body field, unless the
|
|
223
|
+
// header or trailer already holds it (a field a dictionary declares there): then it is read and
|
|
224
|
+
// edited in place. A new dictionary-declared header field goes in with setHeaderField.
|
|
225
|
+
message.getFieldIfSet(tag: number): string | undefined;
|
|
226
|
+
message.isSetField(tag: number): boolean;
|
|
227
|
+
message.removeField(tag: number): this;
|
|
214
228
|
message.setHeaderField(tag, value): this; message.getHeaderField(tag): string;
|
|
215
229
|
message.setTrailerField(tag, value): this; message.getTrailerField(tag): string;
|
|
230
|
+
message.fields(): [tag: number, value: string][]; // body fields in wire order (also iterable)
|
|
231
|
+
message.headerFields(); message.trailerFields(); // same shape
|
|
232
|
+
message.isEmpty(): boolean; message.totalFields(): number; message.clear(): this;
|
|
233
|
+
|
|
234
|
+
// Tags must be integers (positive for setters) and group indices integers: anything else throws a TypeError.
|
|
235
|
+
// Repeating groups (indices are 1-based, as in QuickFIX)
|
|
236
|
+
message.addGroup(group: Group): this; // appends a copy, maintains the count field; TypeError if group.delim differs from the stored entries'
|
|
237
|
+
message.getGroup(index: number, tag: number): Group; // snapshot copy; throws FieldNotFound if absent
|
|
238
|
+
message.replaceGroup(index: number, group: Group): this; // throws FieldNotFound if absent, TypeError if group.delim differs
|
|
239
|
+
message.removeGroup(tag: number): this; message.removeGroup(index: number, tag: number): this; // (index, tag) throws FieldNotFound if absent
|
|
240
|
+
message.hasGroup(tag: number): boolean; message.hasGroup(index: number, tag: number): boolean;
|
|
241
|
+
message.groupCount(tag: number): number;
|
|
242
|
+
|
|
216
243
|
message.getMsgType(): string;
|
|
217
244
|
message.toString(): string; // raw SOH-delimited wire string
|
|
218
245
|
message.toPretty(): string; // human-readable
|
|
219
246
|
message.toJSON(): { msgType?: string; raw: string };
|
|
220
247
|
|
|
221
248
|
createMessage(fields?: Record<number, string | number>): Message;
|
|
222
|
-
parseMessage(raw: string, opts?:
|
|
249
|
+
parseMessage(raw: string, opts?: MessageParseOptions): Message;
|
|
223
250
|
```
|
|
224
251
|
|
|
225
252
|
Field values are accepted as `string | number` (numbers are stringified) and
|
|
@@ -228,6 +255,62 @@ always returned as `string`. Header/trailer fields such as `MsgType`,
|
|
|
228
255
|
section by `setField`, so you rarely need `setHeaderField`/`setTrailerField`
|
|
229
256
|
directly.
|
|
230
257
|
|
|
258
|
+
**Parsing and repeating groups.** A parse without a dictionary is *flat*: the
|
|
259
|
+
fields of a repeating group are kept as plain repeated body fields and
|
|
260
|
+
`getGroup` throws. Pass a `dictionary` (or a `sessionDictionary` /
|
|
261
|
+
`applicationDictionary` pair for FIXT/FIX 5) to parse groups structurally.
|
|
262
|
+
With `UseDataDictionary=Y` (the default), messages delivered to engine handlers
|
|
263
|
+
are already parsed with the session's dictionary, so `getGroup` works on them
|
|
264
|
+
directly; with `UseDataDictionary=N` they arrive flat.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { DataDictionary, Group, Message, FIELD, MsgType } from '@homaiohq/napi-quickfix';
|
|
268
|
+
|
|
269
|
+
const dd = DataDictionary.fromFile('./spec/FIX44.xml');
|
|
270
|
+
|
|
271
|
+
const order = new Message()
|
|
272
|
+
.setField(FIELD.MsgType, MsgType.NewOrderSingle)
|
|
273
|
+
.setField(FIELD.ClOrdID, 'order-1')
|
|
274
|
+
.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'TRADER-1').setField(FIELD.PartyRole, 11))
|
|
275
|
+
.addGroup(new Group(FIELD.NoPartyIDs, FIELD.PartyID).setField(FIELD.PartyID, 'FIRM-7').setField(FIELD.PartyRole, 1));
|
|
276
|
+
// 453=2|448=TRADER-1|452=11|448=FIRM-7|452=1 on the wire
|
|
277
|
+
|
|
278
|
+
const parsed = Message.parse(order.toString(), { dictionary: dd });
|
|
279
|
+
parsed.groupCount(FIELD.NoPartyIDs); // 2
|
|
280
|
+
parsed.getGroup(2, FIELD.NoPartyIDs).getField(FIELD.PartyID); // 'FIRM-7'
|
|
281
|
+
|
|
282
|
+
// getGroup returns a snapshot: write changes back with replaceGroup.
|
|
283
|
+
const first = parsed.getGroup(1, FIELD.NoPartyIDs).setField(FIELD.PartyRole, 12);
|
|
284
|
+
parsed.replaceGroup(1, first);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Group
|
|
288
|
+
|
|
289
|
+
One entry of a repeating group, identified by its **count tag** (`field`, e.g.
|
|
290
|
+
`453 NoPartyIDs`) and its **delimiter** (`delim`, the first tag of every entry,
|
|
291
|
+
e.g. `448 PartyID`). Groups nest through the same `addGroup`/`getGroup` API.
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
new Group(field: number, delim: number, order?: number[]); // order: full entry field order, delimiter first, no tag twice
|
|
295
|
+
// delim and order tags are capped at 2^20 (RangeError beyond that)
|
|
296
|
+
group.field; group.delim; // read-only
|
|
297
|
+
|
|
298
|
+
group.setField(tag, value: string | number): this; group.getField(tag): string;
|
|
299
|
+
group.getFieldIfSet(tag): string | undefined; group.isSetField(tag): boolean;
|
|
300
|
+
group.removeField(tag): this; group.isEmpty(); group.totalFields(); group.clear(): this;
|
|
301
|
+
group.fields(): [tag, value][]; // in wire order (also iterable)
|
|
302
|
+
group.toString(): string; // the entry's fields, SOH-delimited, in wire order
|
|
303
|
+
|
|
304
|
+
// Nested groups — same contract as on Message
|
|
305
|
+
group.addGroup(g: Group): this; group.getGroup(index, tag): Group; group.replaceGroup(index, g): this;
|
|
306
|
+
group.removeGroup(tag): this; group.removeGroup(index, tag): this;
|
|
307
|
+
group.hasGroup(tag): boolean; group.hasGroup(index, tag): boolean; group.groupCount(tag): number;
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Without an explicit `order`, an entry's fields are emitted delimiter-first and
|
|
311
|
+
then by ascending tag number, which matches the standard dictionaries for most
|
|
312
|
+
groups; pass `order` (e.g. `[448, 447, 452]`) when a group's spec order differs.
|
|
313
|
+
|
|
231
314
|
### SessionID
|
|
232
315
|
|
|
233
316
|
```ts
|
|
@@ -250,9 +333,18 @@ settings.getSessions(): SessionID[];
|
|
|
250
333
|
```ts
|
|
251
334
|
DataDictionary.fromString(xml: string): DataDictionary;
|
|
252
335
|
DataDictionary.fromFile(xmlPath: string): DataDictionary;
|
|
253
|
-
dictionary.validate(msg: Message): void; // throws QuickFixError if invalid
|
|
336
|
+
dictionary.validate(msg: Message, bodyOnly?: boolean): void; // throws QuickFixError if invalid
|
|
337
|
+
dictionary.getVersion(): string; // e.g. 'FIX.4.4'
|
|
338
|
+
dictionary.getFieldName(tag: number): string | undefined;
|
|
339
|
+
dictionary.getFieldTag(name: string): number | undefined;
|
|
340
|
+
dictionary.isField(tag: number): boolean;
|
|
341
|
+
dictionary.isMsgType(msgType: string): boolean;
|
|
254
342
|
```
|
|
255
343
|
|
|
344
|
+
`validate(msg, true)` validates as the *application* dictionary only: the
|
|
345
|
+
BeginString version check and header/trailer validation are skipped. A
|
|
346
|
+
dictionary also drives structural parsing — see `Message.parse` above.
|
|
347
|
+
|
|
256
348
|
### Initiator / Acceptor (the session engine)
|
|
257
349
|
|
|
258
350
|
Both extend Node's `EventEmitter` and share the same options and lifecycle:
|
|
@@ -261,13 +353,110 @@ Both extend Node's `EventEmitter` and share the same options and lifecycle:
|
|
|
261
353
|
new Initiator({ settings, handlers?, store?: 'file' | 'memory', log?: 'screen' | 'file' | 'none' });
|
|
262
354
|
new Acceptor({ settings, handlers?, store?, log? });
|
|
263
355
|
|
|
264
|
-
engine.start(): void
|
|
265
|
-
engine.stop(force?: boolean): void
|
|
266
|
-
engine.isLoggedOn(): boolean;
|
|
356
|
+
engine.start(): Promise<void>;
|
|
357
|
+
engine.stop(force?: boolean): Promise<void>; // graceful drain by default; force to abort
|
|
358
|
+
engine.isLoggedOn(sessionID?: SessionID): boolean; // any session, or just that one
|
|
359
|
+
engine.getSessions(): SessionID[]; // the [SESSION]s it was configured with
|
|
360
|
+
engine.getSession(sessionID: SessionID): Session | undefined;
|
|
267
361
|
engine.ref(): void; // keep the event loop alive while running (default)
|
|
268
362
|
engine.unref(): void; // let a short script / test exit
|
|
269
363
|
```
|
|
270
364
|
|
|
365
|
+
`start`/`stop` resolve once the engine has finished starting up / shutting down;
|
|
366
|
+
they run off the main thread so your handlers can fire meanwhile. Once `stop()`
|
|
367
|
+
settles the engine's sessions are destroyed: `getSession` returns `undefined` and
|
|
368
|
+
any `Session` handle you still hold throws `SessionNotFound`. Until then they are
|
|
369
|
+
still live: a graceful `stop()` settles only after the `'logout'` events it
|
|
370
|
+
triggered have been delivered, so a `'logout'` listener can still read the
|
|
371
|
+
session, and a `sendToTarget()` that has already reached its session completes
|
|
372
|
+
before the sessions go away. One that has not reached it by then is refused
|
|
373
|
+
with `SessionNotFound` rather than run against the engine that is gone; a send
|
|
374
|
+
is bound to the engine that owns the session when it is called, never to one
|
|
375
|
+
created later with the same id.
|
|
376
|
+
`stop(true)` discards the events still queued and destroys the sessions right
|
|
377
|
+
away. A `stop()` called while another is still in flight (from that `'logout'`
|
|
378
|
+
listener, say) returns the same Promise, so it too settles only once the
|
|
379
|
+
sessions are gone; the first call's `force` applies to both.
|
|
380
|
+
|
|
381
|
+
Two live engines cannot share a `SessionID`: constructing one whose session
|
|
382
|
+
already exists in the process throws a `ConfigError` ("Duplicate Session") until
|
|
383
|
+
the engine owning it has been stopped. A constructor that fails on a later
|
|
384
|
+
`[SESSION]` (any `ConfigError`) cleans up the sessions it had already created
|
|
385
|
+
(so the ids are not locked out by the failed attempt) and the engine-level log
|
|
386
|
+
QuickFIX had already opened for it.
|
|
387
|
+
|
|
388
|
+
### Session
|
|
389
|
+
|
|
390
|
+
A handle on one **live** session, obtained from `engine.getSession(id)` or the
|
|
391
|
+
module-level `lookupSession(id)`. It stores only the `SessionID` and the identity
|
|
392
|
+
of the engine it came from, and re-resolves the engine-owned `FIX::Session` on
|
|
393
|
+
every call, so it never dangles — once the session is gone (its engine was
|
|
394
|
+
stopped) every method throws a `QuickFixError` whose `fixErrorName` is
|
|
395
|
+
`'SessionNotFound'`, even after a new engine has been created with the same id.
|
|
396
|
+
A handle never crosses over to another engine's session; get a new one from the
|
|
397
|
+
new engine.
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
session.sessionID: SessionID;
|
|
401
|
+
|
|
402
|
+
// State (sync)
|
|
403
|
+
session.isLoggedOn(): boolean; session.isEnabled(): boolean;
|
|
404
|
+
session.sentLogon(): boolean; session.sentLogout(): boolean; session.receivedLogon(): boolean;
|
|
405
|
+
session.isInitiator(): boolean; session.isAcceptor(): boolean;
|
|
406
|
+
session.isSessionTime(now?: Date): boolean; session.isLogonTime(now?: Date): boolean;
|
|
407
|
+
|
|
408
|
+
// Sequence numbers (sync; setters write to the message store -- on a quiet session, see below)
|
|
409
|
+
session.getExpectedSenderNum(): number; session.getExpectedTargetNum(): number;
|
|
410
|
+
session.setNextSenderMsgSeqNum(n: number): void; session.setNextTargetMsgSeqNum(n: number): void;
|
|
411
|
+
|
|
412
|
+
// Runtime options (sync; change the live session, not its SessionSettings)
|
|
413
|
+
session.getResetOnLogon() / setResetOnLogon(v: boolean) // likewise: ResetOnLogout,
|
|
414
|
+
session.getLogonTimeout() / setLogonTimeout(seconds: number) // ResetOnDisconnect, RefreshOnLogon,
|
|
415
|
+
session.getTimestampPrecision() / setTimestampPrecision(0..9) // CheckCompId, CheckLatency, MaxLatency,
|
|
416
|
+
// LogoutTimeout, PersistMessages,
|
|
417
|
+
// SendRedundantResendRequests,
|
|
418
|
+
// ValidateLengthAndChecksum,
|
|
419
|
+
// SendNextExpectedMsgSeqNum, IsNonStopSession
|
|
420
|
+
|
|
421
|
+
// Control
|
|
422
|
+
session.logon(): void; // enable; an initiator reconnects + logs on
|
|
423
|
+
session.logout(reason?: string): void; // disable + graceful Logout; stays down until logon()
|
|
424
|
+
session.refresh(): void; // re-read state from the message store
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Everything is synchronous: none of these members takes the mutex QuickFIX holds
|
|
428
|
+
while it runs your handlers, so they cannot deadlock the main thread — and
|
|
429
|
+
because they run inline, `session.logout('x'); session.logon();` takes effect in
|
|
430
|
+
that order. (`logon()`/`logout()` flip a plain flag the engine's network thread
|
|
431
|
+
reads without a lock, exactly as QuickFIX's own `Initiator::stop()` does from
|
|
432
|
+
the stopping thread; the engine picks the change up on its next pass.) The
|
|
433
|
+
sequence-number setters are likewise not serialised against a message the
|
|
434
|
+
engine is sending or receiving on that session at the same moment: QuickFIX
|
|
435
|
+
reads the number, runs your handler, then increments from the store under a
|
|
436
|
+
lock the setters do not take, so a value set in that window — from a handler,
|
|
437
|
+
or while a `sendToTarget()` or a heartbeat is in flight — is overwritten and the
|
|
438
|
+
session ends up one off from its counterparty. Renumber a **quiet** session:
|
|
439
|
+
before `start()`, from a `'logout'` event, or when you know nothing is in
|
|
440
|
+
flight. QuickFIX's `Session::disconnect()` and `reset()` are deliberately **not**
|
|
441
|
+
exposed: they drop the transport through the engine's socket monitor, which is
|
|
442
|
+
only safe from the engine's own network thread (QuickFIX itself never calls them
|
|
443
|
+
from anywhere else). Bring a session down with `logout()` and renumber it with
|
|
444
|
+
the sequence-number setters. Observe the outcome through the engine's
|
|
445
|
+
`'logon'` / `'logout'` events:
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
initiator.on('logon', (id) => {
|
|
449
|
+
console.log('next outbound seq', initiator.getSession(id)!.getExpectedSenderNum());
|
|
450
|
+
});
|
|
451
|
+
initiator.on('logout', (id) => {
|
|
452
|
+
// The session is quiet now: renumber it, e.g. after the counterparty reset.
|
|
453
|
+
initiator.getSession(id)?.setNextTargetMsgSeqNum(1);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
lookupSession(id)?.logout('maintenance'); // -> 'logout' event, isLoggedOn() === false
|
|
457
|
+
lookupSession(id)?.logon(); // -> reconnects, 'logon' event
|
|
458
|
+
```
|
|
459
|
+
|
|
271
460
|
### Handlers vs. events
|
|
272
461
|
|
|
273
462
|
This is the key distinction to understand:
|
|
@@ -304,13 +493,23 @@ Recognized kinds: `'DoNotSend'`, `'RejectLogon'`, `'UnsupportedMessageType'`,
|
|
|
304
493
|
### Module functions and constants
|
|
305
494
|
|
|
306
495
|
```ts
|
|
307
|
-
sendToTarget(message: Message, sessionID: SessionID): boolean
|
|
496
|
+
sendToTarget(message: Message, sessionID: SessionID): Promise<boolean>;
|
|
497
|
+
sendToTarget(message: Message, qualifier?: string): Promise<boolean>; // session from the message header (8/49/56)
|
|
498
|
+
|
|
499
|
+
lookupSession(sessionID: SessionID): Session | undefined; // any engine in this process
|
|
500
|
+
doesSessionExist(sessionID: SessionID): boolean;
|
|
501
|
+
getSessions(): SessionID[]; // every session in this process, across all engines
|
|
502
|
+
numSessions(): number;
|
|
503
|
+
|
|
308
504
|
version(): string; // engine / addon version string
|
|
309
505
|
|
|
310
|
-
FIELD
|
|
311
|
-
MsgType
|
|
312
|
-
Side
|
|
313
|
-
|
|
506
|
+
FIELD // field-name -> tag number, e.g. FIELD.MsgType === 35, FIELD.Password === 554
|
|
507
|
+
MsgType // MsgType values, e.g. MsgType.Logon === 'A'
|
|
508
|
+
Side // Side values, e.g. Side.Buy === '1'
|
|
509
|
+
OrdType // e.g. OrdType.Limit === '2'
|
|
510
|
+
TimeInForce // e.g. TimeInForce.Day === '0'
|
|
511
|
+
VALUES // every value group keyed by field name, e.g. VALUES.ExecType.Fill === '2'
|
|
512
|
+
enums // the full frozen tree: every value group plus FIELD
|
|
314
513
|
```
|
|
315
514
|
|
|
316
515
|
`FIELD` is generated from the bundled QuickFIX's `FixFieldNumbers.h`, so it has every
|
|
@@ -318,12 +517,30 @@ enums // the full frozen tree of FIX constants
|
|
|
318
517
|
is typed `554`, and a misspelt field name is a compile error. `FieldName` is the union of
|
|
319
518
|
all field names.
|
|
320
519
|
|
|
321
|
-
The
|
|
322
|
-
|
|
323
|
-
|
|
520
|
+
The value groups are generated the same way from `FixValues.h`: one frozen object per
|
|
521
|
+
field (690 groups, 5700+ values), each value a string because FIX is string-on-the-wire
|
|
522
|
+
(`EncryptMethod.None === '0'`, not `0`). Names derive from QuickFIX's
|
|
523
|
+
`FIX::<Field>_<VALUE>` constants with the SCREAMING_SNAKE suffix turned into PascalCase —
|
|
524
|
+
`Side_SELL_SHORT` → `Side.SellShort`, `ExecType_DONE_FOR_DAY` → `ExecType.DoneForDay` —
|
|
525
|
+
while `MsgType`, whose upstream names already mirror message names, is kept verbatim
|
|
526
|
+
(`MsgType.NewOrderSingle`, `MsgType.IOI`). Every word is lower-cased, acronyms included
|
|
527
|
+
(`SecurityIDSource_ISIN_NUMBER` → `SecurityIDSource.IsinNumber`, `MDEntryType_VWAP` →
|
|
528
|
+
`MDEntryType.Vwap`): the upstream names carry no acronym information, so there is no
|
|
529
|
+
allowlist to maintain and the keys are stable. The exact rule is documented in
|
|
530
|
+
`scripts/gen-values.mjs`. Values are literal types too (`Side.Buy` is typed `'1'`), and
|
|
531
|
+
`ValueGroupName` is the union of all group names.
|
|
532
|
+
|
|
533
|
+
The root entry exports `MsgType`, `Side`, `OrdType` and `TimeInForce` by name and every
|
|
534
|
+
group through `VALUES` / `enums`. Both tables are also exposed as subpath exports that
|
|
535
|
+
do **not** load the native addon, for tooling that only needs the constants (log
|
|
536
|
+
parsers, test helpers, bundles for platforms without a prebuild); each group is a named
|
|
537
|
+
export there, so bundlers resolving the ESM build keep only the groups you import (the
|
|
538
|
+
CJS build is not tree-shakeable: tsc drops the `@__PURE__` annotations when it emits
|
|
539
|
+
CommonJS):
|
|
324
540
|
|
|
325
541
|
```ts
|
|
326
542
|
import { FIELD } from '@homaiohq/napi-quickfix/fields';
|
|
543
|
+
import { ExecType, OrdStatus, SecurityType } from '@homaiohq/napi-quickfix/values';
|
|
327
544
|
```
|
|
328
545
|
|
|
329
546
|
### Errors
|