jspurefix
Version:
pure node js fix engine
779 lines • 30 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsfixCmd = void 0;
require("reflect-metadata");
const buffer_1 = require("./buffer");
const ascii_1 = require("./buffer/ascii");
const util_1 = require("./util");
const transport_1 = require("./transport");
const types_1 = require("./types");
const util = require("util");
const minimist = require("minimist");
const path = require("path");
const factory_1 = require("./transport/factory");
const compiler_1 = require("./dictionary/compiler");
const runtime_1 = require("./runtime");
const di_tokens_1 = require("./runtime/di-tokens");
const minimist_options_1 = require("minimist-options");
const quick_fix_xml_file_builder_1 = require("./dictionary/parser/quickfix/quick-fix-xml-file-builder");
const fs = require('node-fs-extra');
const options = (0, minimist_options_1.default)({
dict: {
type: 'string',
alias: 'd',
default: 'data/FIX44.xml'
},
type: {
type: 'string-array',
alias: 't'
},
fix: {
type: 'string',
alias: 'f',
default: 'data/FIX44.xml'
},
session: {
type: 'string',
alias: 's'
},
delimiter: {
type: 'string',
alias: 'l',
default: '|'
},
help: {
type: 'boolean',
alias: ['h'],
default: false
},
unit: {
type: 'boolean',
alias: ['u'],
default: false
},
generate: {
type: 'boolean',
alias: ['g'],
default: false
},
stats: {
type: 'boolean',
alias: ['st'],
default: false
},
tokens: {
type: 'boolean',
alias: ['t'],
default: false
},
objects: {
type: 'boolean',
alias: ['o'],
default: true
},
structures: {
type: 'boolean',
alias: ['r'],
default: false
},
script: {
type: 'boolean',
alias: ['i'],
default: false
},
compile: {
type: 'boolean',
alias: ['c'],
default: false
},
groups: {
type: 'boolean',
alias: ['g'],
default: true
},
density: {
type: 'number',
alias: 'd',
default: 0.8
},
repeats: {
type: 'number',
alias: 'r',
default: 1
},
arr: {
type: 'array',
alias: 'a',
default: []
}
});
const argv = minimist(process.argv.slice(2), options);
var PrintMode;
(function (PrintMode) {
PrintMode[PrintMode["Structure"] = 1] = "Structure";
PrintMode[PrintMode["Object"] = 2] = "Object";
PrintMode[PrintMode["Verbose"] = 3] = "Verbose";
PrintMode[PrintMode["Stats"] = 4] = "Stats";
PrintMode[PrintMode["Token"] = 5] = "Token";
PrintMode[PrintMode["Encoded"] = 6] = "Encoded";
})(PrintMode || (PrintMode = {}));
var Command;
(function (Command) {
Command[Command["Generate"] = 1] = "Generate";
Command[Command["Replay"] = 2] = "Replay";
Command[Command["Lookup"] = 3] = "Lookup";
Command[Command["Encode"] = 4] = "Encode";
Command[Command["Benchmark"] = 5] = "Benchmark";
Command[Command["Compile"] = 6] = "Compile";
Command[Command["Trim"] = 7] = "Trim";
Command[Command["Unknown"] = 8] = "Unknown";
})(Command || (Command = {}));
class ParseSummary {
constructor(content, view, msg_type, iterations, elapsed_ms, fields) {
this.content = content;
this.view = view;
this.msg_type = msg_type;
this.iterations = iterations;
this.elapsed_ms = elapsed_ms;
this.fields = fields;
this.content_length = this.content.length;
this.micros_per_msg = (this.elapsed_ms / this.iterations) * 1000;
this.chars_per_second = Math.round(this.content_length * this.iterations / this.elapsed_ms * 1000);
this.fields_per_second = Math.round(this.fields * this.iterations / this.elapsed_ms * 1000);
}
}
class JsfixCmd {
constructor() {
this.root = path.join(__dirname, '../');
this.delimiter = ascii_1.AsciiChars.Soh;
this.stats = {};
this.filter = new Map();
this.messages = 0;
this.print = true;
}
static getCommand() {
let command = Command.Unknown;
if (argv.trim) {
command = Command.Trim;
}
else if (argv.compile) {
command = Command.Compile;
}
else if (argv.generate) {
command = Command.Generate;
}
else if (argv.fix) {
command = argv.benchmark ? Command.Benchmark : Command.Replay;
}
else if (argv.field) {
command = Command.Lookup;
}
else if (argv.json) {
command = Command.Encode;
}
else if (argv.msg) {
command = Command.Lookup;
}
return command;
}
static getPrintMode() {
let mode = PrintMode.Stats;
if (argv.tokens) {
mode = PrintMode.Token;
}
else if (argv.stats) {
mode = PrintMode.Stats;
}
else if (argv.objects) {
mode = PrintMode.Object;
}
else if (argv.verbose) {
mode = PrintMode.Verbose;
}
else if (argv.structures) {
mode = PrintMode.Structure;
}
else if (argv.encoded) {
mode = PrintMode.Encoded;
}
else if (argv.trim) {
mode = PrintMode.Object;
}
return mode;
}
static writeFile(name, api) {
return __awaiter(this, void 0, void 0, function* () {
const writer = util.promisify(fs.writeFile);
yield writer(name, api, { encoding: 'utf8' }).catch((e) => {
throw e;
});
});
}
exec() {
return __awaiter(this, void 0, void 0, function* () {
return yield new Promise((resolve, reject) => {
this.init().then(() => __awaiter(this, void 0, void 0, function* () {
let actioned = true;
const command = JsfixCmd.getCommand();
switch (command) {
case Command.Generate: {
yield this.generate();
break;
}
case Command.Encode: {
this.encode();
break;
}
case Command.Replay: {
const repeats = !isNaN(argv.repeats) ? argv.repeats : 1;
try {
for (let i = 0; i < repeats; ++i) {
yield this.replay();
}
}
catch (e) {
reject(e);
}
break;
}
case Command.Benchmark: {
const repeats = !isNaN(argv.repeats) ? argv.repeats : 10000;
try {
const summary = yield this.benchmark(repeats);
console.log(JSON.stringify(summary, null, 4));
}
catch (e) {
reject(e);
}
break;
}
case Command.Lookup: {
if (argv.field) {
this.field();
}
else {
this.msg();
}
break;
}
case Command.Compile: {
yield this.compile();
break;
}
case Command.Trim: {
const xml = this.trim();
console.log(xml);
break;
}
case Command.Unknown:
default: {
actioned = false;
}
}
resolve(actioned);
})).catch((e) => {
reject(e);
});
});
});
}
firstMessage(t) {
return __awaiter(this, void 0, void 0, function* () {
return yield new Promise((resolve, reject) => {
t.receiver.on('msg', (msgType, msgView) => {
resolve(msgView.clone());
});
t.receiver.on('error', (e) => {
reject(e);
});
});
});
}
generate() {
return __awaiter(this, void 0, void 0, function* () {
const lipPath = path.join(this.root, 'data/examples/lipsum.txt');
const words = yield (0, util_1.getWords)(lipPath);
const generator = new util_1.MessageGenerator(words, this.definitions);
let density = 1;
if (argv.density) {
density = parseFloat(argv.density);
}
if (isNaN(density)) {
console.log('density must be numeric in range > 0 density <= 1.0');
return;
}
if (argv.script) {
yield this.script(generator, density);
}
else {
yield this.single(generator, density);
}
});
}
single(generator, density) {
return __awaiter(this, void 0, void 0, function* () {
if (!argv.type) {
console.log('specify type to generate e.g. --type = AE');
return;
}
const msgType = `${argv.type}`;
let makeGroups = true;
if (argv.groups) {
makeGroups = argv.groups === 'true';
}
const obj = generator.generate(msgType, density, makeGroups);
console.log(JSON.stringify(obj, null, 4));
const fix = this.encodeObject(msgType, obj);
const ft = new factory_1.MsgTransport(1, this.session.config, new transport_1.StringDuplex(fix));
if (argv.unit) {
yield this.unitTest(fix, obj, ft);
}
else {
this.subscribe(ft);
}
});
}
script(generator, density) {
return __awaiter(this, void 0, void 0, function* () {
const buffer = new buffer_1.ElasticBuffer();
const repeats = argv.repeats || 50;
const key = types_1.MsgTag.MsgType.toString();
const sf = this.definitions.simple.get(key);
const session = this.session;
for (let i = 0; i < repeats; ++i) {
const msgType = sf ? util_1.MessageGenerator.getRandomEnum(sf).toString() : '';
console.log(`i = ${i} ${msgType}`);
const obj = generator.generate(msgType, density);
session.encodeMessage(msgType, obj);
buffer.writeBuffer(session.buffer.slice());
buffer.writeString(require('os').EOL);
}
yield JsfixCmd.writeFile('./fix.txt', buffer.slice().toString('utf8'));
});
}
unitTest(fix, obj, ft) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const view = yield this.firstMessage(ft);
const summary = (_a = view === null || view === void 0 ? void 0 : view.structure) === null || _a === void 0 ? void 0 : _a.summary();
yield JsfixCmd.writeFile('./fix.txt', fix);
yield JsfixCmd.writeFile('./object.json', JSON.stringify(obj, null, 4));
yield JsfixCmd.writeFile('./token.txt', view.toString());
yield JsfixCmd.writeFile('./structure.json', JSON.stringify(summary, null, 4));
});
}
encodeObject(msgType, object) {
const session = this.session;
session.encodeMessage(msgType, object);
return session.buffer.toString();
}
msg() {
const definitions = this.definitions;
const m = definitions.message.get(argv.msg);
if (m) {
console.log(m.toString());
}
}
field() {
let sf;
const tag = parseInt(argv.field, 10);
const definitions = this.definitions;
if (!isNaN(tag)) {
sf = definitions.tagToSimple[tag];
}
else {
sf = definitions.simple.get(argv.field);
}
if (sf) {
console.log(sf.toString());
}
}
ensureExists(path) {
return __awaiter(this, void 0, void 0, function* () {
return yield new Promise((resolve, reject) => {
fs.mkdirp(path, (err) => {
if (err) {
reject(err);
}
else {
resolve(true);
}
});
});
});
}
compileDefinitions(outputPath) {
return __awaiter(this, void 0, void 0, function* () {
yield this.ensureExists(path.join(outputPath, 'set'));
yield this.ensureExists(path.join(outputPath, 'enum'));
const definitions = this.definitions;
const compilerSettings = require('../data/compiler.json');
compilerSettings.output = outputPath;
const msgCompiler = new compiler_1.MsgCompiler(definitions, compilerSettings);
yield msgCompiler.generate();
const enumCompiler = new compiler_1.EnumCompiler(definitions, compilerSettings);
const writeFile = path.join(compilerSettings.output, './enum/all-enum.ts');
yield enumCompiler.generate(writeFile);
});
}
trim() {
var _a, _b;
this.setFilter();
const types = this.filter.size > 0 ? Array.from(this.filter.keys()) : Array.from((_b = (_a = this.definitions.simple.get('MsgType')) === null || _a === void 0 ? void 0 : _a.enums.keys()) !== null && _b !== void 0 ? _b : []);
const qfb = new quick_fix_xml_file_builder_1.QuickFixXmlFileBuilder(this.definitions);
qfb.write(types);
return qfb.elasticBuffer.toString();
}
compile() {
return __awaiter(this, void 0, void 0, function* () {
let output = argv.output;
const dp = new util_1.DefinitionFactory().getDictPath(argv.dict);
if (dp) {
output = dp.output;
}
output = path.join(this.root, output);
yield this.compileDefinitions(output);
});
}
init() {
return __awaiter(this, void 0, void 0, function* () {
let session = argv.session || 'data/session/test-initiator.json';
this.sys = new runtime_1.SessionContainer();
this.sys.registerGlobal('error');
session = this.norm(session);
this.sessionDescription = require(session);
const container = yield this.sys.makeSystem(this.sessionDescription);
this.config = container.resolve(di_tokens_1.DITokens.IJsFixConfig);
this.definitions = this.config.definitions;
let dict;
if (argv.dict) {
dict = argv.dict;
const df = yield new util_1.DefinitionFactory().getDefinitions(dict);
this.config.definitions = df;
this.definitions = df;
}
const definitions = this.definitions;
if (argv.delimiter) {
this.delimiter = ascii_1.AsciiChars.firstChar(argv.delimiter);
this.config.delimiter = this.delimiter;
}
this.jsonHelper = new util_1.JsonHelper(definitions);
if (argv.session) {
this.session = container.resolve(di_tokens_1.DITokens.MsgTransmitter);
}
});
}
setFilter() {
const types = [];
if (argv.type != null) {
if (Array.isArray(argv.type)) {
argv.type.forEach((mt) => {
types.push(mt);
});
}
else {
argv.type.split(',').forEach((mt) => {
types.push(mt);
});
}
types.forEach((mt) => {
this.filter.set(mt, true);
});
}
}
dispatch(ft) {
return __awaiter(this, void 0, void 0, function* () {
this.setFilter();
let time = false;
if (argv.time || argv.stats) {
this.print = false;
time = true;
}
this.subscribe(ft);
const startsAt = new Date();
yield ft.wait();
const elapsed = new Date().getTime() - startsAt.getTime();
if (time) {
console.log(`messages ${this.messages} elapsed ms ${elapsed}`);
}
if (argv.stats) {
console.log(JSON.stringify(this.stats, null, 4));
}
});
}
subscribe(ft) {
this.messages = 0;
this.stats = {};
const filter = this.filter;
ft.receiver.on('msg', (msgType, m) => {
if (filter) {
if (filter.has(msgType)) {
return;
}
}
++this.messages;
this.onMsg(msgType, m);
});
}
onMsg(msgType, m) {
var _a;
const mode = JsfixCmd.getPrintMode();
const print = this.print;
const stats = this.stats;
switch (mode) {
case PrintMode.Stats: {
let i = 0;
if (!stats[msgType]) {
i = 1;
}
else {
i = stats[msgType] + 1;
}
stats[msgType] = i;
break;
}
case PrintMode.Verbose: {
const verbose = m.toVerbose();
if (verbose) {
console.log(verbose);
}
break;
}
case PrintMode.Object: {
const asObject = m.toObject();
if (print) {
const def = this.definitions.message.get(msgType);
console.log(`${msgType} [${def === null || def === void 0 ? void 0 : def.name}] = ${JSON.stringify(asObject, null, 4)}`);
console.log();
}
break;
}
case PrintMode.Structure: {
const summary = (_a = m === null || m === void 0 ? void 0 : m.structure) === null || _a === void 0 ? void 0 : _a.summary();
if (print) {
console.log(JSON.stringify(summary, null, 4));
}
break;
}
case PrintMode.Token: {
const tokens = m.toString();
if (print) {
console.log(tokens);
}
break;
}
case PrintMode.Encoded: {
const fix = this.encodeObject(msgType, m.toObject());
console.log(fix);
break;
}
default:
throw new Error(`unknown mode ${mode}`);
}
}
replay() {
return __awaiter(this, void 0, void 0, function* () {
if (!argv.fix) {
console.log('provide a path to fix file i.e. --fix=data/examples/execution-report/fix.txt');
return;
}
const fix = this.norm(argv.fix);
const config = this.config;
const ft = new factory_1.MsgTransport(1, config, new transport_1.FileDuplex(fix));
yield this.dispatch(ft);
});
}
promisedRead(f) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
fs.readFile(f, 'utf8', (err, contents) => __awaiter(this, void 0, void 0, function* () {
if (err) {
reject(err);
}
resolve(contents);
}));
});
});
}
benchParse(contents, iterations) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const toParse = new transport_1.StringDuplex(contents.repeat(iterations));
const startsAt = new Date();
let i = 0;
const config = this.config;
const buffer = config.sessionContainer.resolve(di_tokens_1.DITokens.ParseBuffer);
const asciiParser = new ascii_1.AsciiParser(config, toParse.readable, buffer);
asciiParser.on('msg', (msgType, v) => {
var _a, _b;
++i;
if (i === iterations) {
const elapsed = new Date().getTime() - startsAt.getTime();
const fields = (_b = (_a = v === null || v === void 0 ? void 0 : v.structure) === null || _a === void 0 ? void 0 : _a.tags.nextTagPos) !== null && _b !== void 0 ? _b : 0;
const summary = new ParseSummary(contents, v.toString(), msgType, iterations, elapsed, fields);
resolve(summary);
}
});
asciiParser.on('error', e => {
reject(e);
});
});
});
}
benchmark(repeats) {
return __awaiter(this, void 0, void 0, function* () {
if (!argv.fix) {
console.log('provide a path to fix file i.e. --fix=data/examples/execution-report/fix.txt');
return null;
}
return yield new Promise((resolve, reject) => {
const fix = this.norm(argv.fix);
this.promisedRead(fix)
.then(contents => {
this.benchParse(contents, repeats)
.then((a) => {
resolve(a);
})
.catch(e => { reject(e); });
}).catch(e => {
reject(e);
});
});
});
}
encode() {
const session = this.session;
if (!session) {
console.log('provide a session json file e.g. --session=data/session/test-initiator.json');
return;
}
if (!argv.type) {
console.log('provide a message type e.g. --type=8');
return;
}
if (!argv.json) {
console.log('provide a json representation e.g. data/examples/execution-report/object.json');
return;
}
const ts = argv.type.toString();
const msg = this.jsonHelper.fromJson(path.join(this.root, argv.json), ts);
session.encodeMessage(ts, msg);
const fix = session.buffer.toString();
console.log(fix);
}
norm(p) {
let f = p;
if (!path.isAbsolute(p)) {
f = path.join(this.root, f);
}
return f;
}
}
exports.JsfixCmd = JsfixCmd;
function showHelp() {
console.log('this help page');
console.log('npm run cmd');
console.log('npm run cmd -- --help');
console.log();
console.log('print to console a trim quickfix format xml only including given message types');
console.log('node dist/jsfix-cmd --dict=qf44 --trim --type="0,1,2,3,4,5,AE"');
console.log('token format i.e. [602] 687 (LegQty) = 33589');
console.log('node dist/jsfix-cmd --dict=data/FIX44.xml --fix=data/examples/quickfix/FIX.4.4/execution-report/fix.txt --delimiter="|" --tokens');
console.log();
console.log('token format use fix repo dictionary');
console.log('node dist/jsfix-cmd --dict=data/fix_repo/FIX.4.4/Base --fix=data/examples/quickfix/FIX.4.4/execution-report/fix.txt' +
' --delimiter="|" --tokens');
console.log();
console.log('structure format i.e. show locations of components etc.');
console.log('node dist/jsfix-cmd --dict=data/FIX44.xml --fix=data/examples/FIX.4.4/quickfix/execution-report/fix.txt' +
' --delimiter="|" --tokens --structures');
console.log();
console.log('full JS object in JSON format.');
console.log('node dist/jsfix-cmd --dict=data/FIX44.xml --fix=data/examples/FIX.4.4/quickfix/execution-report/fix.txt' +
' --delimiter="|" --tokens --objects');
console.log();
console.log('full JS object in JSON format - filter only type messages.');
console.log('node dist/jsfix-cmd --dict=data/FIX44.xml --fix=data/examples/FIX.4.4/quickfix/execution-report/fix.txt' +
' --delimiter="|" --tokens --type=8 --objects');
console.log();
console.log('timing stats and message counts. Structured parsing of all messages.');
console.log('node dist/jsfix-cmd --dict=data/FIX44.xml --fix=data/examples/FIX.4.4/quickfix/execution-report/fix.txt --stats');
console.log();
console.log('encode a json object to fix format');
console.log('node dist/jsfix-cmd --json=data/examples/FIX.4.4/quickfix/execution-report/object.json' +
' --session=data/session.json --type=8 --delimiter="|"');
console.log();
console.log('display field definition');
console.log('node dist/jsfix-cmd --dict=data/FIX44.xml --field=MsgType|35');
console.log();
console.log('display field use fix repo dictionary e.g. 271 MDEntrySize QTY Quantity or volume represented by the Market Data Entry.');
console.log('node dist/jsfix-cmd --dict=data/fix_repo/FIX.4.4/Base --field=MsgType');
console.log('node dist/jsfix-cmd --dict=data/fix_repo/FIX.4.4/Base --field=35');
console.log();
console.log('script to describe field in repository version 4.4');
console.log('npm run repo44 -- --field=8');
console.log();
console.log('script to describe field in fixml');
console.log('npm run fixml -- --field=50');
console.log();
console.log('generate unit test set of files - i.e. randomly generate an object, encode to fix. density 1 is all fields');
console.log('node dist/jsfix-cmd --generate --type=AE --density=0.8 --unit --delimiter="|" --session=data/session/test-initiator.json');
console.log('npm run repo44-unit -- --type=AE');
console.log('test script with no repeat groups');
console.log('npm run repo44-unit -- --type=AE --groups=false');
console.log();
console.log('generate a fix log of randomly generated but syntactically correct messages');
console.log('node dist/jsfix-cmd --generate --density=0.8 --repeats=50 --script --delimiter="|" --session=data/session/test-initiator.json');
console.log('npm run repo44-script');
console.log('parse above generated script');
console.log('npm run repo44-repscr');
console.log();
console.log('replay example repo fix file of 50 messages.');
console.log('node dist/jsfix-cmd --session=data/session/test-initiator.json --fix=data/examples/FIX.4.4/fix.txt --delimiter="|" --stats');
console.log('npm run repo44-replay -- --stats');
console.log('npm run repo44-replay -- --objects');
console.log('npm run repo44-replay -- --tokens');
console.log('npm run repo44-replay -- --structures');
console.log();
console.log('benchmark parse a message');
console.log('node dist/jsfix-cmd --delimiter="|" --session=data/session/test-initiator.json --fix=data/examples/FIX.4.4/repo/trade-capture-no-groups/fix.txt --benchmark');
console.log('npm run repo44-bench -- --fix=data/examples/FIX.4.4/repo/trade-capture-no-groups/fix.txt');
console.log();
console.log('compile typescript interfaces - i.e. outputs to src/types/FIX4.4 - requires set and enum sub folders');
console.log('npm run cmd -- --dict=repo40 --compile');
console.log('npm run cmd -- --dict=repo41 --compile');
console.log('npm run cmd -- --dict=repo42 --compile');
console.log('npm run cmd -- --dict=repo43 --compile');
console.log('npm run cmd -- --dict=repo44 --compile');
console.log('npm run cmd -- --dict=repo50 --compile');
console.log('npm run cmd -- --dict=repo50sp1 --compile');
console.log('npm run cmd -- --dict=repo50sp2 --compile');
console.log('npm run cmd -- --dict=repofixml --compile');
console.log('npm run cmd -- --dict=qf44 --compile');
console.log('npm run cmd -- --dict=data/handmade.xml --compile --output=src/types/handmade');
console.log();
}
const help = argv.h || argv.help;
if (help) {
showHelp();
}
else {
const cmd = new JsfixCmd();
cmd.exec().then((res) => {
if (!res) {
showHelp();
}
}).catch((e) => {
console.error(e);
});
}
//# sourceMappingURL=jsfix-cmd.js.map