envio 3.0.0-alpha.0 → 3.0.0-alpha.10

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.
Files changed (136) hide show
  1. package/README.md +2 -2
  2. package/evm.schema.json +53 -57
  3. package/fuel.schema.json +35 -31
  4. package/index.d.ts +446 -1
  5. package/index.js +4 -0
  6. package/package.json +23 -11
  7. package/rescript.json +4 -1
  8. package/src/Batch.res.mjs +1 -1
  9. package/src/Benchmark.res +394 -0
  10. package/src/Benchmark.res.mjs +398 -0
  11. package/src/ChainFetcher.res +526 -0
  12. package/src/ChainFetcher.res.mjs +343 -0
  13. package/src/ChainManager.res +182 -0
  14. package/src/ChainManager.res.mjs +150 -0
  15. package/src/Config.res +367 -27
  16. package/src/Config.res.mjs +338 -28
  17. package/src/{Indexer.res → Ctx.res} +1 -1
  18. package/src/Ecosystem.res +25 -0
  19. package/src/Ecosystem.res.mjs +29 -0
  20. package/src/Env.res +252 -0
  21. package/src/Env.res.mjs +271 -0
  22. package/src/Envio.gen.ts +9 -1
  23. package/src/Envio.res +12 -9
  24. package/src/EventProcessing.res +476 -0
  25. package/src/EventProcessing.res.mjs +341 -0
  26. package/src/EventRegister.res +4 -15
  27. package/src/EventRegister.res.mjs +3 -9
  28. package/src/EventRegister.resi +2 -8
  29. package/src/FetchState.res +54 -29
  30. package/src/FetchState.res.mjs +62 -35
  31. package/src/GlobalState.res +1163 -0
  32. package/src/GlobalState.res.mjs +1196 -0
  33. package/src/GlobalStateManager.res +68 -0
  34. package/src/GlobalStateManager.res.mjs +75 -0
  35. package/src/GlobalStateManager.resi +7 -0
  36. package/src/HandlerLoader.res +89 -0
  37. package/src/HandlerLoader.res.mjs +79 -0
  38. package/src/Internal.gen.ts +3 -14
  39. package/src/Internal.res +45 -13
  40. package/src/LoadLayer.res +444 -0
  41. package/src/LoadLayer.res.mjs +296 -0
  42. package/src/LoadLayer.resi +32 -0
  43. package/src/LogSelection.res +33 -27
  44. package/src/LogSelection.res.mjs +6 -0
  45. package/src/Logging.res +11 -4
  46. package/src/Logging.res.mjs +14 -6
  47. package/src/Main.res +351 -0
  48. package/src/Main.res.mjs +312 -0
  49. package/src/Persistence.res +1 -2
  50. package/src/PgStorage.gen.ts +10 -0
  51. package/src/PgStorage.res +28 -34
  52. package/src/PgStorage.res.d.mts +5 -0
  53. package/src/PgStorage.res.mjs +27 -27
  54. package/src/Prometheus.res +8 -8
  55. package/src/Prometheus.res.mjs +10 -10
  56. package/src/ReorgDetection.res +6 -10
  57. package/src/ReorgDetection.res.mjs +6 -6
  58. package/src/TestIndexer.res +536 -0
  59. package/src/TestIndexer.res.mjs +412 -0
  60. package/src/TestIndexerProxyStorage.res +210 -0
  61. package/src/TestIndexerProxyStorage.res.mjs +157 -0
  62. package/src/Types.ts +1 -1
  63. package/src/UserContext.res +355 -0
  64. package/src/UserContext.res.mjs +236 -0
  65. package/src/Utils.res +28 -0
  66. package/src/Utils.res.mjs +18 -0
  67. package/src/bindings/ClickHouse.res +31 -1
  68. package/src/bindings/ClickHouse.res.mjs +27 -1
  69. package/src/bindings/DateFns.res +71 -0
  70. package/src/bindings/DateFns.res.mjs +22 -0
  71. package/src/bindings/Ethers.res +27 -67
  72. package/src/bindings/Ethers.res.mjs +18 -70
  73. package/src/bindings/EventSource.res +13 -0
  74. package/src/bindings/EventSource.res.mjs +2 -0
  75. package/src/bindings/NodeJs.res +44 -3
  76. package/src/bindings/NodeJs.res.mjs +11 -3
  77. package/src/bindings/Pino.res +21 -7
  78. package/src/bindings/Pino.res.mjs +11 -5
  79. package/src/bindings/Postgres.gen.ts +8 -0
  80. package/src/bindings/Postgres.res +3 -0
  81. package/src/bindings/Postgres.res.d.mts +5 -0
  82. package/src/bindings/RescriptMocha.res +123 -0
  83. package/src/bindings/RescriptMocha.res.mjs +18 -0
  84. package/src/bindings/Vitest.res +134 -0
  85. package/src/bindings/Vitest.res.mjs +9 -0
  86. package/src/bindings/Yargs.res +8 -0
  87. package/src/bindings/Yargs.res.mjs +2 -0
  88. package/src/db/InternalTable.res +2 -0
  89. package/src/db/InternalTable.res.mjs +1 -1
  90. package/src/sources/Evm.res +87 -0
  91. package/src/sources/Evm.res.mjs +105 -0
  92. package/src/sources/EvmChain.res +90 -0
  93. package/src/sources/EvmChain.res.mjs +57 -0
  94. package/src/sources/Fuel.res +19 -34
  95. package/src/sources/Fuel.res.mjs +34 -16
  96. package/src/sources/FuelSDK.res +38 -0
  97. package/src/sources/FuelSDK.res.mjs +29 -0
  98. package/src/sources/HyperFuel.res +2 -2
  99. package/src/sources/HyperFuel.resi +1 -1
  100. package/src/sources/HyperFuelClient.res +2 -2
  101. package/src/sources/HyperFuelSource.res +8 -8
  102. package/src/sources/HyperFuelSource.res.mjs +5 -5
  103. package/src/sources/HyperSyncHeightStream.res +97 -0
  104. package/src/sources/HyperSyncHeightStream.res.mjs +94 -0
  105. package/src/sources/HyperSyncSource.res +64 -172
  106. package/src/sources/HyperSyncSource.res.mjs +73 -155
  107. package/src/sources/Rpc.res +43 -0
  108. package/src/sources/Rpc.res.mjs +31 -0
  109. package/src/sources/RpcSource.res +32 -130
  110. package/src/sources/RpcSource.res.mjs +47 -121
  111. package/src/sources/Source.res +3 -2
  112. package/src/sources/SourceManager.res +183 -108
  113. package/src/sources/SourceManager.res.mjs +162 -99
  114. package/src/sources/SourceManager.resi +4 -5
  115. package/src/sources/Svm.res +59 -0
  116. package/src/sources/Svm.res.mjs +79 -0
  117. package/src/tui/Tui.res +266 -0
  118. package/src/tui/Tui.res.mjs +342 -0
  119. package/src/tui/bindings/Ink.res +376 -0
  120. package/src/tui/bindings/Ink.res.mjs +75 -0
  121. package/src/tui/bindings/Style.res +123 -0
  122. package/src/tui/bindings/Style.res.mjs +2 -0
  123. package/src/tui/components/BufferedProgressBar.res +40 -0
  124. package/src/tui/components/BufferedProgressBar.res.mjs +57 -0
  125. package/src/tui/components/CustomHooks.res +122 -0
  126. package/src/tui/components/CustomHooks.res.mjs +179 -0
  127. package/src/tui/components/Messages.res +41 -0
  128. package/src/tui/components/Messages.res.mjs +75 -0
  129. package/src/tui/components/SyncETA.res +193 -0
  130. package/src/tui/components/SyncETA.res.mjs +269 -0
  131. package/src/tui/components/TuiData.res +46 -0
  132. package/src/tui/components/TuiData.res.mjs +29 -0
  133. package/src/Platform.res +0 -140
  134. package/src/Platform.res.mjs +0 -170
  135. package/src/bindings/Ethers.gen.ts +0 -14
  136. /package/src/{Indexer.res.mjs → Ctx.res.mjs} +0 -0
@@ -0,0 +1,122 @@
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(({sourceConfig, id}) => {
26
+ // Check if chain uses HyperSync based on sourceConfig
27
+ let usesHyperSync = switch sourceConfig {
28
+ | Config.EvmSourceConfig({hypersync: Some(_)}) => true
29
+ | Config.EvmSourceConfig({hypersync: None}) => false
30
+ | Config.FuelSourceConfig(_) => true // Fuel always uses HyperFuel
31
+ | Config.SvmSourceConfig(_) => false
32
+ | Config.CustomSources(sources) => sources->Array.some(s => s.poweredByHyperSync)
33
+ }
34
+ switch usesHyperSync {
35
+ | true => hyperSyncNetworks
36
+ | false => rpcNetworks
37
+ }
38
+ ->Js.Array2.push(id)
39
+ ->ignore
40
+ })
41
+
42
+ {
43
+ envioVersion,
44
+ envioApiToken,
45
+ ecosystem: (config.ecosystem.name :> ecosystem),
46
+ hyperSyncNetworks,
47
+ rpcNetworks,
48
+ }
49
+ }
50
+
51
+ type messageColor =
52
+ | @as("primary") Primary
53
+ | @as("secondary") Secondary
54
+ | @as("info") Info
55
+ | @as("danger") Danger
56
+ | @as("success") Success
57
+ | @as("white") White
58
+ | @as("gray") Gray
59
+
60
+ let toTheme = (color: messageColor): Style.chalkTheme =>
61
+ switch color {
62
+ | Primary => Primary
63
+ | Secondary => Secondary
64
+ | Info => Info
65
+ | Danger => Danger
66
+ | Success => Success
67
+ | White => White
68
+ | Gray => Gray
69
+ }
70
+
71
+ type message = {
72
+ color: messageColor,
73
+ content: string,
74
+ }
75
+
76
+ let messageSchema = S.object(s => {
77
+ color: s.field("color", S.enum([Primary, Secondary, Info, Danger, Success, White, Gray])),
78
+ content: s.field("content", S.string),
79
+ })
80
+
81
+ let client = Rest.client(Env.envioAppUrl ++ "/api")
82
+
83
+ let route = Rest.route(() => {
84
+ method: Post,
85
+ path: "/hyperindex/init",
86
+ input: s => s.body(bodySchema),
87
+ responses: [s => s.field("messages", S.array(messageSchema))],
88
+ })
89
+
90
+ let getMessages = async (~config) => {
91
+ let envioVersion = Utils.EnvioPackage.value.version
92
+ let body = makeBody(~envioVersion, ~envioApiToken=Env.envioApiToken, ~config)
93
+
94
+ switch await route->Rest.fetch(body, ~client) {
95
+ | exception exn => Error(exn->Obj.magic)
96
+ | messages => Ok(messages)
97
+ }
98
+ }
99
+ }
100
+
101
+ type request<'ok, 'err> = Data('ok) | Loading | Err('err)
102
+
103
+ let useMessages = (~config) => {
104
+ let (request, setRequest) = React.useState(_ => Loading)
105
+ React.useEffect0(() => {
106
+ InitApi.getMessages(~config)
107
+ ->Promise.thenResolve(res =>
108
+ switch res {
109
+ | Ok(data) => setRequest(_ => Data(data))
110
+ | Error(e) =>
111
+ Logging.error({
112
+ "msg": "Failed to load messages from envio server",
113
+ "err": e->Utils.prettifyExn,
114
+ })
115
+ setRequest(_ => Err(e))
116
+ }
117
+ )
118
+ ->ignore
119
+ None
120
+ })
121
+ request
122
+ }
@@ -0,0 +1,179 @@
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
+ var sourceConfig = param.sourceConfig;
32
+ var usesHyperSync;
33
+ switch (sourceConfig.TAG) {
34
+ case "EvmSourceConfig" :
35
+ usesHyperSync = sourceConfig.hypersync !== undefined;
36
+ break;
37
+ case "FuelSourceConfig" :
38
+ usesHyperSync = true;
39
+ break;
40
+ case "SvmSourceConfig" :
41
+ usesHyperSync = false;
42
+ break;
43
+ case "CustomSources" :
44
+ usesHyperSync = Belt_Array.some(sourceConfig._0, (function (s) {
45
+ return s.poweredByHyperSync;
46
+ }));
47
+ break;
48
+
49
+ }
50
+ (
51
+ usesHyperSync ? hyperSyncNetworks : rpcNetworks
52
+ ).push(param.id);
53
+ }));
54
+ return {
55
+ envioVersion: envioVersion,
56
+ envioApiToken: envioApiToken,
57
+ ecosystem: config.ecosystem.name,
58
+ hyperSyncNetworks: hyperSyncNetworks,
59
+ rpcNetworks: rpcNetworks
60
+ };
61
+ }
62
+
63
+ function toTheme(color) {
64
+ switch (color) {
65
+ case "primary" :
66
+ return "#9860E5";
67
+ case "secondary" :
68
+ return "#FFBB2F";
69
+ case "info" :
70
+ return "#6CBFEE";
71
+ case "danger" :
72
+ return "#FF8269";
73
+ case "success" :
74
+ return "#3B8C3D";
75
+ case "white" :
76
+ return "white";
77
+ case "gray" :
78
+ return "gray";
79
+
80
+ }
81
+ }
82
+
83
+ var messageSchema = S$RescriptSchema.object(function (s) {
84
+ return {
85
+ color: s.f("color", S$RescriptSchema.$$enum([
86
+ "primary",
87
+ "secondary",
88
+ "info",
89
+ "danger",
90
+ "success",
91
+ "white",
92
+ "gray"
93
+ ])),
94
+ content: s.f("content", S$RescriptSchema.string)
95
+ };
96
+ });
97
+
98
+ var client = Rest.client(Env.envioAppUrl + "/api", undefined);
99
+
100
+ function route() {
101
+ return {
102
+ method: "POST",
103
+ path: "/hyperindex/init",
104
+ input: (function (s) {
105
+ return s.body(bodySchema);
106
+ }),
107
+ responses: [(function (s) {
108
+ return s.field("messages", S$RescriptSchema.array(messageSchema));
109
+ })]
110
+ };
111
+ }
112
+
113
+ async function getMessages(config) {
114
+ var envioVersion = Utils.EnvioPackage.value.version;
115
+ var body = makeBody(envioVersion, Env.envioApiToken, config);
116
+ var messages;
117
+ try {
118
+ messages = await Rest.$$fetch(route, body, client);
119
+ }
120
+ catch (raw_exn){
121
+ var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
122
+ return {
123
+ TAG: "Error",
124
+ _0: exn
125
+ };
126
+ }
127
+ return {
128
+ TAG: "Ok",
129
+ _0: messages
130
+ };
131
+ }
132
+
133
+ var InitApi = {
134
+ bodySchema: bodySchema,
135
+ makeBody: makeBody,
136
+ toTheme: toTheme,
137
+ messageSchema: messageSchema,
138
+ client: client,
139
+ route: route,
140
+ getMessages: getMessages
141
+ };
142
+
143
+ function useMessages(config) {
144
+ var match = React.useState(function () {
145
+ return "Loading";
146
+ });
147
+ var setRequest = match[1];
148
+ React.useEffect((function () {
149
+ getMessages(config).then(function (res) {
150
+ if (res.TAG === "Ok") {
151
+ var data = res._0;
152
+ return setRequest(function (param) {
153
+ return {
154
+ TAG: "Data",
155
+ _0: data
156
+ };
157
+ });
158
+ }
159
+ var e = res._0;
160
+ Logging.error({
161
+ msg: "Failed to load messages from envio server",
162
+ err: Utils.prettifyExn(e)
163
+ });
164
+ setRequest(function (param) {
165
+ return {
166
+ TAG: "Err",
167
+ _0: e
168
+ };
169
+ });
170
+ });
171
+ }), []);
172
+ return match[0];
173
+ }
174
+
175
+ export {
176
+ InitApi ,
177
+ useMessages ,
178
+ }
179
+ /* 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
+ }