envio
Version:
A latency and sync speed optimized, developer friendly blockchain data indexer.
442 lines (429 loc) • 15.2 kB
JavaScript
// 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 */