UNPKG

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
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