npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jspurefix

v5.8.0

Published

pure node js fix engine

Readme

jspurefix

CI JavaScript Style Guide

A fast, fully native TypeScript FIX protocol engine for Node.js. Built around a data-dictionary driven parser, with first-class support for sessions over TCP/TLS, persistent message stores, sequence recovery, FIXML over HTTP, and generated typed interfaces for any FIX dialect.

Table of Contents

Features

  • 100% native TypeScript — no native bindings, runs anywhere Node.js does
  • ASCII (tag=value) and FIXML message encodings
  • Repeating groups, components, nested structures and raw data fields
  • Dictionary-driven: load QuickFIX XML or FIX repository definitions, compile typed interfaces
  • Full session lifecycle: logon, heartbeats, test requests, resend requests, logout
  • TLS-encrypted sessions over TCP
  • Pluggable persistent message store (in-memory or file) with sequence recovery
  • HTTP initiator/acceptor for FIXML
  • Command-line tool for parsing FIX logs into tokens, JSON, or structure dumps
  • Sample applications: trade capture, market data, FIXML OMS, recovering skeleton

Installation

npm install jspurefix
cd node_modules/jspurefix && npm run unzip-repo

unzip-repo extracts the bundled FIX dictionaries. The postinstall hook will normally do this for you, but the command is exposed in case you need to re-run it.

A standalone demo project lives at TimelordUK/jspf-demo — the fastest way to see a working initiator/acceptor.

Quickstart

Import the session types you need from jspurefix and the typed FIX messages for your dialect:

import {
  AsciiSession,
  MsgView,
  IJsFixConfig,
  IJsFixLogger,
  Dictionary,
  MsgType,
  initiator,
  acceptor,
  makeConfig
} from 'jspurefix'

import {
  ITradeCaptureReport,
  ITradeCaptureReportRequest,
  ITradeCaptureReportRequestAck
} from 'jspurefix/dist/types/FIX4.4/repo'

A minimal session subclasses AsciiSession and implements two callbacks: onReady (connection up, logon confirmed) and onApplicationMsg (a non-session message arrived).

class TradeCaptureClient extends AsciiSession {
  constructor (public readonly config: IJsFixConfig) {
    super(config)
    this.logReceivedMsgs = true
    this.fixLog = config.logFactory.plain(`jsfix.${config.description.application.name}.txt`)
    this.logger = config.logFactory.logger(`${this.me}:TradeCaptureClient`)
  }

  protected onReady (view: MsgView): void {
    const tcr: ITradeCaptureReportRequest =
      TradeFactory.tradeCaptureReportRequest('all-trades', new Date())
    this.send(MsgType.TradeCaptureReportRequest, tcr)
  }

  protected onApplicationMsg (msgType: string, view: MsgView): void {
    switch (msgType) {
      case MsgType.TradeCaptureReport: {
        const tc: ITradeCaptureReport = view.toObject()
        this.logger.info(`tc ${tc.TradeReportID} ${tc.Instrument.Symbol} ${tc.LastQty} @ ${tc.LastPx}`)
        break
      }
    }
  }
}

The full sample lives at src/sample/tcp/trade-capture/ and runs both sides over a local socket:

npm run tcp-tc      # full trade-capture client/server demo
npm run tcp-sk      # bare skeleton: connect, log on, idle
npm run http-oms    # FIXML order/exec-report over HTTP

Each demo terminates after about a minute, or with Ctrl-C.

Session Configuration

A session is described by a JSON file (or any object matching ISessionDescription). Example: data/session/test-initiator.json.

{
  "application": {
    "type": "initiator",
    "name": "test_client",
    "reconnectSeconds": 10,
    "tcp": { "host": "localhost", "port": 2344 },
    "protocol": "ascii",
    "dictionary": "repo44"
  },
  "Username": "js-client",
  "Password": "pwd-client",
  "EncryptMethod": 0,
  "ResetSeqNumFlag": true,
  "HeartBtInt": 30,
  "SenderCompId": "init-comp",
  "TargetCompID": "accept-comp",
  "TargetSubID": "fix",
  "BeginString": "FIX.4.4"
}

TLS

Add a tls block under application.tcp. The ca field is only needed for self-signed certificates; commercial vendors will supply this for you. script/getKey.ps1 will generate a self-signed CA + client/server pair (requires openssl on the path).

{
  "application": {
    "type": "initiator",
    "name": "test_client",
    "tcp": {
      "host": "localhost",
      "port": 2344,
      "tls": {
        "timeout": 10000,
        "sessionTimeout": 10000,
        "enableTrace": true,
        "key": "data/session/certs/client/client.key",
        "cert": "data/session/certs/client/client.crt",
        "ca": ["data/session/certs/ca/ca.crt"]
      }
    },
    "protocol": "ascii",
    "dictionary": "repo44"
  },
  "BeginString": "FIX4.4"
}

See data/session/test-initiator-tls.json for the complete file.

Body length padding

BodyLengthChars controls how the tag-9 body-length field is zero-padded. Defaults to 7; set to a smaller value (e.g. 6) when interoperating with a counterparty that requires it.

{ "BodyLengthChars": 6 }

Persistence & Recovery

By default, every session uses an in-memory message store — sequence numbers and any stored messages are lost when the process exits. For production use you'll typically want either persisted sequences or a full file-backed store with replay.

Choosing a store

Add a store block to the session description. Omit it to keep the in-memory default.

{
  "store": { "type": "memory" }
}
{
  "store": {
    "type": "file",
    "directory": "/var/fix/sessions"
  }
}

The file store writes QuickFIX-compatible files into the directory:

| File | Contents | | --- | --- | | <session>.seqnums | Current sender / target sequence numbers | | <session>.session | Session creation timestamp | | <session>.header | Index lines seqnum,offset,length into .body | | <session>.body | Concatenated raw FIX messages for resend |

Session names are derived from BeginString-SenderCompID-TargetCompID.

You can also pass a custom factory programmatically via IJsFixConfig.sessionStoreFactory — useful for testing or for plugging in an alternative backend (Redis, S3, etc.).

ResetSeqNumFlag semantics

  • "ResetSeqNumFlag": true — every logon resets sender/target sequences back to 1. The engine clears the persisted store before sending the Logon, so the message correctly carries MsgSeqNum=1 even after a reconnect with recovered state. (See issue #140.)
  • "ResetSeqNumFlag": false — sequences are preserved across logons. To seed initial sequences for a brand-new session (no persisted store), set LastSentSeqNum and LastReceivedSeqNum:
{
  "ResetSeqNumFlag": false,
  "LastSentSeqNum": 10,
  "LastReceivedSeqNum": 11
}

With a file store configured, LastSentSeqNum / LastReceivedSeqNum are only consulted the first time a session is started; subsequent runs read from the persisted .seqnums file.

Resending messages

When a counterparty asks for missed messages, the engine needs access to the originals. The file store keeps every encoded message and the bundled AsciiSession.onResendRequest() will replay from it automatically. If you're using the in-memory store and want resend support, override onResendRequest() with your own retrieval logic and set the duplicate flag on each replayed message:

{
  ...messageBodyData,
  StandardHeader: { PossDupFlag: true, MsgSeqNum: sequenceNumber }
}

Example payload:

{
  "ClOrdID": "acceptor-order-id",
  "HandlInst": "2",
  "OrdType": "2",
  "Side": "2",
  "TransactTime": "2021-08-03T08:23:57.041Z",
  "Symbol": "some ticker",
  "StandardHeader": {
    "PossDupFlag": true,
    "MsgSeqNum": 2
  }
}

Working with Messages

A MsgView is a zero-copy view over the parse buffer. The view is only valid inside the callback that received it — clone it (view.clone()) if you need to hold onto it past the current tick. Most code converts the view to a typed object via toObject().

import { ITradeCaptureReport } from 'jspurefix/dist/types/FIX4.4/repo'
const tc: ITradeCaptureReport = view.toObject()

Read a single tag by name or number:

expect(erView.getString(35)).toEqual('8')
expect(erView.getString('MsgType')).toEqual('8')
expect(erView.getTyped(9)).toEqual(6542)
expect(erView.getTyped('TotNumReports')).toEqual(19404)

Read several tags in one call:

const [a, b, c, d] = view.getTypedTags([8, 9, 35, 49])

Read all instances of a repeated tag:

expect(erView.getStrings('PartyID')).toEqual(['magna.', 'iaculis', 'vitae,'])

Drill into a repeating group or component:

const noMDEntriesView: MsgView = view.getView('NoMDEntries')
const firstEntry: MsgView = noMDEntriesView.getGroupInstance(1)
const expireTime: string = firstEntry.getString('ExpireTime')

const instrument: IInstrument = view.getView('Instrument').toObject()

Convert nested structures in one call:

const legGrp: IInstrumentLeg[] = view.getView('InstrmtLegGrp.NoLegs').toObject()

Dump a tokenised view of every tag in a message:

console.log(view.toString())

See src/test/ascii/view-decode.test.ts for many more examples.

FIXML over HTTP

ASCII and FIXML sessions share the same AsciiSession-style application API — the framing is the only thing that changes. A small HTTP OMS demo lives at src/sample/http/oms/.

Build an order:

public createOrder (symbol: string, side: Side, qty: number, price: number): INewOrderSingle {
  return {
    ClOrdID: `Cli${this.id++}`,
    Account: this.account,
    Side: side,
    Price: price,
    OrdType: OrdType.Limit,
    OrderQtyData: { OrderQty: qty } as IOrderQtyData,
    Instrument: {
      Symbol: symbol,
      SecurityID: '459200101',
      SecurityIDSource: SecurityIDSource.IsinNumber
    } as IInstrument,
    TimeInForce: TimeInForce.GoodTillCancelGtc
  } as INewOrderSingle
}

That renders to:

<FIXML>
  <Order ID="Cli1" Acct="TradersRUs" Side="1" Typ="2" Px="100.12" TmInForce="1">
    <Hdr SID="accept-comp" TID="init-comp" SSub="user123" TSub="INC"/>
    <Instrmt Sym="IBM" ID="459200101" Src="4"/>
    <OrdQty Qty="10000"/>
  </Order>
</FIXML>

The server receives the order and produces an execution report:

protected onApplicationMsg (msgType: string, view: MsgView): void {
  if (msgType === 'Order') {
    const order: INewOrderSingle = view.toObject()
    const fill: IExecutionReport = this.factory.fillOrder(order)
    this.send('ExecutionReport', fill)
  }
}

Reply on the wire:

<FIXML>
  <ExecRpt ID="Cli1" ExecID="exec1" ExecTyp="I" Stat="2" Side="1" Typ="2"
           Px="100.12" LastPx="100.12" LeavesQty="0" AvgPx="100.12"
           TxnTm="2018-10-07T12:16:12.584">
    <Hdr SID="accept-comp" TID="init-comp" TSub="fix"/>
    <Instrmt Sym="IBM" ID="459200101" Src="4"/>
    <OrdQty Qty="10000"/>
  </ExecRpt>
</FIXML>

Data Dictionaries

jspurefix ships definitions for FIX 4.0–4.4 and FIX 5.0 SP0/SP1/SP2 in both QuickFIX XML and FIX-repository formats, under bundled aliases such as repo44, qf44, qf50sp2. The alias map lives at data/dictionary.json.

To add a custom dialect:

  1. Drop your QuickFIX-style XML into data/.

  2. Add an alias to data/dictionary.json.

  3. Generate typed interfaces under src/types:

    npm run cmd -- --dict=repo42 --compile
  4. Reference the alias from your session description ("dictionary": "repo42").

See jspf-md-demo for a worked example.

jsfix CLI — log parsing & stats

The jsfix-cmd tool parses any FIX log given an appropriate dictionary.

Token dump for a specific message type:

npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --tokens
[0] 8 (BeginString) = FIX4.4, [1] 9 (BodyLength) = 0000135
[2] 35 (MsgType) = AD[TradeCaptureReportRequest], [3] 49 (SenderCompID) = init-comp
[4] 56 (TargetCompID) = accept-comp, [5] 34 (MsgSeqNum) = 2
...

Per-type message counts for the file:

npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats
{ "0": 1, "5": 2, "A": 2, "AD": 1, "AQ": 2, "AE": 5 }

Convert to typed objects:

npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --objects

Show the parser's view of nested structures within a message:

npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --type=AD --structures

Repeat-parse for benchmarking (--repeats=N):

npm run cmd -- --dict=repo44 --fix=data/examples/FIX.4.4/jsfix.test_client.txt --delimiter="|" --stats --repeats=20

Performance

Numbers below are illustrative — generated messages, single-threaded, parser-only (no I/O). Run them yourself with the corresponding npm run script.

| Benchmark | Script | Fields/msg | Length (chars) | I7-4770 @ 3.5 GHz | i7-12700H @ 2.3 GHz | Ryzen 9 7950X @ 4.5 GHz | | --- | --- | --- | --- | --- | --- | --- | | Heartbeat | npm run qf-bench-hb | 10 | 131 | 3.8 µs/msg | 1.9 µs/msg | 1.7 µs/msg | | Logon | npm run qf-bench-lo | 22 | 214 | 5.9 µs/msg | 2.8 µs/msg | 2.5 µs/msg | | Execution Report (large) | npm run repo44-bench-er | 646 | 6 571 | 206.2 µs/msg | 93.5 µs/msg | 72.9 µs/msg | | Security Definition † | npm run repo44-bench-sd | 52 | 557 | — | — | 5.6 µs/msg | | Trade Capture † | npm run repo44-bench-tc | 112 | 1 137 | — | — | 9.3 µs/msg |

† The SD and TC fixtures have shrunk since the older measurements were taken (SD was 229 fields / 2 466 chars, TC was 578 fields / 5 741 chars), so the prior numbers aren't comparable with the current fixture and have been dropped from the row. Re-running them on the older hardware would produce a clean third column. Ryzen measurements taken on Node 24 LTS, WSL2.

Developing on jspurefix

Clone and build:

git clone https://github.com/TimelordUK/jspurefix.git
cd jspurefix
npm install         # postinstall unpacks the FIX dictionaries
npm run build

Run the test suite (Jest, single worker, with coverage):

npm test

The full suite currently runs 535 tests across 43 suites and takes ~70 s on a modern laptop. script/build.sh (unix) and script\build.cmd (windows) wrap install + build + test if you want a one-shot bootstrap.

Try a sample end-to-end:

npm run tcp-tc      # trade-capture client + server

C# Port

This engine has been ported to C# as cspurefix, which is kept in lockstep with this codebase. If you're on .NET:

License

MIT. See LICENSE.