jspurefix 5.2.0 → 5.3.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/BACKPORT_PLAN.md +15 -29
- package/dist/config/js-fix-config.d.ts +2 -0
- package/dist/config/js-fix-config.js.map +1 -1
- package/dist/store/file-session-store.d.ts +42 -0
- package/dist/store/file-session-store.js +256 -0
- package/dist/store/file-session-store.js.map +1 -0
- package/dist/store/file-session-stream-provider.d.ts +25 -0
- package/dist/store/file-session-stream-provider.js +162 -0
- package/dist/store/file-session-stream-provider.js.map +1 -0
- package/dist/store/fix-session-store-factory.d.ts +13 -0
- package/dist/store/fix-session-store-factory.js +21 -0
- package/dist/store/fix-session-store-factory.js.map +1 -0
- package/dist/store/fix-session-store.d.ts +19 -0
- package/dist/store/fix-session-store.js +3 -0
- package/dist/store/fix-session-store.js.map +1 -0
- package/dist/store/index.d.ts +9 -0
- package/dist/store/index.js +9 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/memory-session-store.d.ts +27 -0
- package/dist/store/memory-session-store.js +104 -0
- package/dist/store/memory-session-store.js.map +1 -0
- package/dist/store/memory-session-stream-provider.d.ts +26 -0
- package/dist/store/memory-session-stream-provider.js +103 -0
- package/dist/store/memory-session-stream-provider.js.map +1 -0
- package/dist/store/session-id.d.ts +9 -0
- package/dist/store/session-id.js +55 -0
- package/dist/store/session-id.js.map +1 -0
- package/dist/store/session-stream-provider.d.ts +15 -0
- package/dist/store/session-stream-provider.js +3 -0
- package/dist/store/session-stream-provider.js.map +1 -0
- package/dist/store/store-config.d.ts +4 -0
- package/dist/store/store-config.js +3 -0
- package/dist/store/store-config.js.map +1 -0
- package/dist/transport/ascii/ascii-session.d.ts +6 -1
- package/dist/transport/ascii/ascii-session.js +37 -5
- package/dist/transport/ascii/ascii-session.js.map +1 -1
- package/dist/transport/session/session-description.d.ts +2 -0
- package/dist/transport/session/session-description.js.map +1 -1
- package/jsfix.test_client.txt +67 -67
- package/jsfix.test_server.txt +64 -64
- package/package.json +1 -1
- package/src/config/js-fix-config.ts +2 -0
- package/src/store/file-session-store.ts +294 -0
- package/src/store/file-session-stream-provider.ts +123 -0
- package/src/store/fix-session-store-factory.ts +31 -0
- package/src/store/fix-session-store.ts +37 -0
- package/src/store/index.ts +9 -0
- package/src/store/memory-session-store.ts +102 -0
- package/src/store/memory-session-stream-provider.ts +97 -0
- package/src/store/session-id.ts +32 -0
- package/src/store/session-stream-provider.ts +74 -0
- package/src/store/store-config.ts +15 -0
- package/src/transport/ascii/ascii-session.ts +57 -6
- package/src/transport/session/session-description.ts +2 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as path from 'path'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Identifies a FIX session for file naming and lookup.
|
|
5
|
+
* Format: {BeginString}-{SenderCompID}-{TargetCompID}
|
|
6
|
+
*/
|
|
7
|
+
export class SessionId {
|
|
8
|
+
constructor (
|
|
9
|
+
public readonly beginString: string,
|
|
10
|
+
public readonly senderCompID: string,
|
|
11
|
+
public readonly targetCompID: string
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a file prefix for QuickFix-compatible file naming.
|
|
16
|
+
* Example: "FIX.4.4-SENDER-TARGET"
|
|
17
|
+
*/
|
|
18
|
+
toFilePrefix (): string {
|
|
19
|
+
return `${this.beginString}-${this.senderCompID}-${this.targetCompID}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets the full path for a specific file extension.
|
|
24
|
+
*/
|
|
25
|
+
getFilePath (directory: string, extension: string): string {
|
|
26
|
+
return path.join(directory, `${this.toFilePrefix()}.${extension}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
toString (): string {
|
|
30
|
+
return this.toFilePrefix()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides stream access for session store operations.
|
|
3
|
+
* Allows abstraction of file I/O for testing with in-memory buffers.
|
|
4
|
+
*/
|
|
5
|
+
export interface ISessionStreamProvider {
|
|
6
|
+
/**
|
|
7
|
+
* Opens or creates a read-write buffer for the message body file.
|
|
8
|
+
* Must support random-access reads (by offset+length).
|
|
9
|
+
*/
|
|
10
|
+
openBody (): void
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Appends data to the body. Returns the offset at which data was written.
|
|
14
|
+
*/
|
|
15
|
+
appendBody (data: Buffer): Promise<number>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reads data from the body at the given offset and length.
|
|
19
|
+
*/
|
|
20
|
+
readBody (offset: number, length: number): Promise<Buffer>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets the current body size (for calculating offsets).
|
|
24
|
+
*/
|
|
25
|
+
getBodySize (): number
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Appends a line to the header index file.
|
|
29
|
+
*/
|
|
30
|
+
appendHeaderLine (line: string): Promise<void>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads all lines from the header index file.
|
|
34
|
+
* Returns empty array if no data exists.
|
|
35
|
+
*/
|
|
36
|
+
readHeaderLines (): Promise<string[]>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reads the sequence numbers string.
|
|
40
|
+
* Returns null if no data exists.
|
|
41
|
+
*/
|
|
42
|
+
readSeqNums (): Promise<string | null>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Writes the sequence numbers string.
|
|
46
|
+
*/
|
|
47
|
+
writeSeqNums (content: string): Promise<void>
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reads the session time string.
|
|
51
|
+
* Returns null if no data exists.
|
|
52
|
+
*/
|
|
53
|
+
readSessionTime (): Promise<string | null>
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Writes the session time string.
|
|
57
|
+
*/
|
|
58
|
+
writeSessionTime (content: string): Promise<void>
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resets all streams/files for a new session.
|
|
62
|
+
*/
|
|
63
|
+
reset (): Promise<void>
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Flushes any pending writes.
|
|
67
|
+
*/
|
|
68
|
+
flush (): Promise<void>
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Disposes of all resources.
|
|
72
|
+
*/
|
|
73
|
+
dispose (): Promise<void>
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for session message store.
|
|
3
|
+
* Add to session description JSON to enable persistent storage.
|
|
4
|
+
*
|
|
5
|
+
* Examples:
|
|
6
|
+
* "store": { "type": "memory" } — explicit in-memory (default)
|
|
7
|
+
* "store": { "type": "file" } — file store in ./store directory
|
|
8
|
+
* "store": { "type": "file", "directory": "/var/fix/sessions" }
|
|
9
|
+
*
|
|
10
|
+
* Omitting the store block entirely uses in-memory storage.
|
|
11
|
+
*/
|
|
12
|
+
export interface StoreConfig {
|
|
13
|
+
readonly type: 'memory' | 'file'
|
|
14
|
+
readonly directory?: string
|
|
15
|
+
}
|
|
@@ -2,22 +2,29 @@ import { MsgView } from '../../buffer'
|
|
|
2
2
|
import { MsgTag, MsgType, SessionRejectReason } from '../../types'
|
|
3
3
|
import { IJsFixConfig } from '../../config'
|
|
4
4
|
import { FixSession } from '../session/fix-session'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
FixMsgAsciiStoreResend, FixMsgMemoryStore, FixMsgStoreRecord,
|
|
7
|
+
IFixMsgStore, IFixMsgStoreRecord,
|
|
8
|
+
IFixSessionStore, IFixSessionStoreFactory,
|
|
9
|
+
MemorySessionStoreFactory, FileSessionStoreFactory, SessionId
|
|
10
|
+
} from '../../store'
|
|
6
11
|
import { SessionState } from '../tcp'
|
|
7
12
|
import { TickAction } from '../tick-action'
|
|
8
13
|
import { IMsgApplication } from '../msg-application'
|
|
9
14
|
import { SegmentType } from '../../buffer/segment/segment-type'
|
|
10
15
|
import { SessionSequenceCoordinator } from '../session/session-sequence-coordinator'
|
|
11
|
-
import { MemorySequenceStore } from '../session/session-sequence-store'
|
|
12
16
|
import { DefaultFixClock } from '../session/fix-clock'
|
|
13
17
|
import { ResendActionType } from '../session/resend-request-manager'
|
|
14
18
|
import { AsciiMsgTransmitter } from './ascii-msg-transmitter'
|
|
19
|
+
import { ILooseObject } from '../../collections/collection'
|
|
15
20
|
|
|
16
21
|
export abstract class AsciiSession extends FixSession {
|
|
17
22
|
public heartbeat: boolean = true
|
|
18
23
|
protected store: IFixMsgStore | null = null
|
|
19
24
|
protected resender: FixMsgAsciiStoreResend
|
|
20
25
|
protected readonly coordinator: SessionSequenceCoordinator
|
|
26
|
+
protected readonly sessionStore: IFixSessionStore
|
|
27
|
+
protected readonly sessionId: SessionId
|
|
21
28
|
|
|
22
29
|
protected constructor (public readonly config: IJsFixConfig) {
|
|
23
30
|
super(config)
|
|
@@ -26,9 +33,18 @@ export abstract class AsciiSession extends FixSession {
|
|
|
26
33
|
this.store = new FixMsgMemoryStore(this.config.description.SenderCompId, this.config)
|
|
27
34
|
this.resender = new FixMsgAsciiStoreResend(this.store, this.config)
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
// Create session store from factory.
|
|
37
|
+
// Priority: programmatic config > JSON store config > default in-memory
|
|
38
|
+
const storeFactory = config.sessionStoreFactory ?? AsciiSession.createStoreFactory(config.description.store)
|
|
39
|
+
this.sessionId = new SessionId(
|
|
40
|
+
config.description.BeginString,
|
|
41
|
+
config.description.SenderCompId,
|
|
42
|
+
config.description.TargetCompID
|
|
43
|
+
)
|
|
44
|
+
this.sessionStore = storeFactory.create(this.sessionId)
|
|
45
|
+
|
|
30
46
|
const clock = new DefaultFixClock()
|
|
31
|
-
this.coordinator = new SessionSequenceCoordinator(
|
|
47
|
+
this.coordinator = new SessionSequenceCoordinator(this.sessionStore, clock)
|
|
32
48
|
const lastReceivedSeqNum = config.description.LastReceivedSeqNum ?? 0
|
|
33
49
|
this.coordinator.initializeFromConfig(undefined, lastReceivedSeqNum + 1)
|
|
34
50
|
}
|
|
@@ -67,6 +83,14 @@ export abstract class AsciiSession extends FixSession {
|
|
|
67
83
|
this.coordinator.onMessageReceived(seqNo, true)
|
|
68
84
|
return true
|
|
69
85
|
}
|
|
86
|
+
// Check if this is a delayed message that fills a pending gap range.
|
|
87
|
+
const pendingRequests = this.coordinator.pendingResendRequests
|
|
88
|
+
const inPendingGapRange = pendingRequests.some(p => seqNo >= p.begin && seqNo <= p.end)
|
|
89
|
+
if (inPendingGapRange) {
|
|
90
|
+
this.sessionLogger.info(`accepting delayed message seq ${seqNo} (in pending gap range)`)
|
|
91
|
+
this.coordinator.onMessageReceived(seqNo, false)
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
70
94
|
// serious problem ... drop immediately
|
|
71
95
|
this.sessionLogger.warning(`terminate as seqDelta (${seqDelta}) < 0 lastSeq = ${lastSeq} seqNo = ${seqNo}`)
|
|
72
96
|
this.stop()
|
|
@@ -106,8 +130,10 @@ export abstract class AsciiSession extends FixSession {
|
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
//
|
|
110
|
-
//
|
|
133
|
+
// Accept the current message — don't block waiting for gap fill.
|
|
134
|
+
// The gap will be filled by the resend response, but this message is valid.
|
|
135
|
+
ret = true
|
|
136
|
+
state.lastPeerMsgSeqNum = seqNo
|
|
111
137
|
this.coordinator.onMessageReceived(seqNo, false)
|
|
112
138
|
} else {
|
|
113
139
|
ret = true
|
|
@@ -250,9 +276,32 @@ export abstract class AsciiSession extends FixSession {
|
|
|
250
276
|
this.sessionLogger.info('coordinator reset transient state for reconnect')
|
|
251
277
|
}
|
|
252
278
|
|
|
279
|
+
protected override txOnEncoded (msgType: string, data: string, hdr: ILooseObject): void {
|
|
280
|
+
super.txOnEncoded(msgType, data, hdr)
|
|
281
|
+
// Store the encoded message in the session store for recovery/resend
|
|
282
|
+
const seqNum = hdr?.MsgSeqNum as number | undefined
|
|
283
|
+
if (seqNum != null) {
|
|
284
|
+
const record = new FixMsgStoreRecord(msgType, new Date(), seqNum, undefined, data)
|
|
285
|
+
this.sessionStore.put(record).catch((e: Error) => {
|
|
286
|
+
// Never block sends on store errors
|
|
287
|
+
this.sessionLogger.warning(`failed to store message seq=${seqNum}: ${e.message}`)
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
253
292
|
private static readonly MaxLogonRetries = 100
|
|
254
293
|
private static readonly MaxTimeoutRecoveryAttempts = 0
|
|
255
294
|
|
|
295
|
+
private static createStoreFactory (storeConfig?: { type: string, directory?: string }): IFixSessionStoreFactory {
|
|
296
|
+
if (!storeConfig) return new MemorySessionStoreFactory()
|
|
297
|
+
switch (storeConfig.type?.toLowerCase()) {
|
|
298
|
+
case 'file':
|
|
299
|
+
return new FileSessionStoreFactory(storeConfig.directory ?? 'store')
|
|
300
|
+
default:
|
|
301
|
+
return new MemorySessionStoreFactory()
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
256
305
|
private handleLogonRejected (text: string | null): void {
|
|
257
306
|
if (!this.coordinator.onLogonRejectedForSequence(AsciiSession.MaxLogonRetries)) {
|
|
258
307
|
this.sessionLogger.warning(`max logon retries (${AsciiSession.MaxLogonRetries}) exceeded, giving up. Text='${text}'`)
|
|
@@ -488,6 +537,8 @@ export abstract class AsciiSession extends FixSession {
|
|
|
488
537
|
const action: TickAction = sessionState.calcAction(new Date())
|
|
489
538
|
const application: IMsgApplication | null = this.transport.config.description.application ?? null
|
|
490
539
|
const logger = this.sessionLogger
|
|
540
|
+
// Clean up timed-out resend requests
|
|
541
|
+
this.coordinator.tick()
|
|
491
542
|
|
|
492
543
|
switch (action) {
|
|
493
544
|
case TickAction.Nothing: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IMsgApplication } from '../msg-application'
|
|
2
|
+
import { StoreConfig } from '../../store/store-config'
|
|
2
3
|
|
|
3
4
|
export interface IDynamicSessionParams {
|
|
4
5
|
readonly Name: string
|
|
@@ -18,4 +19,5 @@ export interface ISessionDescription extends IDynamicSessionParams {
|
|
|
18
19
|
LastSentSeqNum?: number
|
|
19
20
|
readonly LastReceivedSeqNum?: number
|
|
20
21
|
readonly BodyLengthChars?: number
|
|
22
|
+
readonly store?: StoreConfig
|
|
21
23
|
}
|