envio 3.0.0-alpha.2 → 3.0.0-alpha.20

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 (175) hide show
  1. package/README.md +164 -30
  2. package/bin.mjs +49 -0
  3. package/evm.schema.json +79 -169
  4. package/fuel.schema.json +50 -21
  5. package/index.d.ts +497 -1
  6. package/index.js +4 -0
  7. package/package.json +42 -31
  8. package/rescript.json +4 -1
  9. package/src/Batch.res +11 -8
  10. package/src/Batch.res.mjs +11 -9
  11. package/src/ChainFetcher.res +531 -0
  12. package/src/ChainFetcher.res.mjs +339 -0
  13. package/src/ChainManager.res +190 -0
  14. package/src/ChainManager.res.mjs +166 -0
  15. package/src/Change.res +3 -3
  16. package/src/Config.gen.ts +19 -0
  17. package/src/Config.res +737 -22
  18. package/src/Config.res.mjs +703 -26
  19. package/src/{Indexer.res → Ctx.res} +1 -1
  20. package/src/Ecosystem.res +9 -124
  21. package/src/Ecosystem.res.mjs +19 -160
  22. package/src/Env.res +30 -74
  23. package/src/Env.res.mjs +25 -87
  24. package/src/Envio.gen.ts +3 -1
  25. package/src/Envio.res +20 -9
  26. package/src/EventProcessing.res +469 -0
  27. package/src/EventProcessing.res.mjs +337 -0
  28. package/src/EvmTypes.gen.ts +6 -0
  29. package/src/EvmTypes.res +1 -0
  30. package/src/FetchState.res +1256 -639
  31. package/src/FetchState.res.mjs +1135 -612
  32. package/src/GlobalState.res +1190 -0
  33. package/src/GlobalState.res.mjs +1183 -0
  34. package/src/GlobalStateManager.res +68 -0
  35. package/src/GlobalStateManager.res.mjs +75 -0
  36. package/src/GlobalStateManager.resi +7 -0
  37. package/src/HandlerLoader.res +89 -0
  38. package/src/HandlerLoader.res.mjs +79 -0
  39. package/src/HandlerRegister.res +357 -0
  40. package/src/HandlerRegister.res.mjs +299 -0
  41. package/src/{EventRegister.resi → HandlerRegister.resi} +13 -13
  42. package/src/Hasura.res +111 -175
  43. package/src/Hasura.res.mjs +88 -150
  44. package/src/InMemoryStore.res +1 -1
  45. package/src/InMemoryStore.res.mjs +3 -3
  46. package/src/InMemoryTable.res +1 -1
  47. package/src/InMemoryTable.res.mjs +1 -1
  48. package/src/Internal.gen.ts +4 -0
  49. package/src/Internal.res +230 -12
  50. package/src/Internal.res.mjs +115 -1
  51. package/src/LoadLayer.res +444 -0
  52. package/src/LoadLayer.res.mjs +296 -0
  53. package/src/LoadLayer.resi +32 -0
  54. package/src/LogSelection.res +33 -27
  55. package/src/LogSelection.res.mjs +6 -0
  56. package/src/Logging.res +21 -7
  57. package/src/Logging.res.mjs +16 -8
  58. package/src/Main.res +377 -0
  59. package/src/Main.res.mjs +339 -0
  60. package/src/Persistence.res +7 -21
  61. package/src/Persistence.res.mjs +3 -3
  62. package/src/PgStorage.gen.ts +10 -0
  63. package/src/PgStorage.res +116 -69
  64. package/src/PgStorage.res.d.mts +5 -0
  65. package/src/PgStorage.res.mjs +93 -50
  66. package/src/Prometheus.res +294 -224
  67. package/src/Prometheus.res.mjs +353 -340
  68. package/src/ReorgDetection.res +6 -10
  69. package/src/ReorgDetection.res.mjs +6 -6
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +2 -2
  72. package/src/Sink.res +4 -2
  73. package/src/Sink.res.mjs +2 -1
  74. package/src/TableIndices.res +0 -1
  75. package/src/TestIndexer.res +692 -0
  76. package/src/TestIndexer.res.mjs +527 -0
  77. package/src/TestIndexerProxyStorage.res +205 -0
  78. package/src/TestIndexerProxyStorage.res.mjs +151 -0
  79. package/src/TopicFilter.res +1 -1
  80. package/src/Types.ts +1 -1
  81. package/src/UserContext.res +424 -0
  82. package/src/UserContext.res.mjs +279 -0
  83. package/src/Utils.res +97 -26
  84. package/src/Utils.res.mjs +91 -44
  85. package/src/bindings/BigInt.res +10 -0
  86. package/src/bindings/BigInt.res.mjs +15 -0
  87. package/src/bindings/ClickHouse.res +120 -23
  88. package/src/bindings/ClickHouse.res.mjs +118 -28
  89. package/src/bindings/DateFns.res +74 -0
  90. package/src/bindings/DateFns.res.mjs +22 -0
  91. package/src/bindings/EventSource.res +8 -1
  92. package/src/bindings/EventSource.res.mjs +8 -1
  93. package/src/bindings/Express.res +1 -0
  94. package/src/bindings/Hrtime.res +14 -1
  95. package/src/bindings/Hrtime.res.mjs +22 -2
  96. package/src/bindings/Hrtime.resi +4 -0
  97. package/src/bindings/Lodash.res +0 -1
  98. package/src/bindings/NodeJs.res +49 -3
  99. package/src/bindings/NodeJs.res.mjs +11 -3
  100. package/src/bindings/Pino.res +24 -10
  101. package/src/bindings/Pino.res.mjs +14 -8
  102. package/src/bindings/Postgres.gen.ts +8 -0
  103. package/src/bindings/Postgres.res +5 -1
  104. package/src/bindings/Postgres.res.d.mts +5 -0
  105. package/src/bindings/PromClient.res +0 -10
  106. package/src/bindings/PromClient.res.mjs +0 -3
  107. package/src/bindings/Vitest.res +142 -0
  108. package/src/bindings/Vitest.res.mjs +9 -0
  109. package/src/bindings/WebSocket.res +27 -0
  110. package/src/bindings/WebSocket.res.mjs +2 -0
  111. package/src/bindings/Yargs.res +8 -0
  112. package/src/bindings/Yargs.res.mjs +2 -0
  113. package/src/db/EntityHistory.res +7 -7
  114. package/src/db/EntityHistory.res.mjs +9 -9
  115. package/src/db/InternalTable.res +59 -111
  116. package/src/db/InternalTable.res.mjs +73 -104
  117. package/src/db/Table.res +27 -8
  118. package/src/db/Table.res.mjs +25 -14
  119. package/src/sources/Evm.res +84 -0
  120. package/src/sources/Evm.res.mjs +105 -0
  121. package/src/sources/EvmChain.res +94 -0
  122. package/src/sources/EvmChain.res.mjs +60 -0
  123. package/src/sources/Fuel.res +19 -34
  124. package/src/sources/Fuel.res.mjs +34 -16
  125. package/src/sources/FuelSDK.res +38 -0
  126. package/src/sources/FuelSDK.res.mjs +29 -0
  127. package/src/sources/HyperFuel.res +2 -2
  128. package/src/sources/HyperFuel.resi +1 -1
  129. package/src/sources/HyperFuelClient.res +2 -2
  130. package/src/sources/HyperFuelSource.res +33 -13
  131. package/src/sources/HyperFuelSource.res.mjs +24 -16
  132. package/src/sources/HyperSync.res +36 -6
  133. package/src/sources/HyperSync.res.mjs +9 -7
  134. package/src/sources/HyperSync.resi +4 -0
  135. package/src/sources/HyperSyncClient.res +1 -1
  136. package/src/sources/HyperSyncHeightStream.res +47 -116
  137. package/src/sources/HyperSyncHeightStream.res.mjs +46 -73
  138. package/src/sources/HyperSyncSource.res +118 -139
  139. package/src/sources/HyperSyncSource.res.mjs +104 -121
  140. package/src/sources/Rpc.res +86 -14
  141. package/src/sources/Rpc.res.mjs +101 -9
  142. package/src/sources/RpcSource.res +621 -364
  143. package/src/sources/RpcSource.res.mjs +843 -410
  144. package/src/sources/RpcWebSocketHeightStream.res +181 -0
  145. package/src/sources/RpcWebSocketHeightStream.res.mjs +196 -0
  146. package/src/sources/Source.res +7 -5
  147. package/src/sources/SourceManager.res +325 -225
  148. package/src/sources/SourceManager.res.mjs +314 -171
  149. package/src/sources/SourceManager.resi +17 -6
  150. package/src/sources/Svm.res +81 -0
  151. package/src/sources/Svm.res.mjs +90 -0
  152. package/src/tui/Tui.res +247 -0
  153. package/src/tui/Tui.res.mjs +337 -0
  154. package/src/tui/bindings/Ink.res +371 -0
  155. package/src/tui/bindings/Ink.res.mjs +72 -0
  156. package/src/tui/bindings/Style.res +123 -0
  157. package/src/tui/bindings/Style.res.mjs +2 -0
  158. package/src/tui/components/BufferedProgressBar.res +40 -0
  159. package/src/tui/components/BufferedProgressBar.res.mjs +57 -0
  160. package/src/tui/components/CustomHooks.res +122 -0
  161. package/src/tui/components/CustomHooks.res.mjs +179 -0
  162. package/src/tui/components/Messages.res +41 -0
  163. package/src/tui/components/Messages.res.mjs +75 -0
  164. package/src/tui/components/SyncETA.res +174 -0
  165. package/src/tui/components/SyncETA.res.mjs +263 -0
  166. package/src/tui/components/TuiData.res +47 -0
  167. package/src/tui/components/TuiData.res.mjs +34 -0
  168. package/svm.schema.json +112 -0
  169. package/bin.js +0 -48
  170. package/src/EventRegister.res +0 -241
  171. package/src/EventRegister.res.mjs +0 -240
  172. package/src/bindings/Ethers.gen.ts +0 -14
  173. package/src/bindings/Ethers.res +0 -204
  174. package/src/bindings/Ethers.res.mjs +0 -130
  175. /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,174 @@
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
+ if !shouldDisplayEta {
62
+ // Display ETA once all chains have a known height and some blocks have been fetched
63
+ let allChainsStartedFetching = chains->Array.every(chain => {
64
+ chain.knownHeight > 0 && chain.latestFetchedBlockNumber > 0
65
+ })
66
+
67
+ if allChainsStartedFetching {
68
+ setShouldDisplayEta(_ => true)
69
+ }
70
+ }
71
+
72
+ None
73
+ }, [chains])
74
+
75
+ shouldDisplayEta
76
+ }
77
+
78
+ let useEta = (~chains, ~indexerStartTime) => {
79
+ let shouldDisplayEta = useShouldDisplayEta(~chains)
80
+ let (secondsToSub, setSecondsToSub) = React.useState(_ => 0.)
81
+ let (timeSinceStart, setTimeSinceStart) = React.useState(_ => 0.)
82
+
83
+ React.useEffect2(() => {
84
+ setTimeSinceStart(_ => Js.Date.now() -. indexerStartTime->Js.Date.valueOf)
85
+ setSecondsToSub(_ => 0.)
86
+
87
+ let intervalId = Js.Global.setInterval(() => {
88
+ setSecondsToSub(prev => prev +. 1.)
89
+ }, 1000)
90
+
91
+ Some(() => Js.Global.clearInterval(intervalId))
92
+ }, (chains, indexerStartTime))
93
+
94
+ //blocksProcessed/remainingBlocks = timeSoFar/eta
95
+ //eta = (timeSoFar/blocksProcessed) * remainingBlocks
96
+
97
+ let blocksProcessed = getTotalBlocksProcessed(chains)->Int.toFloat
98
+ if shouldDisplayEta && blocksProcessed > 0. {
99
+ let nowDate = Js.Date.now()
100
+ let remainingBlocks = getTotalRemainingBlocks(chains)->Int.toFloat
101
+ let etaFloat = timeSinceStart /. blocksProcessed *. remainingBlocks
102
+ let millisToSub = secondsToSub *. 1000.
103
+ let etaFloat = Pervasives.max(etaFloat -. millisToSub, 0.0) //template this
104
+ let eta = (etaFloat +. nowDate)->Js.Date.fromFloat
105
+ let interval: DateFns.interval = {start: nowDate->Js.Date.fromFloat, end: eta}
106
+ let duration = DateFns.intervalToDuration(interval)
107
+ let formattedDuration = DateFns.formatDuration(
108
+ duration,
109
+ {format: ["hours", "minutes", "seconds"]},
110
+ )
111
+ let outputString = switch formattedDuration {
112
+ | "" => "less than 1 second"
113
+ | formattedDuration => formattedDuration
114
+ }
115
+ Some(outputString)
116
+ } else {
117
+ None
118
+ }
119
+ }
120
+
121
+ module Syncing = {
122
+ @react.component
123
+ let make = (~etaStr) => {
124
+ <Text bold=true>
125
+ <Text> {"Sync Time ETA: "->React.string} </Text>
126
+ <Text> {etaStr->React.string} </Text>
127
+ <Text> {" ("->React.string} </Text>
128
+ <Text color=Primary>
129
+ <Spinner />
130
+ </Text>
131
+ <Text color=Secondary> {" in progress"->React.string} </Text>
132
+ <Text> {")"->React.string} </Text>
133
+ </Text>
134
+ }
135
+ }
136
+
137
+ module Synced = {
138
+ @react.component
139
+ let make = (~latestTimeCaughtUpToHeadStr) => {
140
+ <Text bold=true>
141
+ <Text> {"Time Synced: "->React.string} </Text>
142
+ <Text> {`${latestTimeCaughtUpToHeadStr}`->React.string} </Text>
143
+ <Text> {" ("->React.string} </Text>
144
+ <Text color=Success> {"synced"->React.string} </Text>
145
+ <Text> {")"->React.string} </Text>
146
+ </Text>
147
+ }
148
+ }
149
+
150
+ module Calculating = {
151
+ @react.component
152
+ let make = () => {
153
+ <Text>
154
+ <Text color=Primary>
155
+ <Spinner />
156
+ </Text>
157
+ <Text bold=true> {" Calculating ETA..."->React.string} </Text>
158
+ </Text>
159
+ }
160
+ }
161
+
162
+ @react.component
163
+ let make = (~chains, ~indexerStartTime) => {
164
+ let optEta = useEta(~chains, ~indexerStartTime)
165
+ if isIndexerFullySynced(chains) {
166
+ let latestTimeCaughtUpToHeadStr = getLatestTimeCaughtUpToHead(chains, indexerStartTime)
167
+ <Synced latestTimeCaughtUpToHeadStr /> //TODO add real time
168
+ } else {
169
+ switch optEta {
170
+ | Some(etaStr) => <Syncing etaStr />
171
+ | None => <Calculating />
172
+ }
173
+ }
174
+ }