envio 3.0.0-alpha.3 → 3.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/evm.schema.json +0 -1
- package/index.d.ts +333 -2
- package/index.js +4 -0
- package/package.json +13 -6
- package/rescript.json +4 -1
- package/src/ChainFetcher.res +25 -1
- package/src/ChainFetcher.res.mjs +19 -1
- package/src/Config.res +212 -19
- package/src/Config.res.mjs +228 -29
- package/src/{Indexer.res → Ctx.res} +1 -1
- package/src/Ecosystem.res +2 -2
- package/src/Ecosystem.res.mjs +1 -1
- package/src/Envio.gen.ts +1 -1
- package/src/Envio.res +1 -1
- package/src/EventProcessing.res +18 -18
- package/src/EventProcessing.res.mjs +14 -14
- package/src/GlobalState.res +29 -35
- package/src/GlobalState.res.mjs +47 -47
- package/src/GlobalStateManager.res +68 -0
- package/src/GlobalStateManager.res.mjs +75 -0
- package/src/GlobalStateManager.resi +7 -0
- package/src/Internal.res +41 -1
- package/src/LogSelection.res +33 -27
- package/src/LogSelection.res.mjs +6 -0
- package/src/Main.res +342 -0
- package/src/Main.res.mjs +289 -0
- package/src/PgStorage.gen.ts +10 -0
- package/src/PgStorage.res +24 -2
- package/src/PgStorage.res.d.mts +5 -0
- package/src/PgStorage.res.mjs +22 -1
- package/src/Types.ts +1 -1
- package/src/UserContext.res +0 -1
- package/src/UserContext.res.mjs +0 -2
- package/src/Utils.res +28 -0
- package/src/Utils.res.mjs +18 -0
- package/src/bindings/ClickHouse.res +31 -1
- package/src/bindings/ClickHouse.res.mjs +27 -1
- package/src/bindings/Ethers.res +27 -67
- package/src/bindings/Ethers.res.mjs +18 -70
- package/src/bindings/Postgres.gen.ts +8 -0
- package/src/bindings/Postgres.res +3 -0
- package/src/bindings/Postgres.res.d.mts +5 -0
- package/src/bindings/RescriptMocha.res +123 -0
- package/src/bindings/RescriptMocha.res.mjs +18 -0
- package/src/bindings/Yargs.res +8 -0
- package/src/bindings/Yargs.res.mjs +2 -0
- package/src/sources/FuelSDK.res +4 -3
- package/src/sources/HyperSyncHeightStream.res +28 -110
- package/src/sources/HyperSyncHeightStream.res.mjs +30 -63
- package/src/sources/HyperSyncSource.res +11 -13
- package/src/sources/HyperSyncSource.res.mjs +20 -20
- package/src/sources/Rpc.res +43 -0
- package/src/sources/Rpc.res.mjs +31 -0
- package/src/sources/RpcSource.res +9 -4
- package/src/sources/RpcSource.res.mjs +9 -4
- package/src/sources/Source.res +1 -0
- package/src/sources/SourceManager.res +164 -81
- package/src/sources/SourceManager.res.mjs +146 -83
- package/src/sources/{Solana.res → Svm.res} +4 -4
- package/src/sources/{Solana.res.mjs → Svm.res.mjs} +4 -4
- package/src/tui/Tui.res +266 -0
- package/src/tui/Tui.res.mjs +342 -0
- package/src/tui/bindings/Ink.res +376 -0
- package/src/tui/bindings/Ink.res.mjs +75 -0
- package/src/tui/bindings/Style.res +123 -0
- package/src/tui/bindings/Style.res.mjs +2 -0
- package/src/tui/components/BufferedProgressBar.res +40 -0
- package/src/tui/components/BufferedProgressBar.res.mjs +57 -0
- package/src/tui/components/CustomHooks.res +114 -0
- package/src/tui/components/CustomHooks.res.mjs +162 -0
- package/src/tui/components/Messages.res +41 -0
- package/src/tui/components/Messages.res.mjs +75 -0
- package/src/tui/components/SyncETA.res +193 -0
- package/src/tui/components/SyncETA.res.mjs +269 -0
- package/src/tui/components/TuiData.res +46 -0
- package/src/tui/components/TuiData.res.mjs +29 -0
- package/src/bindings/Ethers.gen.ts +0 -14
- /package/src/{Indexer.res.mjs → Ctx.res.mjs} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
open Belt
|
|
2
|
+
module InitApi = {
|
|
3
|
+
type ecosystem = | @as("evm") Evm | @as("fuel") Fuel | @as("svm") Svm
|
|
4
|
+
type body = {
|
|
5
|
+
envioVersion: string,
|
|
6
|
+
envioApiToken: option<string>,
|
|
7
|
+
ecosystem: ecosystem,
|
|
8
|
+
hyperSyncNetworks: array<int>,
|
|
9
|
+
rpcNetworks: array<int>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let bodySchema = S.object(s => {
|
|
13
|
+
envioVersion: s.field("envioVersion", S.string),
|
|
14
|
+
envioApiToken: s.field("envioApiToken", S.option(S.string)),
|
|
15
|
+
ecosystem: s.field("ecosystem", S.enum([Evm, Fuel, Svm])),
|
|
16
|
+
hyperSyncNetworks: s.field("hyperSyncNetworks", S.array(S.int)),
|
|
17
|
+
rpcNetworks: s.field("rpcNetworks", S.array(S.int)),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
let makeBody = (~envioVersion, ~envioApiToken, ~config: Config.t) => {
|
|
21
|
+
let hyperSyncNetworks = []
|
|
22
|
+
let rpcNetworks = []
|
|
23
|
+
config.chainMap
|
|
24
|
+
->ChainMap.values
|
|
25
|
+
->Array.forEach(({sources, id}) => {
|
|
26
|
+
switch sources->Js.Array2.some(s => s.poweredByHyperSync) {
|
|
27
|
+
| true => hyperSyncNetworks
|
|
28
|
+
| false => rpcNetworks
|
|
29
|
+
}
|
|
30
|
+
->Js.Array2.push(id)
|
|
31
|
+
->ignore
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
envioVersion,
|
|
36
|
+
envioApiToken,
|
|
37
|
+
ecosystem: (config.ecosystem.name :> ecosystem),
|
|
38
|
+
hyperSyncNetworks,
|
|
39
|
+
rpcNetworks,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type messageColor =
|
|
44
|
+
| @as("primary") Primary
|
|
45
|
+
| @as("secondary") Secondary
|
|
46
|
+
| @as("info") Info
|
|
47
|
+
| @as("danger") Danger
|
|
48
|
+
| @as("success") Success
|
|
49
|
+
| @as("white") White
|
|
50
|
+
| @as("gray") Gray
|
|
51
|
+
|
|
52
|
+
let toTheme = (color: messageColor): Style.chalkTheme =>
|
|
53
|
+
switch color {
|
|
54
|
+
| Primary => Primary
|
|
55
|
+
| Secondary => Secondary
|
|
56
|
+
| Info => Info
|
|
57
|
+
| Danger => Danger
|
|
58
|
+
| Success => Success
|
|
59
|
+
| White => White
|
|
60
|
+
| Gray => Gray
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type message = {
|
|
64
|
+
color: messageColor,
|
|
65
|
+
content: string,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let messageSchema = S.object(s => {
|
|
69
|
+
color: s.field("color", S.enum([Primary, Secondary, Info, Danger, Success, White, Gray])),
|
|
70
|
+
content: s.field("content", S.string),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
let client = Rest.client(Env.envioAppUrl ++ "/api")
|
|
74
|
+
|
|
75
|
+
let route = Rest.route(() => {
|
|
76
|
+
method: Post,
|
|
77
|
+
path: "/hyperindex/init",
|
|
78
|
+
input: s => s.body(bodySchema),
|
|
79
|
+
responses: [s => s.field("messages", S.array(messageSchema))],
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
let getMessages = async (~config) => {
|
|
83
|
+
let envioVersion = Utils.EnvioPackage.value.version
|
|
84
|
+
let body = makeBody(~envioVersion, ~envioApiToken=Env.envioApiToken, ~config)
|
|
85
|
+
|
|
86
|
+
switch await route->Rest.fetch(body, ~client) {
|
|
87
|
+
| exception exn => Error(exn->Obj.magic)
|
|
88
|
+
| messages => Ok(messages)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type request<'ok, 'err> = Data('ok) | Loading | Err('err)
|
|
94
|
+
|
|
95
|
+
let useMessages = (~config) => {
|
|
96
|
+
let (request, setRequest) = React.useState(_ => Loading)
|
|
97
|
+
React.useEffect0(() => {
|
|
98
|
+
InitApi.getMessages(~config)
|
|
99
|
+
->Promise.thenResolve(res =>
|
|
100
|
+
switch res {
|
|
101
|
+
| Ok(data) => setRequest(_ => Data(data))
|
|
102
|
+
| Error(e) =>
|
|
103
|
+
Logging.error({
|
|
104
|
+
"msg": "Failed to load messages from envio server",
|
|
105
|
+
"err": e->Utils.prettifyExn,
|
|
106
|
+
})
|
|
107
|
+
setRequest(_ => Err(e))
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
->ignore
|
|
111
|
+
None
|
|
112
|
+
})
|
|
113
|
+
request
|
|
114
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Env from "../../Env.res.mjs";
|
|
4
|
+
import * as Rest from "../../vendored/Rest.res.mjs";
|
|
5
|
+
import * as Utils from "../../Utils.res.mjs";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import * as Logging from "../../Logging.res.mjs";
|
|
8
|
+
import * as ChainMap from "../../ChainMap.res.mjs";
|
|
9
|
+
import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
|
|
10
|
+
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
11
|
+
import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
|
|
12
|
+
|
|
13
|
+
var bodySchema = S$RescriptSchema.object(function (s) {
|
|
14
|
+
return {
|
|
15
|
+
envioVersion: s.f("envioVersion", S$RescriptSchema.string),
|
|
16
|
+
envioApiToken: s.f("envioApiToken", S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
17
|
+
ecosystem: s.f("ecosystem", S$RescriptSchema.$$enum([
|
|
18
|
+
"evm",
|
|
19
|
+
"fuel",
|
|
20
|
+
"svm"
|
|
21
|
+
])),
|
|
22
|
+
hyperSyncNetworks: s.f("hyperSyncNetworks", S$RescriptSchema.array(S$RescriptSchema.$$int)),
|
|
23
|
+
rpcNetworks: s.f("rpcNetworks", S$RescriptSchema.array(S$RescriptSchema.$$int))
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function makeBody(envioVersion, envioApiToken, config) {
|
|
28
|
+
var hyperSyncNetworks = [];
|
|
29
|
+
var rpcNetworks = [];
|
|
30
|
+
Belt_Array.forEach(ChainMap.values(config.chainMap), (function (param) {
|
|
31
|
+
(
|
|
32
|
+
param.sources.some(function (s) {
|
|
33
|
+
return s.poweredByHyperSync;
|
|
34
|
+
}) ? hyperSyncNetworks : rpcNetworks
|
|
35
|
+
).push(param.id);
|
|
36
|
+
}));
|
|
37
|
+
return {
|
|
38
|
+
envioVersion: envioVersion,
|
|
39
|
+
envioApiToken: envioApiToken,
|
|
40
|
+
ecosystem: config.ecosystem.name,
|
|
41
|
+
hyperSyncNetworks: hyperSyncNetworks,
|
|
42
|
+
rpcNetworks: rpcNetworks
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toTheme(color) {
|
|
47
|
+
switch (color) {
|
|
48
|
+
case "primary" :
|
|
49
|
+
return "#9860E5";
|
|
50
|
+
case "secondary" :
|
|
51
|
+
return "#FFBB2F";
|
|
52
|
+
case "info" :
|
|
53
|
+
return "#6CBFEE";
|
|
54
|
+
case "danger" :
|
|
55
|
+
return "#FF8269";
|
|
56
|
+
case "success" :
|
|
57
|
+
return "#3B8C3D";
|
|
58
|
+
case "white" :
|
|
59
|
+
return "white";
|
|
60
|
+
case "gray" :
|
|
61
|
+
return "gray";
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
var messageSchema = S$RescriptSchema.object(function (s) {
|
|
67
|
+
return {
|
|
68
|
+
color: s.f("color", S$RescriptSchema.$$enum([
|
|
69
|
+
"primary",
|
|
70
|
+
"secondary",
|
|
71
|
+
"info",
|
|
72
|
+
"danger",
|
|
73
|
+
"success",
|
|
74
|
+
"white",
|
|
75
|
+
"gray"
|
|
76
|
+
])),
|
|
77
|
+
content: s.f("content", S$RescriptSchema.string)
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
var client = Rest.client(Env.envioAppUrl + "/api", undefined);
|
|
82
|
+
|
|
83
|
+
function route() {
|
|
84
|
+
return {
|
|
85
|
+
method: "POST",
|
|
86
|
+
path: "/hyperindex/init",
|
|
87
|
+
input: (function (s) {
|
|
88
|
+
return s.body(bodySchema);
|
|
89
|
+
}),
|
|
90
|
+
responses: [(function (s) {
|
|
91
|
+
return s.field("messages", S$RescriptSchema.array(messageSchema));
|
|
92
|
+
})]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function getMessages(config) {
|
|
97
|
+
var envioVersion = Utils.EnvioPackage.value.version;
|
|
98
|
+
var body = makeBody(envioVersion, Env.envioApiToken, config);
|
|
99
|
+
var messages;
|
|
100
|
+
try {
|
|
101
|
+
messages = await Rest.$$fetch(route, body, client);
|
|
102
|
+
}
|
|
103
|
+
catch (raw_exn){
|
|
104
|
+
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
105
|
+
return {
|
|
106
|
+
TAG: "Error",
|
|
107
|
+
_0: exn
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
TAG: "Ok",
|
|
112
|
+
_0: messages
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var InitApi = {
|
|
117
|
+
bodySchema: bodySchema,
|
|
118
|
+
makeBody: makeBody,
|
|
119
|
+
toTheme: toTheme,
|
|
120
|
+
messageSchema: messageSchema,
|
|
121
|
+
client: client,
|
|
122
|
+
route: route,
|
|
123
|
+
getMessages: getMessages
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function useMessages(config) {
|
|
127
|
+
var match = React.useState(function () {
|
|
128
|
+
return "Loading";
|
|
129
|
+
});
|
|
130
|
+
var setRequest = match[1];
|
|
131
|
+
React.useEffect((function () {
|
|
132
|
+
getMessages(config).then(function (res) {
|
|
133
|
+
if (res.TAG === "Ok") {
|
|
134
|
+
var data = res._0;
|
|
135
|
+
return setRequest(function (param) {
|
|
136
|
+
return {
|
|
137
|
+
TAG: "Data",
|
|
138
|
+
_0: data
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
var e = res._0;
|
|
143
|
+
Logging.error({
|
|
144
|
+
msg: "Failed to load messages from envio server",
|
|
145
|
+
err: Utils.prettifyExn(e)
|
|
146
|
+
});
|
|
147
|
+
setRequest(function (param) {
|
|
148
|
+
return {
|
|
149
|
+
TAG: "Err",
|
|
150
|
+
_0: e
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}), []);
|
|
155
|
+
return match[0];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
InitApi ,
|
|
160
|
+
useMessages ,
|
|
161
|
+
}
|
|
162
|
+
/* bodySchema Not a pure module */
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
open Belt
|
|
2
|
+
open Ink
|
|
3
|
+
module Message = {
|
|
4
|
+
@react.component
|
|
5
|
+
let make = (~message: CustomHooks.InitApi.message) => {
|
|
6
|
+
<Text color={message.color->CustomHooks.InitApi.toTheme}>
|
|
7
|
+
{message.content->React.string}
|
|
8
|
+
</Text>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module Notifications = {
|
|
13
|
+
@react.component
|
|
14
|
+
let make = (~children) => {
|
|
15
|
+
<>
|
|
16
|
+
<Newline />
|
|
17
|
+
<Text bold=true> {"Notifications:"->React.string} </Text>
|
|
18
|
+
{children}
|
|
19
|
+
</>
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@react.component
|
|
24
|
+
let make = (~config) => {
|
|
25
|
+
let messages = CustomHooks.useMessages(~config)
|
|
26
|
+
<>
|
|
27
|
+
{switch messages {
|
|
28
|
+
| Data([]) | Loading => React.null //Don't show anything while loading or no messages
|
|
29
|
+
| Data(messages) =>
|
|
30
|
+
<Notifications>
|
|
31
|
+
{messages
|
|
32
|
+
->Array.mapWithIndex((i, message) => {<Message key={i->Int.toString} message />})
|
|
33
|
+
->React.array}
|
|
34
|
+
</Notifications>
|
|
35
|
+
| Err(_) =>
|
|
36
|
+
<Notifications>
|
|
37
|
+
<Message message={color: Danger, content: "Failed to load messages from envio server"} />
|
|
38
|
+
</Notifications>
|
|
39
|
+
}}
|
|
40
|
+
</>
|
|
41
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as $$Ink from "../bindings/Ink.res.mjs";
|
|
4
|
+
import * as $$Ink$1 from "ink";
|
|
5
|
+
import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
|
|
6
|
+
import * as Caml_option from "rescript/lib/es6/caml_option.js";
|
|
7
|
+
import * as CustomHooks from "./CustomHooks.res.mjs";
|
|
8
|
+
import * as JsxRuntime from "react/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
function Messages$Message(props) {
|
|
11
|
+
var message = props.message;
|
|
12
|
+
return JsxRuntime.jsx($$Ink$1.Text, {
|
|
13
|
+
children: message.content,
|
|
14
|
+
color: CustomHooks.InitApi.toTheme(message.color)
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
var Message = {
|
|
19
|
+
make: Messages$Message
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function Messages$Notifications(props) {
|
|
23
|
+
return JsxRuntime.jsxs(JsxRuntime.Fragment, {
|
|
24
|
+
children: [
|
|
25
|
+
JsxRuntime.jsx($$Ink.Newline.make, {}),
|
|
26
|
+
JsxRuntime.jsx($$Ink$1.Text, {
|
|
27
|
+
children: "Notifications:",
|
|
28
|
+
bold: true
|
|
29
|
+
}),
|
|
30
|
+
props.children
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var Notifications = {
|
|
36
|
+
make: Messages$Notifications
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function Messages(props) {
|
|
40
|
+
var messages = CustomHooks.useMessages(props.config);
|
|
41
|
+
var tmp;
|
|
42
|
+
if (typeof messages !== "object") {
|
|
43
|
+
tmp = null;
|
|
44
|
+
} else if (messages.TAG === "Data") {
|
|
45
|
+
var messages$1 = messages._0;
|
|
46
|
+
tmp = messages$1.length !== 0 ? JsxRuntime.jsx(Messages$Notifications, {
|
|
47
|
+
children: Belt_Array.mapWithIndex(messages$1, (function (i, message) {
|
|
48
|
+
return JsxRuntime.jsx(Messages$Message, {
|
|
49
|
+
message: message
|
|
50
|
+
}, String(i));
|
|
51
|
+
}))
|
|
52
|
+
}) : null;
|
|
53
|
+
} else {
|
|
54
|
+
tmp = JsxRuntime.jsx(Messages$Notifications, {
|
|
55
|
+
children: JsxRuntime.jsx(Messages$Message, {
|
|
56
|
+
message: {
|
|
57
|
+
color: "danger",
|
|
58
|
+
content: "Failed to load messages from envio server"
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return JsxRuntime.jsx(JsxRuntime.Fragment, {
|
|
64
|
+
children: Caml_option.some(tmp)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var make = Messages;
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
Message ,
|
|
72
|
+
Notifications ,
|
|
73
|
+
make ,
|
|
74
|
+
}
|
|
75
|
+
/* Ink Not a pure module */
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
open Ink
|
|
2
|
+
open Belt
|
|
3
|
+
|
|
4
|
+
let isIndexerFullySynced = (chains: array<TuiData.chain>) => {
|
|
5
|
+
chains->Array.reduce(true, (accum, current) => {
|
|
6
|
+
switch current.progress {
|
|
7
|
+
| Synced(_) => accum
|
|
8
|
+
| _ => false
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let getTotalRemainingBlocks = (chains: array<TuiData.chain>) => {
|
|
14
|
+
chains->Array.reduce(0, (accum, {progress, knownHeight, latestFetchedBlockNumber, endBlock}) => {
|
|
15
|
+
let finalBlock = switch endBlock {
|
|
16
|
+
| Some(endBlock) => endBlock
|
|
17
|
+
| None => knownHeight
|
|
18
|
+
}
|
|
19
|
+
switch progress {
|
|
20
|
+
| Syncing({latestProcessedBlock})
|
|
21
|
+
| Synced({latestProcessedBlock}) =>
|
|
22
|
+
finalBlock - latestProcessedBlock + accum
|
|
23
|
+
| SearchingForEvents => finalBlock - latestFetchedBlockNumber + accum
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let getLatestTimeCaughtUpToHead = (chains: array<TuiData.chain>, indexerStartTime: Js.Date.t) => {
|
|
29
|
+
let latesttimestampCaughtUpToHeadOrEndblockFloat = chains->Array.reduce(0.0, (accum, current) => {
|
|
30
|
+
switch current.progress {
|
|
31
|
+
| Synced({timestampCaughtUpToHeadOrEndblock}) =>
|
|
32
|
+
timestampCaughtUpToHeadOrEndblock->Js.Date.valueOf > accum
|
|
33
|
+
? timestampCaughtUpToHeadOrEndblock->Js.Date.valueOf
|
|
34
|
+
: accum
|
|
35
|
+
| Syncing(_)
|
|
36
|
+
| SearchingForEvents => accum
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
DateFns.formatDistanceWithOptions(
|
|
41
|
+
indexerStartTime,
|
|
42
|
+
latesttimestampCaughtUpToHeadOrEndblockFloat->Js.Date.fromFloat,
|
|
43
|
+
{includeSeconds: true},
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let getTotalBlocksProcessed = (chains: array<TuiData.chain>) => {
|
|
48
|
+
chains->Array.reduce(0, (accum, {progress, latestFetchedBlockNumber}) => {
|
|
49
|
+
switch progress {
|
|
50
|
+
| Syncing({latestProcessedBlock, firstEventBlockNumber})
|
|
51
|
+
| Synced({latestProcessedBlock, firstEventBlockNumber}) =>
|
|
52
|
+
latestProcessedBlock - firstEventBlockNumber + accum
|
|
53
|
+
| SearchingForEvents => latestFetchedBlockNumber + accum
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let useShouldDisplayEta = (~chains: array<TuiData.chain>) => {
|
|
59
|
+
let (shouldDisplayEta, setShouldDisplayEta) = React.useState(_ => false)
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
//Only compute this while it is not displaying eta
|
|
62
|
+
if !shouldDisplayEta {
|
|
63
|
+
//Each chain should have fetched at least one batch
|
|
64
|
+
let (allChainsHaveFetchedABatch, totalNumBatchesFetched) = chains->Array.reduce((true, 0), (
|
|
65
|
+
(allChainsHaveFetchedABatch, totalNumBatchesFetched),
|
|
66
|
+
chain,
|
|
67
|
+
) => {
|
|
68
|
+
(
|
|
69
|
+
allChainsHaveFetchedABatch && chain.numBatchesFetched >= 1,
|
|
70
|
+
totalNumBatchesFetched + chain.numBatchesFetched,
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
//Min num fetched batches is num of chains + 2. All
|
|
75
|
+
// Chains should have fetched at least 1 batch. (They
|
|
76
|
+
// could then be blocked from fetching if they are past
|
|
77
|
+
//the max queue size on first batch)
|
|
78
|
+
// Only display once an additinal 2 batches have been fetched to allow
|
|
79
|
+
// eta to realistically stabalize
|
|
80
|
+
let numChains = chains->Array.length
|
|
81
|
+
let minTotalBatches = numChains + 2
|
|
82
|
+
let hasMinNumBatches = totalNumBatchesFetched >= minTotalBatches
|
|
83
|
+
|
|
84
|
+
let shouldDisplayEta = allChainsHaveFetchedABatch && hasMinNumBatches
|
|
85
|
+
|
|
86
|
+
if shouldDisplayEta {
|
|
87
|
+
setShouldDisplayEta(_ => true)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
None
|
|
92
|
+
}, [chains])
|
|
93
|
+
|
|
94
|
+
shouldDisplayEta
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let useEta = (~chains, ~indexerStartTime) => {
|
|
98
|
+
let shouldDisplayEta = useShouldDisplayEta(~chains)
|
|
99
|
+
let (secondsToSub, setSecondsToSub) = React.useState(_ => 0.)
|
|
100
|
+
let (timeSinceStart, setTimeSinceStart) = React.useState(_ => 0.)
|
|
101
|
+
|
|
102
|
+
React.useEffect2(() => {
|
|
103
|
+
setTimeSinceStart(_ => Js.Date.now() -. indexerStartTime->Js.Date.valueOf)
|
|
104
|
+
setSecondsToSub(_ => 0.)
|
|
105
|
+
|
|
106
|
+
let intervalId = Js.Global.setInterval(() => {
|
|
107
|
+
setSecondsToSub(prev => prev +. 1.)
|
|
108
|
+
}, 1000)
|
|
109
|
+
|
|
110
|
+
Some(() => Js.Global.clearInterval(intervalId))
|
|
111
|
+
}, (chains, indexerStartTime))
|
|
112
|
+
|
|
113
|
+
//blocksProcessed/remainingBlocks = timeSoFar/eta
|
|
114
|
+
//eta = (timeSoFar/blocksProcessed) * remainingBlocks
|
|
115
|
+
|
|
116
|
+
let blocksProcessed = getTotalBlocksProcessed(chains)->Int.toFloat
|
|
117
|
+
if shouldDisplayEta && blocksProcessed > 0. {
|
|
118
|
+
let nowDate = Js.Date.now()
|
|
119
|
+
let remainingBlocks = getTotalRemainingBlocks(chains)->Int.toFloat
|
|
120
|
+
let etaFloat = timeSinceStart /. blocksProcessed *. remainingBlocks
|
|
121
|
+
let millisToSub = secondsToSub *. 1000.
|
|
122
|
+
let etaFloat = Pervasives.max(etaFloat -. millisToSub, 0.0) //template this
|
|
123
|
+
let eta = (etaFloat +. nowDate)->Js.Date.fromFloat
|
|
124
|
+
let interval: DateFns.interval = {start: nowDate->Js.Date.fromFloat, end: eta}
|
|
125
|
+
let duration = DateFns.intervalToDuration(interval)
|
|
126
|
+
let formattedDuration = DateFns.formatDuration(
|
|
127
|
+
duration,
|
|
128
|
+
{format: ["hours", "minutes", "seconds"]},
|
|
129
|
+
)
|
|
130
|
+
let outputString = switch formattedDuration {
|
|
131
|
+
| "" => "less than 1 second"
|
|
132
|
+
| formattedDuration => formattedDuration
|
|
133
|
+
}
|
|
134
|
+
Some(outputString)
|
|
135
|
+
} else {
|
|
136
|
+
None
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module Syncing = {
|
|
141
|
+
@react.component
|
|
142
|
+
let make = (~etaStr) => {
|
|
143
|
+
<Text bold=true>
|
|
144
|
+
<Text> {"Sync Time ETA: "->React.string} </Text>
|
|
145
|
+
<Text> {etaStr->React.string} </Text>
|
|
146
|
+
<Text> {" ("->React.string} </Text>
|
|
147
|
+
<Text color=Primary>
|
|
148
|
+
<Spinner />
|
|
149
|
+
</Text>
|
|
150
|
+
<Text color=Secondary> {" in progress"->React.string} </Text>
|
|
151
|
+
<Text> {")"->React.string} </Text>
|
|
152
|
+
</Text>
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module Synced = {
|
|
157
|
+
@react.component
|
|
158
|
+
let make = (~latestTimeCaughtUpToHeadStr) => {
|
|
159
|
+
<Text bold=true>
|
|
160
|
+
<Text> {"Time Synced: "->React.string} </Text>
|
|
161
|
+
<Text> {`${latestTimeCaughtUpToHeadStr}`->React.string} </Text>
|
|
162
|
+
<Text> {" ("->React.string} </Text>
|
|
163
|
+
<Text color=Success> {"synced"->React.string} </Text>
|
|
164
|
+
<Text> {")"->React.string} </Text>
|
|
165
|
+
</Text>
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module Calculating = {
|
|
170
|
+
@react.component
|
|
171
|
+
let make = () => {
|
|
172
|
+
<Text>
|
|
173
|
+
<Text color=Primary>
|
|
174
|
+
<Spinner />
|
|
175
|
+
</Text>
|
|
176
|
+
<Text bold=true> {" Calculating ETA..."->React.string} </Text>
|
|
177
|
+
</Text>
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@react.component
|
|
182
|
+
let make = (~chains, ~indexerStartTime) => {
|
|
183
|
+
let optEta = useEta(~chains, ~indexerStartTime)
|
|
184
|
+
if isIndexerFullySynced(chains) {
|
|
185
|
+
let latestTimeCaughtUpToHeadStr = getLatestTimeCaughtUpToHead(chains, indexerStartTime)
|
|
186
|
+
<Synced latestTimeCaughtUpToHeadStr /> //TODO add real time
|
|
187
|
+
} else {
|
|
188
|
+
switch optEta {
|
|
189
|
+
| Some(etaStr) => <Syncing etaStr />
|
|
190
|
+
| None => <Calculating />
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|