UNPKG

envio

Version:

A latency and sync speed optimized, developer friendly blockchain data indexer.

442 lines (429 loc) 15.2 kB
// Generated by ReScript, PLEASE EDIT WITH CARE import * as Env from "../Env.res.mjs"; import * as Ink from "./bindings/Ink.res.mjs"; import * as Ink$1 from "ink"; import * as React from "react"; import * as SyncETA from "./components/SyncETA.res.mjs"; import * as TuiData from "./components/TuiData.res.mjs"; import * as ChainMap from "../ChainMap.res.mjs"; import * as Messages from "./components/Messages.res.mjs"; import * as FetchState from "../FetchState.res.mjs"; import * as ChainFetcher from "../ChainFetcher.res.mjs"; import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js"; import InkBigText from "ink-big-text"; import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js"; import * as SourceManager from "../sources/SourceManager.res.mjs"; import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js"; import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js"; import * as JsxRuntime from "react/jsx-runtime"; import * as BufferedProgressBar from "./components/BufferedProgressBar.res.mjs"; function Tui$ChainLine(props) { let blockUnit = props.blockUnit; let poweredByHyperSync = props.poweredByHyperSync; let endBlock = props.endBlock; let startBlock = props.startBlock; let sourceBlock = props.sourceBlock; let bufferBlock = props.bufferBlock; let progressBlock = props.progressBlock; let chainId = props.chainId; let chainsWidth = Primitive_int.min(props.stdoutColumns - 2 | 0, 60); let headerWidth = props.maxChainIdLength + 10 | 0; if (progressBlock !== undefined && bufferBlock !== undefined && sourceBlock !== undefined) { let toBlock = endBlock !== undefined ? Primitive_int.min(sourceBlock, endBlock) : sourceBlock; let progressBlockStr = TuiData.formatLocaleString(progressBlock); let toBlockStr = TuiData.formatLocaleString(toBlock); let eventsStr = TuiData.formatFloatLocaleString(props.eventsProcessed); let endLabel = ` (End ` + blockUnit + `)`; let blocksText = blockUnit + `s: ` + progressBlockStr + ` / ` + toBlockStr + ( Stdlib_Option.isSome(endBlock) ? endLabel : "" ) + ` `; let eventsText = `Events: ` + eventsStr; let fitsSameLine = (blocksText.length + eventsText.length | 0) <= chainsWidth; return JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "Chain: " }), JsxRuntime.jsx(Ink$1.Text, { children: chainId, bold: true }), JsxRuntime.jsx(Ink$1.Text, { children: " " }), poweredByHyperSync ? JsxRuntime.jsx(Ink$1.Text, { children: "⚡", color: "#FFBB2F" }) : null ], width: headerWidth }), JsxRuntime.jsx(BufferedProgressBar.make, { loaded: progressBlock - startBlock | 0, buffered: bufferBlock - startBlock | 0, outOf: toBlock - startBlock | 0, barWidth: chainsWidth - headerWidth | 0, loadingColor: "#FFBB2F" }) ], width: chainsWidth, flexDirection: "row" }), JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: blocksText, color: "gray" }), fitsSameLine ? JsxRuntime.jsx(Ink$1.Text, { children: eventsText, color: "gray" }) : null ], flexDirection: "row" }), fitsSameLine ? null : JsxRuntime.jsx(Ink$1.Box, { children: JsxRuntime.jsx(Ink$1.Text, { children: eventsText.trim(), color: "gray" }), flexDirection: "row" }), JsxRuntime.jsx(Ink.Newline.make, {}) ], flexDirection: "column" }); } return JsxRuntime.jsxs(JsxRuntime.Fragment, { children: [ JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "Chain: " }), JsxRuntime.jsx(Ink$1.Text, { children: chainId, bold: true }), JsxRuntime.jsx(Ink$1.Text, { children: " " }), poweredByHyperSync ? JsxRuntime.jsx(Ink$1.Text, { children: "⚡", color: "#FFBB2F" }) : null ], width: headerWidth }), JsxRuntime.jsx(Ink$1.Text, { children: "Loading progress..." }) ], width: chainsWidth, flexDirection: "row" }), JsxRuntime.jsx(Ink.Newline.make, {}) ] }); } let ChainLine = { make: Tui$ChainLine }; function computeEps(samples) { let len = samples.length; let match = samples[0]; let match$1 = samples[len - 1 | 0]; if (match !== undefined && match$1 !== undefined && match$1.time > match.time) { return (match$1.events - match.events) / ((match$1.time - match.time) / 1000); } } function use(totalEventsProcessed, tick) { let match = React.useState(() => []); let setSamples = match[1]; React.useEffect(() => { let now = Date.now(); let cutoff = now - 60000; setSamples(prev => { let kept = prev.filter(s => s.time >= cutoff); return kept.concat([{ time: now, events: totalEventsProcessed }]); }); }, [tick]); return computeEps(match[0]); } let EventsPerSecond = { windowMs: 60000, computeEps: computeEps, use: use }; function Tui$TotalEventsProcessed(props) { let eventsPerSecond = props.eventsPerSecond; return JsxRuntime.jsxs(Ink$1.Text, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "Total Events: ", bold: true }), JsxRuntime.jsx(Ink$1.Text, { children: TuiData.formatFloatLocaleString(props.totalEventsProcessed), color: "#FFBB2F" }), eventsPerSecond !== undefined ? JsxRuntime.jsx(Ink$1.Text, { children: ` (` + TuiData.formatFloatLocaleString(Math.round(eventsPerSecond)) + ` events/sec)`, color: "gray" }) : null ] }); } let TotalEventsProcessed = { make: Tui$TotalEventsProcessed }; function Tui$App(props) { let getState = props.getState; let stdoutColumns = Ink.Hooks.useStdoutColumns(); let match = React.useState(() => 0); let setTick = match[1]; let state = getState(); React.useEffect(() => { let intervalId = setInterval(() => setTick(t => t + 1 | 0), 500); return () => { clearInterval(intervalId); }; }, [getState]); let chains = ChainMap.values(state.chainManager.chainFetchers).map(cf => { let numEventsProcessed = cf.numEventsProcessed; let latestFetchedBlockNumber = Primitive_int.max(FetchState.bufferBlockNumber(cf.fetchState), 0); let hasProcessedToEndblock = ChainFetcher.hasProcessedToEndblock(cf); let knownHeight = ChainFetcher.hasProcessedToEndblock(cf) ? Stdlib_Option.getOr(cf.fetchState.endBlock, cf.fetchState.knownHeight) : cf.fetchState.knownHeight; let firstEventBlock = cf.fetchState.firstEventBlock; let progress; if (hasProcessedToEndblock) { progress = { TAG: "Synced", _0: { firstEventBlockNumber: Stdlib_Option.getOr(firstEventBlock, 0), latestProcessedBlock: cf.committedProgressBlockNumber, numEventsProcessed: numEventsProcessed, timestampCaughtUpToHeadOrEndblock: Stdlib_Option.getOr(cf.timestampCaughtUpToHeadOrEndblock, new Date(Date.now())) } }; } else { let match = cf.timestampCaughtUpToHeadOrEndblock; progress = firstEventBlock !== undefined ? ( match !== undefined ? ({ TAG: "Synced", _0: { firstEventBlockNumber: firstEventBlock, latestProcessedBlock: cf.committedProgressBlockNumber, numEventsProcessed: numEventsProcessed, timestampCaughtUpToHeadOrEndblock: match } }) : ({ TAG: "Syncing", _0: { firstEventBlockNumber: firstEventBlock, latestProcessedBlock: cf.committedProgressBlockNumber, numEventsProcessed: numEventsProcessed } }) ) : "SearchingForEvents"; } let match$1 = state.ctx.config.ecosystem.name; let tmp; switch (match$1) { case "evm" : case "fuel" : tmp = "Block"; break; case "svm" : tmp = "Slot"; break; } return { chainId: cf.chainConfig.id.toString(), eventsProcessed: numEventsProcessed, progressBlock: cf.committedProgressBlockNumber < cf.fetchState.startBlock ? cf.fetchState.startBlock : cf.committedProgressBlockNumber, bufferBlock: latestFetchedBlockNumber, sourceBlock: cf.fetchState.knownHeight, startBlock: cf.fetchState.startBlock, endBlock: cf.fetchState.endBlock, firstEventBlockNumber: cf.fetchState.firstEventBlock, poweredByHyperSync: SourceManager.getActiveSource(cf.sourceManager).poweredByHyperSync, progress: progress, latestFetchedBlockNumber: latestFetchedBlockNumber, knownHeight: knownHeight, blockUnit: tmp, rateLimitTimeMs: SourceManager.getRateLimitTimeMs(cf.sourceManager), isRateLimited: SourceManager.isRateLimited(cf.sourceManager), rateLimitResetInMs: SourceManager.getRateLimitResetInMs(cf.sourceManager) }; }); let totalEventsProcessed = Stdlib_Array.reduce(chains, 0, (acc, chain) => acc + chain.eventsProcessed); let maxChainIdLength = Stdlib_Array.reduce(chains, 0, (acc, chain) => { let chainIdLength = chain.chainId.length; if (chainIdLength > acc) { return chainIdLength; } else { return acc; } }); let eventsPerSecond = use(totalEventsProcessed, match[0]); let maxRateLimitTimeMs = Stdlib_Array.reduce(chains, 0, (acc, chain) => Primitive_float.max(acc, chain.rateLimitTimeMs)); let maxResetInMs = Stdlib_Array.reduce(chains, 0.0, (acc, chain) => Primitive_float.max(acc, Stdlib_Option.getOr(chain.rateLimitResetInMs, 0.0))); let tmp; if (maxRateLimitTimeMs > 1000) { let rateLimitSecs = Math.round(maxRateLimitTimeMs / 1000); let activeSuffix; if (maxResetInMs > 0.0) { let resetSecs = Primitive_float.max(1.0, Math.ceil(maxResetInMs / 1000)); activeSuffix = ` (⏳ ` + TuiData.formatFloatLocaleString(resetSecs) + `s until reset)`; } else { activeSuffix = ""; } tmp = JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink.Newline.make, {}), JsxRuntime.jsx(Ink$1.Text, { children: `Backfill ` + TuiData.formatFloatLocaleString(rateLimitSecs) + `s slower due to your plan's rate limit` + activeSuffix, color: "#FF8269" }), JsxRuntime.jsxs(Ink$1.Text, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "Upgrade at ", color: "#FF8269" }), JsxRuntime.jsx(Ink$1.Text, { children: "https://envio.dev/app/api-tokens", color: "#FF8269", underline: true }), JsxRuntime.jsx(Ink$1.Text, { children: " for higher rate limits.", color: "#FF8269" }) ], color: "#FF8269" }) ], flexDirection: "column" }); } else { tmp = null; } let defaultPassword = "testing"; let match$1 = state.ctx.config.storage.clickhouse; let match$2 = Env.ClickHouse.host(); return JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(InkBigText, { text: "envio", font: chains.length > 5 ? "tiny" : "block", colors: [ "#FFBB2F", "#9860E5" ], space: false }), JsxRuntime.jsx(Ink.Newline.make, {}), chains.map((chainData, i) => JsxRuntime.jsx(Tui$ChainLine, { chainId: chainData.chainId, maxChainIdLength: maxChainIdLength, stdoutColumns: stdoutColumns, progressBlock: chainData.progressBlock, bufferBlock: chainData.bufferBlock, sourceBlock: chainData.sourceBlock, startBlock: chainData.startBlock, endBlock: chainData.endBlock, poweredByHyperSync: chainData.poweredByHyperSync, eventsProcessed: chainData.eventsProcessed, blockUnit: chainData.blockUnit }, i.toString())), JsxRuntime.jsx(Tui$TotalEventsProcessed, { totalEventsProcessed: totalEventsProcessed, eventsPerSecond: SyncETA.isIndexerFullySynced(chains) ? undefined : eventsPerSecond }), JsxRuntime.jsx(SyncETA.make, { chains: chains, indexerStartTime: state.indexerStartTime }), tmp, JsxRuntime.jsx(Ink.Newline.make, {}), JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "GraphQL: " }), JsxRuntime.jsx(Ink$1.Text, { children: Env.Hasura.url, color: "#6CBFEE", underline: true }), Env.Hasura.secret === defaultPassword ? JsxRuntime.jsx(Ink$1.Text, { children: ` (password: ` + defaultPassword + `)`, color: "gray" }) : null ], flexDirection: "row" }), state.ctx.config.isDev ? JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "Dev Console: " }), JsxRuntime.jsx(Ink$1.Text, { children: Env.envioAppUrl + `/console`, color: "#6CBFEE", underline: true }) ], flexDirection: "row" }) : null, match$1 && match$2 !== undefined ? JsxRuntime.jsxs(Ink$1.Box, { children: [ JsxRuntime.jsx(Ink$1.Text, { children: "ClickHouse: " }), JsxRuntime.jsx(Ink$1.Text, { children: match$2 + `/play`, color: "#6CBFEE", underline: true }) ], flexDirection: "row" }) : null, JsxRuntime.jsx(Messages.make, { config: state.ctx.config }) ], flexDirection: "column" }); } let App = { make: Tui$App }; function start(getState) { let match = Ink.render(undefined, JsxRuntime.jsx(Tui$App, { getState: getState })); let rerender = match.rerender; return () => rerender(JsxRuntime.jsx(Tui$App, { getState: getState })); } export { ChainLine, EventsPerSecond, TotalEventsProcessed, App, start, } /* Env Not a pure module */