jspurefix 5.8.0 → 5.8.5
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/README.md +11 -2
- package/dist/buffer/ascii/ascii-parser.d.ts +2 -1
- package/dist/buffer/ascii/ascii-parser.js +5 -3
- package/dist/buffer/ascii/ascii-parser.js.map +1 -1
- package/dist/buffer/tag/tags.d.ts +2 -1
- package/dist/buffer/tag/tags.js +8 -4
- package/dist/buffer/tag/tags.js.map +1 -1
- package/dist/transport/ascii/ascii-session.js +33 -11
- package/dist/transport/ascii/ascii-session.js.map +1 -1
- package/dist/transport/session/fix-session.js +12 -4
- package/dist/transport/session/fix-session.js.map +1 -1
- package/jsfix.test_client.txt +75 -75
- package/jsfix.test_server.txt +72 -72
- package/package.json +9 -9
- package/src/buffer/ascii/ascii-parser.ts +3 -2
- package/src/buffer/tag/tags.ts +8 -4
- package/src/transport/ascii/ascii-session.ts +33 -11
- package/src/transport/session/fix-session.ts +17 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jspurefix",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.5",
|
|
4
4
|
"description": "pure node js fix engine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"repo44-bench-tc": "node dist/jsfix-cmd jsfix-cmd --dict=repo44 --fix=data/examples/FIX.4.4/repo/trade-capture/fix.txt --benchmark --delimiter=\"|\" --repeats=30000",
|
|
45
45
|
"repo44-bench-sd": "node dist/jsfix-cmd --dict=repo44 --fix=data/examples/FIX.4.4/repo/security-definition/fix.txt --benchmark --delimiter=\"|\" --repeats=150000",
|
|
46
46
|
"repo44-bench-lo": "node dist/jsfix-cmd --dict=repo44 --fix=data/examples/FIX.4.4/repo/logon/fix.txt --benchmark --delimiter=\"|\" --repeats=250000",
|
|
47
|
-
"qf-bench-lo": "node dist/jsfix-cmd --session=data/session/test-initiator.json --fix=data/examples/FIX.4.4/quickfix/logon/fix.txt --benchmark --delimiter=\"|\" --repeats=
|
|
48
|
-
"qf-bench-hb": "node dist/jsfix-cmd --session=data/session/test-initiator.json --fix=data/examples/FIX.4.4/quickfix/heartbeat/fix.txt --benchmark --delimiter=\"|\" --repeats=
|
|
47
|
+
"qf-bench-lo": "node dist/jsfix-cmd --session=data/session/test-initiator.json --fix=data/examples/FIX.4.4/quickfix/logon/fix.txt --benchmark --delimiter=\"|\" --repeats=1000000",
|
|
48
|
+
"qf-bench-hb": "node dist/jsfix-cmd --session=data/session/test-initiator.json --fix=data/examples/FIX.4.4/quickfix/heartbeat/fix.txt --benchmark --delimiter=\"|\" --repeats=1000000",
|
|
49
49
|
"fixml": "node dist/jsfix-cmd --dict=repofixml",
|
|
50
50
|
"tcp-qf-md": "node dist/sample/tcp/qf-md/app",
|
|
51
51
|
"tcp-tls-tc": "node dist/sample/tcp/tls-trade-capture/app",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"author": "",
|
|
75
75
|
"license": "MIT",
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@jest/globals": "^30.
|
|
77
|
+
"@jest/globals": "^30.4.1",
|
|
78
78
|
"align-text": "^1.0.2",
|
|
79
|
-
"axios": "^1.
|
|
79
|
+
"axios": "^1.16.1",
|
|
80
80
|
"express": "^5.2.1",
|
|
81
81
|
"lodash": "^4.18.1",
|
|
82
82
|
"mathjs": "^15.2.0",
|
|
@@ -99,16 +99,16 @@
|
|
|
99
99
|
"@types/lodash": "^4.17.24",
|
|
100
100
|
"@types/mathjs": "^9.4.2",
|
|
101
101
|
"@types/minimist": "^1.2.5",
|
|
102
|
-
"@types/node": "^25.
|
|
102
|
+
"@types/node": "^25.9.1",
|
|
103
103
|
"@types/request-promise-native": "^1.0.21",
|
|
104
104
|
"@types/sax": "^1.2.7",
|
|
105
105
|
"@types/winston": "^2.4.4",
|
|
106
106
|
"eslint": "^9.39.4",
|
|
107
107
|
"eslint-config-love": "^151.0.0",
|
|
108
|
-
"jest": "^30.
|
|
108
|
+
"jest": "^30.4.2",
|
|
109
109
|
"madge": "^8.0.0",
|
|
110
110
|
"standard": "^17.1.2",
|
|
111
|
-
"ts-jest": "^29.4.
|
|
112
|
-
"typescript": "^6.0.
|
|
111
|
+
"ts-jest": "^29.4.11",
|
|
112
|
+
"typescript": "^6.0.3"
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -30,7 +30,8 @@ export class AsciiParser extends MsgParser {
|
|
|
30
30
|
|
|
31
31
|
constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig,
|
|
32
32
|
@inject(DITokens.readStream) public readonly readStream: Readable | null,
|
|
33
|
-
@inject(DITokens.ParseBuffer) protected readonly receivingBuffer: ElasticBuffer
|
|
33
|
+
@inject(DITokens.ParseBuffer) protected readonly receivingBuffer: ElasticBuffer,
|
|
34
|
+
@inject(DITokens.maxMessageLen) public readonly maxMessageLen: number = 160 * 1024) {
|
|
34
35
|
super()
|
|
35
36
|
|
|
36
37
|
this.delimiter = config.delimiter ?? AsciiChars.Soh
|
|
@@ -38,7 +39,7 @@ export class AsciiParser extends MsgParser {
|
|
|
38
39
|
this.id = AsciiParser.nextId++
|
|
39
40
|
this.segmentParser = config.sessionContainer.resolve<AsciiSegmentParser>(AsciiSegmentParser)
|
|
40
41
|
this.state = config.sessionContainer.resolve<AsciiParserState>(AsciiParserState)
|
|
41
|
-
this.state.locations = new Tags(this.receivingBuffer.size / 10)
|
|
42
|
+
this.state.locations = new Tags(this.receivingBuffer.size / 10, this.maxMessageLen)
|
|
42
43
|
this.state.definitions = this.config.definitions
|
|
43
44
|
this.state.beginMessage()
|
|
44
45
|
if (readStream !== null) {
|
package/src/buffer/tag/tags.ts
CHANGED
|
@@ -8,10 +8,11 @@ export class Tags {
|
|
|
8
8
|
public static readonly CheckSumTag: number = MsgTag.CheckSum
|
|
9
9
|
public static readonly MsgTag: number = MsgTag.MsgType
|
|
10
10
|
|
|
11
|
-
public tagPos: TagPos[] = new Array(this.startingLength)
|
|
11
|
+
public tagPos: TagPos[] = new Array(Math.min(this.startingLength, this.maxLength))
|
|
12
12
|
public nextTagPos: number = 0
|
|
13
13
|
|
|
14
|
-
constructor (public readonly startingLength: number = 30 * 1000
|
|
14
|
+
constructor (public readonly startingLength: number = 30 * 1000,
|
|
15
|
+
public readonly maxLength: number = 1024 * 1024) {
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
public static toJSType (tagType: TagType): string {
|
|
@@ -101,7 +102,7 @@ export class Tags {
|
|
|
101
102
|
|
|
102
103
|
public clone (): Tags {
|
|
103
104
|
const next: number = this.nextTagPos
|
|
104
|
-
const cloned: Tags = new Tags(next)
|
|
105
|
+
const cloned: Tags = new Tags(next, this.maxLength)
|
|
105
106
|
cloned.nextTagPos = next
|
|
106
107
|
for (let i = 0; i < next; ++i) {
|
|
107
108
|
cloned.tagPos[i] = this.tagPos[i].clone()
|
|
@@ -128,7 +129,10 @@ export class Tags {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
private expand (): void {
|
|
131
|
-
|
|
132
|
+
if (this.tagPos.length >= this.maxLength) {
|
|
133
|
+
throw new Error(`tag count exceeds maximum of ${this.maxLength} - message too large or stream misaligned`)
|
|
134
|
+
}
|
|
135
|
+
const size = Math.min(this.tagPos.length * 2, this.maxLength)
|
|
132
136
|
const tagPos = new Array(size)
|
|
133
137
|
for (let i = 0; i < this.tagPos.length; ++i) {
|
|
134
138
|
tagPos[i] = this.tagPos[i]
|
|
@@ -101,7 +101,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
101
101
|
this.sessionLogger.info('logon with ResetSeqNumFlag=Y, accepting regardless of sequence')
|
|
102
102
|
const seqNo = view.getTyped(MsgTag.MsgSeqNum) as number
|
|
103
103
|
this.sessionState.lastPeerMsgSeqNum = seqNo
|
|
104
|
-
this.coordinator.onMessageReceived(seqNo, false)
|
|
104
|
+
this.coordinator.onMessageReceived(seqNo, false).catch((e: Error) => {
|
|
105
|
+
this.sessionLogger.warning(`coordinator.onMessageReceived store persist failed: ${e.message}`)
|
|
106
|
+
})
|
|
105
107
|
return true
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -119,7 +121,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
119
121
|
const possDupFlag = view.getTyped(MsgTag.PossDupFlag) as boolean | undefined
|
|
120
122
|
if (possDupFlag === true) {
|
|
121
123
|
this.sessionLogger.debug(`message '${msgType}' has PossDupFlag=Y, bypassing sequence check`)
|
|
122
|
-
this.coordinator.onMessageReceived(seqNo, true)
|
|
124
|
+
this.coordinator.onMessageReceived(seqNo, true).catch((e: Error) => {
|
|
125
|
+
this.sessionLogger.warning(`coordinator.onMessageReceived store persist failed: ${e.message}`)
|
|
126
|
+
})
|
|
123
127
|
return true
|
|
124
128
|
}
|
|
125
129
|
// Check if this is a delayed message that fills a pending gap range.
|
|
@@ -127,7 +131,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
127
131
|
const inPendingGapRange = pendingRequests.some(p => seqNo >= p.begin && seqNo <= p.end)
|
|
128
132
|
if (inPendingGapRange) {
|
|
129
133
|
this.sessionLogger.info(`accepting delayed message seq ${seqNo} (in pending gap range)`)
|
|
130
|
-
this.coordinator.onMessageReceived(seqNo, false)
|
|
134
|
+
this.coordinator.onMessageReceived(seqNo, false).catch((e: Error) => {
|
|
135
|
+
this.sessionLogger.warning(`coordinator.onMessageReceived store persist failed: ${e.message}`)
|
|
136
|
+
})
|
|
131
137
|
return true
|
|
132
138
|
}
|
|
133
139
|
// serious problem ... drop immediately
|
|
@@ -171,7 +177,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
171
177
|
|
|
172
178
|
// Update sequence state regardless — the gap-triggering message is valid.
|
|
173
179
|
state.lastPeerMsgSeqNum = seqNo
|
|
174
|
-
this.coordinator.onMessageReceived(seqNo, false)
|
|
180
|
+
this.coordinator.onMessageReceived(seqNo, false).catch((e: Error) => {
|
|
181
|
+
this.sessionLogger.warning(`coordinator.onMessageReceived store persist failed: ${e.message}`)
|
|
182
|
+
})
|
|
175
183
|
|
|
176
184
|
// Logon and ResendRequest were already fully handled above (peerLogon / onResendRequest).
|
|
177
185
|
// Don't return true for them — they must not be dispatched to onSessionMsg again.
|
|
@@ -182,7 +190,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
182
190
|
} else {
|
|
183
191
|
ret = true
|
|
184
192
|
state.lastPeerMsgSeqNum = seqNo
|
|
185
|
-
this.coordinator.onMessageReceived(seqNo, false)
|
|
193
|
+
this.coordinator.onMessageReceived(seqNo, false).catch((e: Error) => {
|
|
194
|
+
this.sessionLogger.warning(`coordinator.onMessageReceived store persist failed: ${e.message}`)
|
|
195
|
+
})
|
|
186
196
|
}
|
|
187
197
|
|
|
188
198
|
// Reset timeout recovery on successful message receipt
|
|
@@ -333,7 +343,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
333
343
|
this.sessionState.lastPeerMsgSeqNum = this.coordinator.lastProcessedPeerSeqNum
|
|
334
344
|
|
|
335
345
|
if (this.store) {
|
|
336
|
-
this.store.clear()
|
|
346
|
+
this.store.clear().catch((e: Error) => {
|
|
347
|
+
this.sessionLogger.warning(`store.clear failed: ${e.message}`)
|
|
348
|
+
})
|
|
337
349
|
this.resender = new FixMsgAsciiStoreResend(this.store, this.config)
|
|
338
350
|
}
|
|
339
351
|
|
|
@@ -439,7 +451,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
439
451
|
// expect newSeqNo to be the next message's sequence number.
|
|
440
452
|
this.sessionState.lastPeerMsgSeqNum = newSeqNo - 1
|
|
441
453
|
// Notify coordinator to update expected target and clear pending resend requests
|
|
442
|
-
this.coordinator.onGapFillReceived(gapFillSeq, newSeqNo)
|
|
454
|
+
this.coordinator.onGapFillReceived(gapFillSeq, newSeqNo).catch((e: Error) => {
|
|
455
|
+
this.sessionLogger.warning(`coordinator.onGapFillReceived store persist failed: ${e.message}`)
|
|
456
|
+
})
|
|
443
457
|
break
|
|
444
458
|
}
|
|
445
459
|
|
|
@@ -524,7 +538,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
524
538
|
|
|
525
539
|
// Fire-and-forget the async coordinator call (store updates resolve on next microtask)
|
|
526
540
|
// but compute the expected values synchronously since we know the reset outcome
|
|
527
|
-
this.coordinator.handlePeerReset(peerSeqNum, weAlsoReset)
|
|
541
|
+
this.coordinator.handlePeerReset(peerSeqNum, weAlsoReset).catch((e: Error) => {
|
|
542
|
+
this.sessionLogger.warning(`coordinator.handlePeerReset store persist failed: ${e.message}`)
|
|
543
|
+
})
|
|
528
544
|
if (transmitter) {
|
|
529
545
|
transmitter.msgSeqNum = savedEncoderSeqNum ?? 1
|
|
530
546
|
}
|
|
@@ -532,7 +548,9 @@ export abstract class AsciiSession extends FixSession {
|
|
|
532
548
|
|
|
533
549
|
// Recreate resender with empty store
|
|
534
550
|
if (this.store) {
|
|
535
|
-
this.store.clear()
|
|
551
|
+
this.store.clear().catch((e: Error) => {
|
|
552
|
+
this.sessionLogger.warning(`store.clear failed: ${e.message}`)
|
|
553
|
+
})
|
|
536
554
|
this.resender = new FixMsgAsciiStoreResend(this.store, this.config)
|
|
537
555
|
}
|
|
538
556
|
logger.info(`reset complete: encoderSeqNum=${transmitter?.msgSeqNum}, lastPeerMsgSeqNum=${peerSeqNum}`)
|
|
@@ -555,14 +573,18 @@ export abstract class AsciiSession extends FixSession {
|
|
|
555
573
|
if (weReset && resetSeqNumFlag !== true) {
|
|
556
574
|
logger.info('acceptor sending ResetSeqNumFlag=Y (peer sent N), resetting sequences')
|
|
557
575
|
// Fire-and-forget async coordinator call, set values synchronously
|
|
558
|
-
this.coordinator.resetAsAcceptor()
|
|
576
|
+
this.coordinator.resetAsAcceptor().catch((e: Error) => {
|
|
577
|
+
this.sessionLogger.warning(`coordinator.resetAsAcceptor store persist failed: ${e.message}`)
|
|
578
|
+
})
|
|
559
579
|
const transmitter = this.transport?.transmitter as AsciiMsgTransmitter | undefined
|
|
560
580
|
if (transmitter) {
|
|
561
581
|
transmitter.msgSeqNum = 1
|
|
562
582
|
}
|
|
563
583
|
this.sessionState.lastPeerMsgSeqNum = 0
|
|
564
584
|
if (this.store) {
|
|
565
|
-
this.store.clear()
|
|
585
|
+
this.store.clear().catch((e: Error) => {
|
|
586
|
+
this.sessionLogger.warning(`store.clear failed: ${e.message}`)
|
|
587
|
+
})
|
|
566
588
|
this.resender = new FixMsgAsciiStoreResend(this.store, this.config)
|
|
567
589
|
}
|
|
568
590
|
}
|
|
@@ -110,14 +110,26 @@ export abstract class FixSession extends events.EventEmitter {
|
|
|
110
110
|
this.setState(SessionState.WaitingForALogon)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// Use named handlers and detach BOTH when the promise settles. Only one of
|
|
114
|
+
// 'error'/'done' fires per run; without removing the sibling the unfired
|
|
115
|
+
// listener would leak on every reconnect (issue #146). `once` alone is not
|
|
116
|
+
// enough for the same reason — the sibling never fires, so never self-detaches.
|
|
117
|
+
const cleanup = (): void => {
|
|
118
|
+
this.removeListener('error', onError)
|
|
119
|
+
this.removeListener('done', onDone)
|
|
120
|
+
}
|
|
121
|
+
const onError = (e: Error): void => {
|
|
122
|
+
cleanup()
|
|
114
123
|
logger.error(e)
|
|
115
124
|
reject(e)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
}
|
|
126
|
+
const onDone = (): void => {
|
|
127
|
+
cleanup()
|
|
119
128
|
resolve(this.transport?.id)
|
|
120
|
-
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.on('error', onError)
|
|
132
|
+
this.on('done', onDone)
|
|
121
133
|
})
|
|
122
134
|
}
|
|
123
135
|
|