tardis-machine
Version:
Locally runnable server with built-in data caching, providing both tick-level historical and consolidated real-time cryptocurrency market data via HTTP and WebSocket APIs
117 lines • 4.45 kB
JavaScript
import findMyWay from 'find-my-way';
import http from 'node:http';
import { createRequire } from 'module';
import { clearCache, init } from 'tardis-dev';
import { App, DISABLED } from 'uWebSockets.js';
import { healthCheck, replayHttp, replayNormalizedHttp } from "./http/index.js";
import { replayNormalizedWS, replayWS, streamNormalizedWS } from "./ws/index.js";
import { debug } from "./debug.js";
const require = createRequire(import.meta.url);
const packageJson = require('../package.json');
export class TardisMachine {
options;
_httpServer;
_wsServer;
_eventLoopTimerId = undefined;
constructor(options) {
this.options = options;
init({
apiKey: options.apiKey,
cacheDir: options.cacheDir,
_userAgent: `tardis-machine/${packageJson.version} (+https://github.com/tardis-dev/tardis-machine)`
});
const router = findMyWay({ ignoreTrailingSlash: true });
this._httpServer = http.createServer((req, res) => {
router.lookup(req, res);
});
// set timeout to 0 meaning infinite http timout - streaming may take some time expecially for longer date ranges
this._httpServer.timeout = 0;
router.on('GET', '/replay', replayHttp);
router.on('GET', '/replay-normalized', replayNormalizedHttp);
router.on('GET', '/health-check', healthCheck);
const wsRoutes = {
'/ws-replay': replayWS,
'/ws-replay-normalized': replayNormalizedWS,
'/ws-stream-normalized': streamNormalizedWS
};
this._wsServer = App().ws('/*', {
compression: DISABLED,
maxPayloadLength: 512 * 1024,
idleTimeout: 60,
maxBackpressure: 5 * 1024 * 1024,
closeOnBackpressureLimit: true,
upgrade: (res, req, context) => {
res.upgrade({ req }, req.getHeader('sec-websocket-key'), req.getHeader('sec-websocket-protocol'), req.getHeader('sec-websocket-extensions'), context);
},
open: (ws) => {
const path = ws.req.getUrl().toLocaleLowerCase();
ws.closed = false;
const matchingRoute = wsRoutes[path];
if (matchingRoute !== undefined) {
matchingRoute(ws, ws.req);
}
else {
ws.end(1008);
}
},
message: (ws, message) => {
if (ws.onmessage !== undefined) {
ws.onmessage(message);
}
},
close: (ws) => {
ws.closed = true;
if (ws.onclose !== undefined) {
ws.onclose();
}
}
});
}
async start(port) {
let start = process.hrtime();
const interval = 500;
// based on https://github.com/tj/node-blocked/blob/master/index.js
this._eventLoopTimerId = setInterval(() => {
const delta = process.hrtime(start);
const nanosec = delta[0] * 1e9 + delta[1];
const ms = nanosec / 1e6;
const n = ms - interval;
if (n > 2000) {
debug('Tardis-machine server event loop blocked for %d ms.', Math.round(n));
}
start = process.hrtime();
}, interval);
if (this.options.clearCache) {
await clearCache();
}
await new Promise((resolve, reject) => {
try {
this._httpServer.on('error', reject);
this._httpServer.listen(port, () => {
this._wsServer.listen(port + 1, (listenSocket) => {
if (listenSocket) {
resolve();
}
else {
reject(new Error('ws server did not start'));
}
});
});
}
catch (e) {
reject(e);
}
});
}
async stop() {
await new Promise((resolve, reject) => {
this._httpServer.close((err) => {
err ? reject(err) : resolve();
});
});
if (this._eventLoopTimerId !== undefined) {
clearInterval(this._eventLoopTimerId);
}
}
}
//# sourceMappingURL=tardismachine.js.map