@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,115 @@
1
+ /**
2
+ * Application-callback types for the session engine.
3
+ *
4
+ * Handlers are invoked **synchronously** by QuickFIX on its own threads via the
5
+ * native bridge. Two callbacks may mutate their message argument in place
6
+ * (`toAdmin`/`toApp`), and several may reject a message by **throwing** a typed
7
+ * rejection (`toApp`/`fromApp`/`fromAdmin`). Throwing anything else surfaces as
8
+ * a generic rejection.
9
+ *
10
+ * The {@link Initiator}/{@link Acceptor} also emit observe-only EventEmitter
11
+ * events mirroring these callbacks — but events cannot mutate or reject; that
12
+ * MUST go through the handlers.
13
+ */
14
+ import type { Message } from './message.js';
15
+ import type { SessionID } from './session-id.js';
16
+ /**
17
+ * User-supplied FIX application callbacks.
18
+ *
19
+ * All are optional. Runtime semantics:
20
+ * - `onCreate`/`onLogon`/`onLogout` — fire-and-forget notifications.
21
+ * - `toAdmin`/`toApp` — invoked before an outbound message is sent; may **mutate**
22
+ * the message in place (e.g. add credentials to a Logon in `toAdmin`).
23
+ * - `toApp` — additionally may **throw** {@link FixReject} with kind `'DoNotSend'`
24
+ * to suppress sending.
25
+ * - `fromAdmin`/`fromApp` — invoked for inbound messages; may **throw**
26
+ * {@link FixReject} to reject (e.g. `'RejectLogon'`, `'UnsupportedMessageType'`).
27
+ */
28
+ export interface ApplicationHandlers {
29
+ /** A new session was created. */
30
+ onCreate?(sessionID: SessionID): void;
31
+ /** A session successfully logged on. */
32
+ onLogon?(sessionID: SessionID): void;
33
+ /** A session logged out (or was disconnected). */
34
+ onLogout?(sessionID: SessionID): void;
35
+ /**
36
+ * About to send an admin (session-level) message. May mutate `message`.
37
+ * @param message The outbound message (mutable).
38
+ */
39
+ toAdmin?(message: Message, sessionID: SessionID): void;
40
+ /**
41
+ * About to send an application message. May mutate `message`, or throw a
42
+ * {@link FixReject} with kind `'DoNotSend'` to suppress it.
43
+ */
44
+ toApp?(message: Message, sessionID: SessionID): void;
45
+ /**
46
+ * Received an admin (session-level) message. May throw a {@link FixReject}
47
+ * (e.g. `'RejectLogon'`) to reject it.
48
+ */
49
+ fromAdmin?(message: Message, sessionID: SessionID): void;
50
+ /**
51
+ * Received an application message. May throw a {@link FixReject} (e.g.
52
+ * `'UnsupportedMessageType'`, `'DoNotSend'`) to reject it.
53
+ */
54
+ fromApp?(message: Message, sessionID: SessionID): void;
55
+ }
56
+ /**
57
+ * The kinds of rejection the native bridge understands. Throwing a
58
+ * {@link FixReject} with one of these maps to the matching QuickFIX behaviour:
59
+ * - `'DoNotSend'` → `FIX::DoNotSend` (from `toApp`)
60
+ * - `'RejectLogon'` → `FIX::RejectLogon` (from `fromAdmin`)
61
+ * - `'UnsupportedMessageType'` → `FIX::UnsupportedMessageType` (from `fromApp`)
62
+ * - `'IncorrectDataFormat'` → `FIX::IncorrectDataFormat`
63
+ * - `'IncorrectTagValue'` → `FIX::IncorrectTagValue`
64
+ * - `'FieldNotFound'` → `FIX::FieldNotFound`
65
+ */
66
+ export type FixRejectKind = 'DoNotSend' | 'RejectLogon' | 'UnsupportedMessageType' | 'IncorrectDataFormat' | 'IncorrectTagValue' | 'FieldNotFound';
67
+ /**
68
+ * A typed rejection error to throw from an {@link ApplicationHandlers} callback.
69
+ *
70
+ * The native bridge inspects a thrown error's `fixError` property (a
71
+ * {@link FixRejectKind}) to decide which QuickFIX exception to raise on the
72
+ * engine thread. Throwing any error without a recognized `fixError` results in a
73
+ * generic rejection.
74
+ *
75
+ * For symmetry with errors flowing the OTHER way (native → JS
76
+ * {@link import('./native.js').QuickFixError}), a `FixReject` also carries a
77
+ * `fixErrorName` set to the same stable {@link FixRejectKind} discriminator.
78
+ * Here `fixError` and `fixErrorName` are identical (the kinds are already
79
+ * space-free); the pair exists so both directions expose the same shape.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * handlers: {
84
+ * fromApp(msg) {
85
+ * if (msg.getMsgType() === 'X') throw new FixReject('UnsupportedMessageType');
86
+ * },
87
+ * }
88
+ * ```
89
+ */
90
+ export declare class FixReject extends Error {
91
+ /**
92
+ * Discriminator read by the native bridge to map to a FIX exception. The
93
+ * bridge maps rejections by THIS property.
94
+ */
95
+ readonly fixError: FixRejectKind;
96
+ /**
97
+ * Stable, space-free discriminator (equal to {@link FixReject.fixError}).
98
+ * Mirrors {@link import('./native.js').QuickFixError.fixErrorName} so both
99
+ * error directions share one shape.
100
+ */
101
+ readonly fixErrorName: FixRejectKind;
102
+ /** Optional extra detail forwarded to the engine where supported. */
103
+ readonly detail?: string;
104
+ constructor(kind: FixRejectKind, message?: string, detail?: string);
105
+ }
106
+ /**
107
+ * Convenience factory for a {@link FixReject}. Equivalent to
108
+ * `new FixReject(kind, message, detail)`.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * throw fixReject('DoNotSend');
113
+ * ```
114
+ */
115
+ export declare function fixReject(kind: FixRejectKind, message?: string, detail?: string): FixReject;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A typed rejection error to throw from an {@link ApplicationHandlers} callback.
3
+ *
4
+ * The native bridge inspects a thrown error's `fixError` property (a
5
+ * {@link FixRejectKind}) to decide which QuickFIX exception to raise on the
6
+ * engine thread. Throwing any error without a recognized `fixError` results in a
7
+ * generic rejection.
8
+ *
9
+ * For symmetry with errors flowing the OTHER way (native → JS
10
+ * {@link import('./native.js').QuickFixError}), a `FixReject` also carries a
11
+ * `fixErrorName` set to the same stable {@link FixRejectKind} discriminator.
12
+ * Here `fixError` and `fixErrorName` are identical (the kinds are already
13
+ * space-free); the pair exists so both directions expose the same shape.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * handlers: {
18
+ * fromApp(msg) {
19
+ * if (msg.getMsgType() === 'X') throw new FixReject('UnsupportedMessageType');
20
+ * },
21
+ * }
22
+ * ```
23
+ */
24
+ export class FixReject extends Error {
25
+ /**
26
+ * Discriminator read by the native bridge to map to a FIX exception. The
27
+ * bridge maps rejections by THIS property.
28
+ */
29
+ fixError;
30
+ /**
31
+ * Stable, space-free discriminator (equal to {@link FixReject.fixError}).
32
+ * Mirrors {@link import('./native.js').QuickFixError.fixErrorName} so both
33
+ * error directions share one shape.
34
+ */
35
+ fixErrorName;
36
+ /** Optional extra detail forwarded to the engine where supported. */
37
+ detail;
38
+ constructor(kind, message, detail) {
39
+ super(message ?? kind);
40
+ this.name = 'FixReject';
41
+ this.fixError = kind;
42
+ this.fixErrorName = kind;
43
+ this.detail = detail;
44
+ }
45
+ }
46
+ /**
47
+ * Convenience factory for a {@link FixReject}. Equivalent to
48
+ * `new FixReject(kind, message, detail)`.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * throw fixReject('DoNotSend');
53
+ * ```
54
+ */
55
+ export function fixReject(kind, message, detail) {
56
+ return new FixReject(kind, message, detail);
57
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Ergonomic wrapper around the native `FIX::DataDictionary`.
3
+ */
4
+ import { type NativeDataDictionary } from './native.js';
5
+ import type { Message } from './message.js';
6
+ /**
7
+ * A FIX data dictionary, used to validate messages against a FIX spec XML.
8
+ *
9
+ * Construct via {@link DataDictionary.fromFile} or
10
+ * {@link DataDictionary.fromString}; direct construction is not supported.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const dd = DataDictionary.fromFile('./spec/FIX44.xml');
15
+ * dd.validate(msg); // throws QuickFixError if invalid
16
+ * dd.getFieldName(35); // 'MsgType'
17
+ * Message.parse(raw, { dictionary: dd }); // structural parse (repeating groups)
18
+ * ```
19
+ */
20
+ export declare class DataDictionary {
21
+ #private;
22
+ /** @internal Use {@link DataDictionary.fromFile} / {@link DataDictionary.fromString}. */
23
+ private constructor();
24
+ /**
25
+ * Load a data dictionary from a FIX spec XML file.
26
+ *
27
+ * @param xmlPath Path to the FIX spec XML (e.g. `FIX44.xml`).
28
+ * @throws A `QuickFixError` (`fixError: 'ConfigError'`) if the file is invalid.
29
+ */
30
+ static fromFile(xmlPath: string): DataDictionary;
31
+ /**
32
+ * Load a data dictionary from FIX spec XML held in memory.
33
+ *
34
+ * @param xml The FIX spec XML contents.
35
+ * @throws A `QuickFixError` (`fixError: 'ConfigError'`) if the XML is invalid.
36
+ */
37
+ static fromString(xml: string): DataDictionary;
38
+ /** @internal Wrap an existing native handle. */
39
+ static fromNative(handle: NativeDataDictionary): DataDictionary;
40
+ /** @internal The native handle backing this dictionary. */
41
+ get nativeHandle(): NativeDataDictionary;
42
+ /**
43
+ * Validate a message against this dictionary.
44
+ *
45
+ * @param msg The message to validate.
46
+ * @param bodyOnly When `true`, validate only as the *application* dictionary
47
+ * (QuickFIX's `validate(msg, true)`): the BeginString version check and the
48
+ * tag/value validation of the header and trailer fields are skipped. It is
49
+ * not a check of a draft body: `BeginString` and `MsgType` must be set, and
50
+ * every field the dictionary requires, header and trailer included (e.g.
51
+ * `BodyLength`, `CheckSum`), must be present. Defaults to `false`.
52
+ * @throws A `QuickFixError` on any validation failure (e.g.
53
+ * `fixErrorName: 'InvalidMessage'` / `'RequiredTagMissing'` /
54
+ * `'IncorrectTagValue'` / `'UnsupportedVersion'`).
55
+ */
56
+ validate(msg: Message, bodyOnly?: boolean): void;
57
+ /** The BeginString the spec declares (e.g. `'FIX.4.4'`), or `''` if it has none. */
58
+ getVersion(): string;
59
+ /** The name of a field tag (e.g. `35` → `'MsgType'`), or `undefined` if the spec does not define it. */
60
+ getFieldName(tag: number): string | undefined;
61
+ /** The tag of a field name (e.g. `'MsgType'` → `35`), or `undefined` if the spec does not define it. */
62
+ getFieldTag(name: string): number | undefined;
63
+ /** Whether the spec defines a field with this tag. */
64
+ isField(tag: number): boolean;
65
+ /** Whether the spec defines a message with this MsgType (e.g. `'D'`). */
66
+ isMsgType(msgType: string): boolean;
67
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Ergonomic wrapper around the native `FIX::DataDictionary`.
3
+ */
4
+ import { native } from './native.js';
5
+ /**
6
+ * A FIX data dictionary, used to validate messages against a FIX spec XML.
7
+ *
8
+ * Construct via {@link DataDictionary.fromFile} or
9
+ * {@link DataDictionary.fromString}; direct construction is not supported.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const dd = DataDictionary.fromFile('./spec/FIX44.xml');
14
+ * dd.validate(msg); // throws QuickFixError if invalid
15
+ * dd.getFieldName(35); // 'MsgType'
16
+ * Message.parse(raw, { dictionary: dd }); // structural parse (repeating groups)
17
+ * ```
18
+ */
19
+ export class DataDictionary {
20
+ /** @internal Underlying native handle. */
21
+ #native;
22
+ /** @internal Use {@link DataDictionary.fromFile} / {@link DataDictionary.fromString}. */
23
+ constructor(handle) {
24
+ this.#native = handle;
25
+ }
26
+ /**
27
+ * Load a data dictionary from a FIX spec XML file.
28
+ *
29
+ * @param xmlPath Path to the FIX spec XML (e.g. `FIX44.xml`).
30
+ * @throws A `QuickFixError` (`fixError: 'ConfigError'`) if the file is invalid.
31
+ */
32
+ static fromFile(xmlPath) {
33
+ // The native `fromFile` may be a module-level fn or a class static;
34
+ // prefer the module-level fn, fall back to the static.
35
+ const handle = native.dataDictionaryFromFile?.(xmlPath) ??
36
+ native.DataDictionaryWrap.fromFile?.(xmlPath);
37
+ if (!handle) {
38
+ throw new Error('native DataDictionary.fromFile is unavailable');
39
+ }
40
+ return new DataDictionary(handle);
41
+ }
42
+ /**
43
+ * Load a data dictionary from FIX spec XML held in memory.
44
+ *
45
+ * @param xml The FIX spec XML contents.
46
+ * @throws A `QuickFixError` (`fixError: 'ConfigError'`) if the XML is invalid.
47
+ */
48
+ static fromString(xml) {
49
+ const handle = native.dataDictionaryFromString?.(xml) ??
50
+ native.DataDictionaryWrap.fromString?.(xml);
51
+ if (!handle) {
52
+ throw new Error('native DataDictionary.fromString is unavailable');
53
+ }
54
+ return new DataDictionary(handle);
55
+ }
56
+ /** @internal Wrap an existing native handle. */
57
+ static fromNative(handle) {
58
+ return new DataDictionary(handle);
59
+ }
60
+ /** @internal The native handle backing this dictionary. */
61
+ get nativeHandle() {
62
+ return this.#native;
63
+ }
64
+ /**
65
+ * Validate a message against this dictionary.
66
+ *
67
+ * @param msg The message to validate.
68
+ * @param bodyOnly When `true`, validate only as the *application* dictionary
69
+ * (QuickFIX's `validate(msg, true)`): the BeginString version check and the
70
+ * tag/value validation of the header and trailer fields are skipped. It is
71
+ * not a check of a draft body: `BeginString` and `MsgType` must be set, and
72
+ * every field the dictionary requires, header and trailer included (e.g.
73
+ * `BodyLength`, `CheckSum`), must be present. Defaults to `false`.
74
+ * @throws A `QuickFixError` on any validation failure (e.g.
75
+ * `fixErrorName: 'InvalidMessage'` / `'RequiredTagMissing'` /
76
+ * `'IncorrectTagValue'` / `'UnsupportedVersion'`).
77
+ */
78
+ validate(msg, bodyOnly = false) {
79
+ this.#native.validate(msg.nativeHandle, bodyOnly);
80
+ }
81
+ /** The BeginString the spec declares (e.g. `'FIX.4.4'`), or `''` if it has none. */
82
+ getVersion() {
83
+ return this.#native.getVersion();
84
+ }
85
+ /** The name of a field tag (e.g. `35` → `'MsgType'`), or `undefined` if the spec does not define it. */
86
+ getFieldName(tag) {
87
+ return this.#native.getFieldName(tag);
88
+ }
89
+ /** The tag of a field name (e.g. `'MsgType'` → `35`), or `undefined` if the spec does not define it. */
90
+ getFieldTag(name) {
91
+ return this.#native.getFieldTag(name);
92
+ }
93
+ /** Whether the spec defines a field with this tag. */
94
+ isField(tag) {
95
+ return this.#native.isField(tag);
96
+ }
97
+ /** Whether the spec defines a message with this MsgType (e.g. `'D'`). */
98
+ isMsgType(msgType) {
99
+ return this.#native.isMsgType(msgType);
100
+ }
101
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Shared implementation for {@link Initiator} and {@link Acceptor}.
3
+ *
4
+ * Both are thin subclasses of {@link Engine} that differ only in which native
5
+ * constructor they instantiate.
6
+ */
7
+ import { EventEmitter } from 'node:events';
8
+ import { type NativeEngineConstructor } from './native.js';
9
+ import { Message } from './message.js';
10
+ import { SessionID } from './session-id.js';
11
+ import { Session } from './session.js';
12
+ import type { ApplicationHandlers } from './application.js';
13
+ import type { SessionSettings } from './session-settings.js';
14
+ /** Options for constructing an {@link Initiator} or {@link Acceptor}. */
15
+ export interface EngineOptions {
16
+ /** Parsed session settings ({@link SessionSettings.fromString}/`fromFile`). */
17
+ settings: SessionSettings;
18
+ /**
19
+ * Synchronous application callbacks. These may mutate outbound messages and
20
+ * throw to reject inbound ones (see {@link ApplicationHandlers}).
21
+ */
22
+ handlers?: ApplicationHandlers;
23
+ /** Message-store factory. Defaults to the engine's default (`'file'`). */
24
+ store?: 'file' | 'memory';
25
+ /** Log factory. Defaults to the engine's default. */
26
+ log?: 'screen' | 'file' | 'none';
27
+ }
28
+ /**
29
+ * Events emitted (observe-only) by an {@link Engine}. Each mirrors a callback in
30
+ * {@link ApplicationHandlers} but CANNOT mutate or reject — do that via
31
+ * `handlers`.
32
+ */
33
+ export interface EngineEvents {
34
+ create: [sessionID: SessionID];
35
+ logon: [sessionID: SessionID];
36
+ logout: [sessionID: SessionID];
37
+ toAdmin: [message: Message, sessionID: SessionID];
38
+ fromAdmin: [message: Message, sessionID: SessionID];
39
+ toApp: [message: Message, sessionID: SessionID];
40
+ fromApp: [message: Message, sessionID: SessionID];
41
+ }
42
+ /**
43
+ * Base class for the FIX session engines.
44
+ *
45
+ * Extends Node's {@link EventEmitter}: in addition to invoking your `handlers`,
46
+ * the engine emits an observe-only event for each callback
47
+ * (`'create' | 'logon' | 'logout' | 'toAdmin' | 'fromAdmin' | 'toApp' | 'fromApp'`).
48
+ *
49
+ * **Important:** mutation of outbound messages and rejection of inbound ones
50
+ * MUST be done in `handlers` (which run synchronously and can throw); event
51
+ * listeners run after and cannot affect the message or reject it.
52
+ */
53
+ export declare abstract class Engine extends EventEmitter {
54
+ #private;
55
+ /**
56
+ * @param Native The native engine constructor (`InitiatorWrap`/`AcceptorWrap`).
57
+ * @param options Engine options.
58
+ */
59
+ protected constructor(Native: NativeEngineConstructor, options: EngineOptions);
60
+ /**
61
+ * Start the engine (begins connecting / listening).
62
+ *
63
+ * Runs off the main thread, so the Node.js event loop stays free to service
64
+ * the application callbacks/events the engine fires. The returned Promise
65
+ * resolves once startup completes.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * await initiator.start();
70
+ * ```
71
+ */
72
+ start(): Promise<void>;
73
+ /**
74
+ * Stop the engine.
75
+ *
76
+ * Runs off the main thread and resolves once shutdown completes, keeping the
77
+ * event loop free for any in-flight callbacks. Once it settles the engine's
78
+ * sessions are destroyed. A `stop()` called while another is still in flight
79
+ * (even from a `'logout'` listener that stop fired) returns the same Promise,
80
+ * so it too settles only once the sessions are gone; the first call's
81
+ * `force` applies to both.
82
+ *
83
+ * @param force When `true`, aborts immediately instead of draining gracefully.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * await initiator.stop();
88
+ * ```
89
+ */
90
+ stop(force?: boolean): Promise<void>;
91
+ /**
92
+ * Whether a session is logged on.
93
+ *
94
+ * With no argument: `true` if **any** of this engine's sessions is logged on.
95
+ * With a {@link SessionID}: `true` only if that session belongs to this engine
96
+ * and is logged on (`false`, not an error, for a foreign id). Always `false`
97
+ * before {@link Engine.start} and once {@link Engine.stop} has settled; while
98
+ * a graceful stop is still in progress it reports the live state.
99
+ *
100
+ * @throws {TypeError} if `sessionID` is given but is not a {@link SessionID}
101
+ * (it does not fall back to the engine-wide form).
102
+ */
103
+ isLoggedOn(sessionID?: SessionID): boolean;
104
+ /**
105
+ * The ids of the sessions this engine was configured with (every `[SESSION]`
106
+ * in its settings). Stable for the engine's lifetime, including after
107
+ * {@link Engine.stop}.
108
+ */
109
+ getSessions(): SessionID[];
110
+ /**
111
+ * A handle on one of this engine's sessions.
112
+ *
113
+ * @returns The {@link Session}, or `undefined` if `sessionID` is not one of
114
+ * this engine's sessions or {@link Engine.stop} has settled (that destroys
115
+ * its sessions). While a stop is still in progress the sessions are alive,
116
+ * so a `'logout'` listener can still read e.g. the final sequence numbers.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * initiator.on('logon', (id) => {
121
+ * const session = initiator.getSession(id)!;
122
+ * console.log('next seq', session.getExpectedSenderNum());
123
+ * });
124
+ * ```
125
+ */
126
+ getSession(sessionID: SessionID): Session | undefined;
127
+ /**
128
+ * Keep the Node.js event loop alive while the engine is running (default once
129
+ * started). Pair with {@link Engine.unref} to let a short script exit.
130
+ */
131
+ ref(): void;
132
+ /** Allow the process to exit even while the engine is running. */
133
+ unref(): void;
134
+ }
135
+ export interface Engine {
136
+ /** Register a listener for an engine event (typed per {@link EngineEvents}). */
137
+ on<E extends keyof EngineEvents>(event: E, listener: (...args: EngineEvents[E]) => void): this;
138
+ /** Register a one-shot listener for an engine event. */
139
+ once<E extends keyof EngineEvents>(event: E, listener: (...args: EngineEvents[E]) => void): this;
140
+ /** Alias of {@link Engine.on}. */
141
+ addListener<E extends keyof EngineEvents>(event: E, listener: (...args: EngineEvents[E]) => void): this;
142
+ /** Remove a previously registered listener. */
143
+ off<E extends keyof EngineEvents>(event: E, listener: (...args: EngineEvents[E]) => void): this;
144
+ /** Alias of {@link Engine.off}. */
145
+ removeListener<E extends keyof EngineEvents>(event: E, listener: (...args: EngineEvents[E]) => void): this;
146
+ /** Remove all listeners (optionally for a single event). */
147
+ removeAllListeners<E extends keyof EngineEvents>(event?: E): this;
148
+ /** Emit an engine event with its typed arguments. */
149
+ emit<E extends keyof EngineEvents>(event: E, ...args: EngineEvents[E]): boolean;
150
+ /** List the listeners registered for an event. */
151
+ listeners<E extends keyof EngineEvents>(event: E): Array<(...args: EngineEvents[E]) => void>;
152
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Shared implementation for {@link Initiator} and {@link Acceptor}.
3
+ *
4
+ * Both are thin subclasses of {@link Engine} that differ only in which native
5
+ * constructor they instantiate.
6
+ */
7
+ import { EventEmitter } from 'node:events';
8
+ import { Message } from './message.js';
9
+ import { SessionID, toNativeSessionID } from './session-id.js';
10
+ import { Session } from './session.js';
11
+ /**
12
+ * Base class for the FIX session engines.
13
+ *
14
+ * Extends Node's {@link EventEmitter}: in addition to invoking your `handlers`,
15
+ * the engine emits an observe-only event for each callback
16
+ * (`'create' | 'logon' | 'logout' | 'toAdmin' | 'fromAdmin' | 'toApp' | 'fromApp'`).
17
+ *
18
+ * **Important:** mutation of outbound messages and rejection of inbound ones
19
+ * MUST be done in `handlers` (which run synchronously and can throw); event
20
+ * listeners run after and cannot affect the message or reject it.
21
+ */
22
+ export class Engine extends EventEmitter {
23
+ /** @internal Underlying native engine handle. */
24
+ #native;
25
+ /**
26
+ * @param Native The native engine constructor (`InitiatorWrap`/`AcceptorWrap`).
27
+ * @param options Engine options.
28
+ */
29
+ constructor(Native, options) {
30
+ super();
31
+ const userHandlers = options.handlers ?? {};
32
+ // Bridge native handlers: wrap native handles into TS classes, invoke the
33
+ // user's handler (preserving mutate/throw semantics — exceptions propagate
34
+ // to the native bridge, which maps them to FIX exceptions), then emit the
35
+ // observe-only event.
36
+ const nativeHandlers = {
37
+ onCreate: (id) => {
38
+ const sid = SessionID.fromNative(id);
39
+ userHandlers.onCreate?.(sid);
40
+ this.emit('create', sid);
41
+ },
42
+ onLogon: (id) => {
43
+ const sid = SessionID.fromNative(id);
44
+ userHandlers.onLogon?.(sid);
45
+ this.emit('logon', sid);
46
+ },
47
+ onLogout: (id) => {
48
+ const sid = SessionID.fromNative(id);
49
+ userHandlers.onLogout?.(sid);
50
+ this.emit('logout', sid);
51
+ },
52
+ toAdmin: (m, id) => {
53
+ const msg = Message.fromNative(m);
54
+ const sid = SessionID.fromNative(id);
55
+ // Handler may mutate `msg` (writes go straight to the native handle).
56
+ userHandlers.toAdmin?.(msg, sid);
57
+ this.emit('toAdmin', msg, sid);
58
+ },
59
+ toApp: (m, id) => {
60
+ const msg = Message.fromNative(m);
61
+ const sid = SessionID.fromNative(id);
62
+ // May mutate or throw FixReject('DoNotSend') — do NOT swallow.
63
+ userHandlers.toApp?.(msg, sid);
64
+ this.emit('toApp', msg, sid);
65
+ },
66
+ fromAdmin: (m, id) => {
67
+ const msg = Message.fromNative(m);
68
+ const sid = SessionID.fromNative(id);
69
+ // May throw FixReject('RejectLogon', ...) — do NOT swallow.
70
+ userHandlers.fromAdmin?.(msg, sid);
71
+ this.emit('fromAdmin', msg, sid);
72
+ },
73
+ fromApp: (m, id) => {
74
+ const msg = Message.fromNative(m);
75
+ const sid = SessionID.fromNative(id);
76
+ // May throw FixReject('UnsupportedMessageType', ...) — do NOT swallow.
77
+ userHandlers.fromApp?.(msg, sid);
78
+ this.emit('fromApp', msg, sid);
79
+ },
80
+ };
81
+ this.#native = new Native({
82
+ settings: options.settings.nativeHandle,
83
+ handlers: nativeHandlers,
84
+ store: options.store,
85
+ log: options.log,
86
+ });
87
+ }
88
+ /**
89
+ * Start the engine (begins connecting / listening).
90
+ *
91
+ * Runs off the main thread, so the Node.js event loop stays free to service
92
+ * the application callbacks/events the engine fires. The returned Promise
93
+ * resolves once startup completes.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * await initiator.start();
98
+ * ```
99
+ */
100
+ start() {
101
+ return this.#native.start();
102
+ }
103
+ /**
104
+ * Stop the engine.
105
+ *
106
+ * Runs off the main thread and resolves once shutdown completes, keeping the
107
+ * event loop free for any in-flight callbacks. Once it settles the engine's
108
+ * sessions are destroyed. A `stop()` called while another is still in flight
109
+ * (even from a `'logout'` listener that stop fired) returns the same Promise,
110
+ * so it too settles only once the sessions are gone; the first call's
111
+ * `force` applies to both.
112
+ *
113
+ * @param force When `true`, aborts immediately instead of draining gracefully.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * await initiator.stop();
118
+ * ```
119
+ */
120
+ stop(force) {
121
+ return this.#native.stop(force);
122
+ }
123
+ /**
124
+ * Whether a session is logged on.
125
+ *
126
+ * With no argument: `true` if **any** of this engine's sessions is logged on.
127
+ * With a {@link SessionID}: `true` only if that session belongs to this engine
128
+ * and is logged on (`false`, not an error, for a foreign id). Always `false`
129
+ * before {@link Engine.start} and once {@link Engine.stop} has settled; while
130
+ * a graceful stop is still in progress it reports the live state.
131
+ *
132
+ * @throws {TypeError} if `sessionID` is given but is not a {@link SessionID}
133
+ * (it does not fall back to the engine-wide form).
134
+ */
135
+ isLoggedOn(sessionID) {
136
+ return sessionID === undefined
137
+ ? this.#native.isLoggedOn()
138
+ : this.#native.isLoggedOn(toNativeSessionID(sessionID, 'sessionID'));
139
+ }
140
+ /**
141
+ * The ids of the sessions this engine was configured with (every `[SESSION]`
142
+ * in its settings). Stable for the engine's lifetime, including after
143
+ * {@link Engine.stop}.
144
+ */
145
+ getSessions() {
146
+ return this.#native.getSessions().map((h) => SessionID.fromNative(h));
147
+ }
148
+ /**
149
+ * A handle on one of this engine's sessions.
150
+ *
151
+ * @returns The {@link Session}, or `undefined` if `sessionID` is not one of
152
+ * this engine's sessions or {@link Engine.stop} has settled (that destroys
153
+ * its sessions). While a stop is still in progress the sessions are alive,
154
+ * so a `'logout'` listener can still read e.g. the final sequence numbers.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * initiator.on('logon', (id) => {
159
+ * const session = initiator.getSession(id)!;
160
+ * console.log('next seq', session.getExpectedSenderNum());
161
+ * });
162
+ * ```
163
+ */
164
+ getSession(sessionID) {
165
+ const handle = this.#native.getSession(toNativeSessionID(sessionID, 'sessionID'));
166
+ return handle ? Session.fromNative(handle) : undefined;
167
+ }
168
+ /**
169
+ * Keep the Node.js event loop alive while the engine is running (default once
170
+ * started). Pair with {@link Engine.unref} to let a short script exit.
171
+ */
172
+ ref() {
173
+ this.#native.ref();
174
+ }
175
+ /** Allow the process to exit even while the engine is running. */
176
+ unref() {
177
+ this.#native.unref();
178
+ }
179
+ }