jspurefix
Version:
pure node js fix engine
577 lines • 23.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const buffer_1 = require("./buffer");
const util_1 = require("./util");
const transport_1 = require("./transport");
const dictionary_1 = require("./dictionary");
const enum_1 = require("./types/enum");
const config_1 = require("./config");
const util = require("util");
const fs = require('node-fs-extra');
const minimist = require("minimist");
const path = require("path");
const argv = minimist(process.argv.slice(2));
const buffer_helper_1 = require("./util/buffer-helper");
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["Unknown"] = 7] = "Unknown";
})(Command || (Command = {}));
class JsfixCmd {
constructor() {
this.root = path.join(__dirname, '../');
this.delimiter = buffer_1.Ascii.Soh;
this.stats = {};
this.filter = null;
this.messages = 0;
this.print = true;
}
static getCommand() {
let command = Command.Unknown;
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;
}
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;
}
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 new Promise((resolve, reject) => {
this.init().then(() => __awaiter(this, void 0, void 0, function* () {
let actioned = true;
let 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 {
yield this.benchmark(repeats);
}
catch (e) {
reject(e);
}
break;
}
case Command.Lookup: {
this.field();
break;
}
case Command.Compile: {
yield this.compile();
break;
}
case Command.Unknown:
default: {
actioned = false;
}
}
resolve(actioned);
})).catch((e) => {
reject(e);
});
});
}
firstMessage(t) {
return 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 buffer_helper_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 transport_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* () {
let buffer = new buffer_1.ElasticBuffer();
const repeats = argv.repeats || 50;
const key = enum_1.MsgTag.MsgType.toString();
const sf = this.definitions.simple.get(key);
const session = this.session;
for (let i = 0; i < repeats; ++i) {
const msgType = 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) {
return __awaiter(this, void 0, void 0, function* () {
const view = yield this.firstMessage(ft);
const summary = view.structure.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();
}
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 new Promise((accept, reject) => {
fs.mkdirp(path, (err) => {
if (err) {
reject(err);
}
else {
accept();
}
});
});
}
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 dictionary_1.MsgCompiler(definitions, compilerSettings);
yield msgCompiler.generate();
const enumCompiler = new dictionary_1.EnumCompiler(definitions, compilerSettings);
const writeFile = path.join(compilerSettings.output, './enum/all-enum.ts');
yield enumCompiler.generate(writeFile);
});
}
compile() {
return __awaiter(this, void 0, void 0, function* () {
let output = argv.output;
const dp = util_1.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';
session = this.norm(session);
this.sessionDescription = require(session);
let dict;
if (argv.dict) {
dict = argv.dict;
}
else {
dict = this.sessionDescription.application.dictionary;
}
this.definitions = yield util_1.getDefinitions(dict);
const definitions = this.definitions;
if (argv.delimiter) {
this.delimiter = buffer_1.Ascii.firstChar(argv.delimiter);
}
this.jsonHelper = new util_1.JsonHelper(definitions);
if (argv.session) {
const description = this.sessionDescription;
const config = new config_1.JsFixConfig(new transport_1.SessionMsgFactory(description), definitions, description, this.delimiter);
this.session = new transport_1.AsciiMsgTransmitter(config);
}
});
}
dispatch(ft) {
return __awaiter(this, void 0, void 0, function* () {
if (argv.type != null) {
this.filter = argv.type.toString();
}
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 (msgType !== filter) {
return;
}
}
++this.messages;
this.onMsg(msgType, m);
});
}
onMsg(msgType, m) {
const mode = JsfixCmd.getPrintMode();
const print = this.print;
const stats = this.stats;
switch (mode) {
case PrintMode.Stats: {
if (!stats[msgType]) {
stats[msgType] = 1;
}
else {
stats[msgType] = stats[msgType] + 1;
}
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.name}] = ${JSON.stringify(asObject, null, 4)}`);
console.log();
}
break;
}
case PrintMode.Structure: {
const summary = m.structure.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 = new config_1.JsFixConfig(null, this.definitions, this.sessionDescription, this.delimiter);
const ft = new transport_1.MsgTransport(1, config, new transport_1.FileDuplex(fix));
yield this.dispatch(ft);
});
}
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;
}
return new Promise((accept, reject) => {
const fix = this.norm(argv.fix);
const fs = require('fs');
const definitions = this.definitions;
const delimiter = this.delimiter;
fs.readFile(fix, 'utf8', (err, contents) => __awaiter(this, void 0, void 0, function* () {
if (err) {
reject(err);
}
const startsAt = new Date();
let i = 0;
const asciiParser = new buffer_1.AsciiParser(definitions, new transport_1.StringDuplex(contents.repeat(repeats)).readable, delimiter);
asciiParser.on('msg', (msgType, v) => {
++i;
if (i === repeats) {
const elapsed = new Date().getTime() - startsAt.getTime();
console.log(contents);
console.log(v.toString());
console.log(`[${msgType}]: repeats = ${repeats}, fields = ${v.structure.tags.nextTagPos}, length = ${contents.length} chars, elapsed ms ${elapsed}, ${(elapsed / repeats) * 1000} micros per msg`);
accept();
}
});
}));
});
});
}
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('token format i.e. [602] 687 (LegQty) = 33589');
console.log('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('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('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('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('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('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('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('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('jsfix-cmd --dict=data/fix_repo/FIX.4.4/Base --field=MsgType');
console.log('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('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('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('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('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.log(`error ${e.message}`);
});
}
//# sourceMappingURL=jsfix-cmd.js.map