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,68 @@
1
+ open Belt
2
+ module type State = {
3
+ type t
4
+ type action
5
+ type task
6
+
7
+ let taskReducer: (t, task, ~dispatchAction: action => unit) => promise<unit>
8
+ let actionReducer: (t, action) => (t, array<task>)
9
+ let invalidatedActionReducer: (t, action) => (t, array<task>)
10
+ let getId: t => int
11
+ }
12
+
13
+ module MakeManager = (S: State) => {
14
+ type t = {mutable state: S.t, onError: exn => unit}
15
+
16
+ let make = (
17
+ state: S.t,
18
+ ~onError=e => {
19
+ e->ErrorHandling.make(~msg="Indexer has failed with an unexpected error")->ErrorHandling.log
20
+ NodeJs.process->NodeJs.exitWithCode(Failure)
21
+ },
22
+ ) => {
23
+ state,
24
+ onError,
25
+ }
26
+
27
+ let rec dispatchAction = (~stateId=0, self: t, action: S.action) => {
28
+ try {
29
+ let reducer = if stateId == self.state->S.getId {
30
+ S.actionReducer
31
+ } else {
32
+ S.invalidatedActionReducer
33
+ }
34
+ let (nextState, nextTasks) = reducer(self.state, action)
35
+ self.state = nextState
36
+ nextTasks->Array.forEach(task => dispatchTask(self, task))
37
+ } catch {
38
+ | e => e->self.onError
39
+ }
40
+ }
41
+ and dispatchTask = (self, task: S.task) => {
42
+ let stateId = self.state->S.getId
43
+ Js.Global.setTimeout(() => {
44
+ if stateId !== self.state->S.getId {
45
+ Logging.info("Invalidated task discarded")
46
+ } else {
47
+ try {
48
+ S.taskReducer(self.state, task, ~dispatchAction=action =>
49
+ dispatchAction(~stateId, self, action)
50
+ )
51
+ ->Promise.catch(e => {
52
+ e->self.onError
53
+ Promise.resolve()
54
+ })
55
+ ->ignore
56
+ } catch {
57
+ | e => e->self.onError
58
+ }
59
+ }
60
+ }, 0)->ignore
61
+ }
62
+
63
+ let getState = self => self.state
64
+ let setState = (self: t, state: S.t) => self.state = state
65
+ }
66
+
67
+ module Manager = MakeManager(GlobalState)
68
+ include Manager
@@ -0,0 +1,75 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Logging from "./Logging.res.mjs";
4
+ import * as $$Promise from "./bindings/Promise.res.mjs";
5
+ import * as Process from "process";
6
+ import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
7
+ import * as GlobalState from "./GlobalState.res.mjs";
8
+ import * as ErrorHandling from "./ErrorHandling.res.mjs";
9
+ import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
10
+
11
+ function make(state, onErrorOpt) {
12
+ var onError = onErrorOpt !== undefined ? onErrorOpt : (function (e) {
13
+ ErrorHandling.log(ErrorHandling.make(e, undefined, "Indexer has failed with an unexpected error"));
14
+ Process.exit(1);
15
+ });
16
+ return {
17
+ state: state,
18
+ onError: onError
19
+ };
20
+ }
21
+
22
+ function dispatchAction(stateIdOpt, self, action) {
23
+ var stateId = stateIdOpt !== undefined ? stateIdOpt : 0;
24
+ try {
25
+ var reducer = stateId === GlobalState.getId(self.state) ? GlobalState.actionReducer : GlobalState.invalidatedActionReducer;
26
+ var match = reducer(self.state, action);
27
+ self.state = match[0];
28
+ return Belt_Array.forEach(match[1], (function (task) {
29
+ dispatchTask(self, task);
30
+ }));
31
+ }
32
+ catch (raw_e){
33
+ var e = Caml_js_exceptions.internalToOCamlException(raw_e);
34
+ return self.onError(e);
35
+ }
36
+ }
37
+
38
+ function dispatchTask(self, task) {
39
+ var stateId = GlobalState.getId(self.state);
40
+ setTimeout((function () {
41
+ if (stateId !== GlobalState.getId(self.state)) {
42
+ return Logging.info("Invalidated task discarded");
43
+ }
44
+ try {
45
+ $$Promise.$$catch(GlobalState.taskReducer(self.state, task, (function (action) {
46
+ dispatchAction(stateId, self, action);
47
+ })), (function (e) {
48
+ self.onError(e);
49
+ return Promise.resolve();
50
+ }));
51
+ return ;
52
+ }
53
+ catch (raw_e){
54
+ var e = Caml_js_exceptions.internalToOCamlException(raw_e);
55
+ return self.onError(e);
56
+ }
57
+ }), 0);
58
+ }
59
+
60
+ function getState(self) {
61
+ return self.state;
62
+ }
63
+
64
+ function setState(self, state) {
65
+ self.state = state;
66
+ }
67
+
68
+ export {
69
+ make ,
70
+ dispatchAction ,
71
+ dispatchTask ,
72
+ getState ,
73
+ setState ,
74
+ }
75
+ /* Logging Not a pure module */
@@ -0,0 +1,7 @@
1
+ type t
2
+
3
+ let make: (GlobalState.t, ~onError: exn => unit=?) => t
4
+ let dispatchAction: (~stateId: int=?, t, GlobalState.action) => unit
5
+ let dispatchTask: (t, GlobalState.task) => unit
6
+ let getState: t => GlobalState.t
7
+ let setState: (t, GlobalState.t) => unit
@@ -0,0 +1,89 @@
1
+ @module("node:fs/promises")
2
+ external globIterator: string => Utils.asyncIterator<string> = "glob"
3
+
4
+ // Register tsx for TypeScript handler support
5
+ // Wrapped in try-catch because if tsx is already loaded via --import (e.g., in tests),
6
+ // calling module.register again will throw an error
7
+ try {
8
+ NodeJs.Module.register("tsx/esm", NodeJs.ImportMeta.url)
9
+ } catch {
10
+ | _ => () // tsx already loaded, ignore
11
+ }
12
+
13
+ // Convert a relative path to a file:// URL for dynamic import
14
+ // Paths are resolved relative to process.cwd() (project root)
15
+ let toImportUrl = (relativePath: string) => {
16
+ let absolutePath = NodeJs.Path.resolve([NodeJs.Process.cwd(), relativePath])->NodeJs.Path.toString
17
+ NodeJs.Url.pathToFileURL(absolutePath)->NodeJs.Url.toString
18
+ }
19
+
20
+ let registerContractHandlers = async (~contractName, ~handler: option<string>) => {
21
+ switch handler {
22
+ | None => ()
23
+ | Some(handlerPath) =>
24
+ try {
25
+ let _ = await Utils.importPath(toImportUrl(handlerPath))
26
+ } catch {
27
+ | exn =>
28
+ Logging.errorWithExn(
29
+ exn,
30
+ `Failed to load handler file for contract ${contractName}: ${handlerPath}`,
31
+ )
32
+ Js.Exn.raiseError(`Failed to load handler file for contract ${contractName}: ${handlerPath}`)
33
+ }
34
+ }
35
+ }
36
+
37
+ let autoLoadFromSrcHandlers = async (~handlers: string) => {
38
+ // Relative to cwd (project root)
39
+ let srcPattern = `./${handlers}/**/*.{js,mjs,ts}`
40
+ let handlerFiles = try {
41
+ let iterator = globIterator(srcPattern)
42
+ let files = await iterator->Utils.Array.fromAsyncIterator
43
+ // Filter out test and spec files
44
+ files->Js.Array2.filter(file => {
45
+ !(
46
+ file->Js.String2.includes(".test.") ||
47
+ file->Js.String2.includes(".spec.") ||
48
+ file->Js.String2.includes("_test.")
49
+ )
50
+ })
51
+ } catch {
52
+ | exn =>
53
+ Js.Exn.raiseError(
54
+ `Failed to glob src/handlers directory for auto-loading handlers. Pattern: ${srcPattern}. Before continuing, check that you're using Node.js >=22 version. Error: ${exn
55
+ ->Utils.prettifyExn
56
+ ->Obj.magic}`,
57
+ )
58
+ }
59
+
60
+ // Import handler files using absolute file:// URLs resolved from cwd
61
+ let _ =
62
+ await handlerFiles
63
+ ->Js.Array2.map(file => {
64
+ Utils.importPath(toImportUrl(file))->Promise.catch(exn => {
65
+ Logging.errorWithExn(exn, `Failed to auto-load handler file: ${file}`)
66
+ Js.Exn.raiseError(`Failed to auto-load handler file: ${file}`)
67
+ })
68
+ })
69
+ ->Promise.all
70
+ }
71
+
72
+ // Register all handlers - must be called BEFORE creating the final config
73
+ // so that event registrations are captured in the config
74
+ let registerAllHandlers = async (~config: Config.t) => {
75
+ HandlerRegister.startRegistration(~ecosystem=config.ecosystem, ~multichain=config.multichain)
76
+
77
+ // Auto-load all .js files from src/handlers directory
78
+ await autoLoadFromSrcHandlers(~handlers=config.handlers)
79
+
80
+ // Load contract-specific handlers
81
+ let _ =
82
+ await config.contractHandlers
83
+ ->Js.Array2.map(({name, handler}) => {
84
+ registerContractHandlers(~contractName=name, ~handler)
85
+ })
86
+ ->Promise.all
87
+
88
+ HandlerRegister.finishRegistration()
89
+ }
@@ -0,0 +1,79 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Url from "url";
4
+ import * as Path from "path";
5
+ import * as Utils from "./Utils.res.mjs";
6
+ import * as Js_exn from "rescript/lib/es6/js_exn.js";
7
+ import * as Logging from "./Logging.res.mjs";
8
+ import * as $$Promise from "./bindings/Promise.res.mjs";
9
+ import * as Process from "process";
10
+ import * as Nodemodule from "node:module";
11
+ import * as HandlerRegister from "./HandlerRegister.res.mjs";
12
+ import * as Promises from "node:fs/promises";
13
+ import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
14
+
15
+ try {
16
+ Nodemodule.register("tsx/esm", import.meta.url);
17
+ }
18
+ catch (exn){
19
+
20
+ }
21
+
22
+ function toImportUrl(relativePath) {
23
+ var absolutePath = Path.resolve(Process.cwd(), relativePath);
24
+ return Url.pathToFileURL(absolutePath).toString();
25
+ }
26
+
27
+ async function registerContractHandlers(contractName, handler) {
28
+ if (handler === undefined) {
29
+ return ;
30
+ }
31
+ try {
32
+ await import(toImportUrl(handler));
33
+ return ;
34
+ }
35
+ catch (raw_exn){
36
+ var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
37
+ Logging.errorWithExn(exn, "Failed to load handler file for contract " + contractName + ": " + handler);
38
+ return Js_exn.raiseError("Failed to load handler file for contract " + contractName + ": " + handler);
39
+ }
40
+ }
41
+
42
+ async function autoLoadFromSrcHandlers(handlers) {
43
+ var srcPattern = "./" + handlers + "/**/*.{js,mjs,ts}";
44
+ var handlerFiles;
45
+ try {
46
+ var iterator = Promises.glob(srcPattern);
47
+ var files = await Utils.$$Array.fromAsyncIterator(iterator);
48
+ handlerFiles = files.filter(function (file) {
49
+ return !(file.includes(".test.") || file.includes(".spec.") || file.includes("_test."));
50
+ });
51
+ }
52
+ catch (raw_exn){
53
+ var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
54
+ handlerFiles = Js_exn.raiseError("Failed to glob src/handlers directory for auto-loading handlers. Pattern: " + srcPattern + ". Before continuing, check that you're using Node.js >=22 version. Error: " + Utils.prettifyExn(exn));
55
+ }
56
+ await Promise.all(handlerFiles.map(function (file) {
57
+ return $$Promise.$$catch(import(toImportUrl(file)), (function (exn) {
58
+ Logging.errorWithExn(exn, "Failed to auto-load handler file: " + file);
59
+ return Js_exn.raiseError("Failed to auto-load handler file: " + file);
60
+ }));
61
+ }));
62
+ }
63
+
64
+ async function registerAllHandlers(config) {
65
+ HandlerRegister.startRegistration(config.ecosystem, config.multichain);
66
+ await autoLoadFromSrcHandlers(config.handlers);
67
+ await Promise.all(config.contractHandlers.map(function (param) {
68
+ return registerContractHandlers(param.name, param.handler);
69
+ }));
70
+ return HandlerRegister.finishRegistration();
71
+ }
72
+
73
+ export {
74
+ toImportUrl ,
75
+ registerContractHandlers ,
76
+ autoLoadFromSrcHandlers ,
77
+ registerAllHandlers ,
78
+ }
79
+ /* Not a pure module */
@@ -0,0 +1,357 @@
1
+ type eventRegistration = {
2
+ handler: option<Internal.handler>,
3
+ contractRegister: option<Internal.contractRegister>,
4
+ eventOptions: option<Internal.eventOptions<Internal.eventFilters>>,
5
+ }
6
+
7
+ let empty = {
8
+ handler: None,
9
+ contractRegister: None,
10
+ eventOptions: None,
11
+ }
12
+
13
+ let eventRegistrations: Js.Dict.t<eventRegistration> = Js.Dict.empty()
14
+
15
+ let getKey = (~contractName, ~eventName) => contractName ++ "." ++ eventName
16
+
17
+ let get = (~contractName, ~eventName) => {
18
+ switch eventRegistrations->Utils.Dict.dangerouslyGetNonOption(getKey(~contractName, ~eventName)) {
19
+ | Some(existing) => existing
20
+ | None => empty
21
+ }
22
+ }
23
+
24
+ let set = (~contractName, ~eventName, registration) => {
25
+ eventRegistrations->Js.Dict.set(getKey(~contractName, ~eventName), registration)
26
+ }
27
+
28
+ type registrations = {onBlockByChainId: dict<array<Internal.onBlockConfig>>}
29
+
30
+ type activeRegistration = {
31
+ ecosystem: Ecosystem.t,
32
+ multichain: Config.multichain,
33
+ registrations: registrations,
34
+ mutable finished: bool,
35
+ }
36
+
37
+ let activeRegistration = ref(None)
38
+
39
+ // Might happen for tests when the handler file
40
+ // is imported by a non-envio process (eg mocha)
41
+ // and initialized before we started registration.
42
+ // So we track them here to register when the startRegistration is called.
43
+ // Theoretically we could keep preRegistration without an explicit start
44
+ // but I want it to be this way, so for the actual indexer run
45
+ // an error is thrown with the exact stack trace where the handler was registered.
46
+ let preRegistered = []
47
+
48
+ let withRegistration = (fn: activeRegistration => unit) => {
49
+ switch activeRegistration.contents {
50
+ | None => preRegistered->Belt.Array.push(fn)
51
+ | Some(r) =>
52
+ if r.finished {
53
+ Js.Exn.raiseError(
54
+ "The indexer finished initializing, so no more handlers can be registered. Make sure the handlers are registered on the top level of the file.",
55
+ )
56
+ } else {
57
+ fn(r)
58
+ }
59
+ }
60
+ }
61
+
62
+ let startRegistration = (~ecosystem, ~multichain) => {
63
+ let r = {
64
+ ecosystem,
65
+ multichain,
66
+ registrations: {
67
+ onBlockByChainId: Js.Dict.empty(),
68
+ },
69
+ finished: false,
70
+ }
71
+ activeRegistration.contents = Some(r)
72
+ while preRegistered->Js.Array2.length > 0 {
73
+ // Loop + cleanup in one go
74
+ switch preRegistered->Js.Array2.pop {
75
+ | Some(fn) => fn(r)
76
+ | None => ()
77
+ }
78
+ }
79
+ }
80
+
81
+ let finishRegistration = () => {
82
+ switch activeRegistration.contents {
83
+ | Some(r) => {
84
+ r.finished = true
85
+ r.registrations
86
+ }
87
+ | None =>
88
+ Js.Exn.raiseError("The indexer has not started registering handlers, so can't finish it.")
89
+ }
90
+ }
91
+
92
+ let isPendingRegistration = () => {
93
+ switch activeRegistration.contents {
94
+ | Some(r) => !r.finished
95
+ | None => false
96
+ }
97
+ }
98
+
99
+ let onBlockOptionsSchema = S.schema(s =>
100
+ {
101
+ "name": s.matches(S.string),
102
+ "chain": s.matches(S.int),
103
+ "interval": s.matches(S.option(S.int->S.intMin(1))->S.Option.getOr(1)),
104
+ "startBlock": s.matches(S.option(S.int)),
105
+ "endBlock": s.matches(S.option(S.int)),
106
+ }
107
+ )
108
+
109
+ let onBlock = (rawOptions: unknown, handler: Internal.onBlockArgs => promise<unit>) => {
110
+ withRegistration(registration => {
111
+ // We need to get timestamp for ordered multichain mode
112
+ switch registration.multichain {
113
+ | Unordered => ()
114
+ | Ordered =>
115
+ Js.Exn.raiseError(
116
+ "Block Handlers are not supported for ordered multichain mode. Please reach out to the Envio team if you need this feature. Or enable unordered multichain mode by removing `multichain: ordered` from the config.yaml file.",
117
+ )
118
+ }
119
+
120
+ let options = rawOptions->S.parseOrThrow(onBlockOptionsSchema)
121
+ let chainId = switch options["chain"] {
122
+ | chainId => chainId
123
+ // Dmitry: I want to add names for chains in the future
124
+ // and to be able to use them as a lookup.
125
+ // To do so, we'll need to pass a config during reigstration
126
+ // instead of isInitialized check.
127
+ }
128
+
129
+ let onBlockByChainId = registration.registrations.onBlockByChainId
130
+
131
+ switch onBlockByChainId->Utils.Dict.dangerouslyGetNonOption(chainId->Belt.Int.toString) {
132
+ | None =>
133
+ onBlockByChainId->Utils.Dict.setByInt(
134
+ chainId,
135
+ [
136
+ (
137
+ {
138
+ index: 0,
139
+ name: options["name"],
140
+ startBlock: options["startBlock"],
141
+ endBlock: options["endBlock"],
142
+ interval: options["interval"],
143
+ chainId,
144
+ handler,
145
+ }: Internal.onBlockConfig
146
+ ),
147
+ ],
148
+ )
149
+ | Some(onBlockConfigs) =>
150
+ onBlockConfigs->Belt.Array.push(
151
+ (
152
+ {
153
+ index: onBlockConfigs->Belt.Array.length,
154
+ name: options["name"],
155
+ startBlock: options["startBlock"],
156
+ endBlock: options["endBlock"],
157
+ interval: options["interval"],
158
+ chainId,
159
+ handler,
160
+ }: Internal.onBlockConfig
161
+ ),
162
+ )
163
+ }
164
+ })
165
+ }
166
+
167
+ let getHandler = (~contractName, ~eventName) => get(~contractName, ~eventName).handler
168
+
169
+ let getContractRegister = (~contractName, ~eventName) =>
170
+ get(~contractName, ~eventName).contractRegister
171
+
172
+ let getEventFilters = (~contractName, ~eventName) =>
173
+ get(~contractName, ~eventName).eventOptions
174
+ ->Belt.Option.flatMap(value => value.eventFilters)
175
+ ->(Utils.magic: option<Internal.eventFilters> => option<Js.Json.t>)
176
+
177
+ let isWildcard = (~contractName, ~eventName) =>
178
+ get(~contractName, ~eventName).eventOptions
179
+ ->Belt.Option.flatMap(value => value.wildcard)
180
+ ->Belt.Option.getWithDefault(false)
181
+
182
+ let hasRegistration = (~contractName, ~eventName) => {
183
+ let r = get(~contractName, ~eventName)
184
+ r.handler->Belt.Option.isSome || r.contractRegister->Belt.Option.isSome
185
+ }
186
+
187
+ type eventNamespace = {contractName: string, eventName: string}
188
+
189
+ let raiseDuplicateRegistration = (~contractName, ~eventName, ~msg, ~logger) => {
190
+ let fullMsg = msg ++ " for " ++ contractName ++ "." ++ eventName
191
+ Logging.createChildFrom(~logger, ~params={contractName, eventName})->Logging.childError(fullMsg)
192
+ Js.Exn.raiseError(fullMsg)
193
+ }
194
+
195
+ let eventFiltersMatch = (a: option<Internal.eventFilters>, b: option<Internal.eventFilters>) => {
196
+ switch (a, b) {
197
+ | (None, None) => true
198
+ | (Some(Static(a)), Some(Static(b))) => a == b
199
+ | (Some(Dynamic(a)), Some(Dynamic(b))) => a === b
200
+ | _ => false
201
+ }
202
+ }
203
+
204
+ let eventOptionsMatch = (
205
+ existing: option<Internal.eventOptions<Internal.eventFilters>>,
206
+ incoming: option<Internal.eventOptions<Internal.eventFilters>>,
207
+ ) => {
208
+ switch (existing, incoming) {
209
+ | (None, None) => true
210
+ | (Some(a), Some(b)) =>
211
+ a.wildcard === b.wildcard && eventFiltersMatch(a.eventFilters, b.eventFilters)
212
+ | _ => false
213
+ }
214
+ }
215
+
216
+ let setEventOptions = (~contractName, ~eventName, ~eventOptions, ~logger=Logging.getLogger()) => {
217
+ switch eventOptions {
218
+ | Some(value) =>
219
+ let value =
220
+ value->(
221
+ Utils.magic: Internal.eventOptions<'eventFilters> => Internal.eventOptions<
222
+ Internal.eventFilters,
223
+ >
224
+ )
225
+ let t = get(~contractName, ~eventName)
226
+ switch t.eventOptions {
227
+ | None => set(~contractName, ~eventName, {...t, eventOptions: Some(value)})
228
+ | Some(existingValue) =>
229
+ if !eventOptionsMatch(Some(existingValue), Some(value)) {
230
+ raiseDuplicateRegistration(
231
+ ~contractName,
232
+ ~eventName,
233
+ ~msg="Cannot register handler with different options. Make sure all handlers for the same event use identical options (wildcard, eventFilters)",
234
+ ~logger,
235
+ )
236
+ }
237
+ }
238
+ | None => ()
239
+ }
240
+ }
241
+
242
+ let setHandler = (
243
+ ~contractName,
244
+ ~eventName,
245
+ handler,
246
+ ~eventOptions,
247
+ ~logger=Logging.getLogger(),
248
+ ) => {
249
+ withRegistration(_registration => {
250
+ let t = get(~contractName, ~eventName)
251
+ let newHandler = handler->(Utils.magic: Internal.genericHandler<'args> => Internal.handler)
252
+ switch t.handler {
253
+ | None =>
254
+ setEventOptions(~contractName, ~eventName, ~eventOptions, ~logger)
255
+ let t = get(~contractName, ~eventName)
256
+ set(
257
+ ~contractName,
258
+ ~eventName,
259
+ {
260
+ ...t,
261
+ handler: Some(newHandler),
262
+ },
263
+ )
264
+ | Some(prevHandler) =>
265
+ let incomingEventOptions =
266
+ eventOptions->Belt.Option.map(v =>
267
+ v->(
268
+ Utils.magic: Internal.eventOptions<'eventFilters> => Internal.eventOptions<
269
+ Internal.eventFilters,
270
+ >
271
+ )
272
+ )
273
+ if eventOptionsMatch(t.eventOptions, incomingEventOptions) {
274
+ let composedHandler: Internal.handler = async args => {
275
+ await prevHandler(args)
276
+ await newHandler(args)
277
+ }
278
+ set(
279
+ ~contractName,
280
+ ~eventName,
281
+ {
282
+ ...t,
283
+ handler: Some(composedHandler),
284
+ },
285
+ )
286
+ } else {
287
+ raiseDuplicateRegistration(
288
+ ~contractName,
289
+ ~eventName,
290
+ ~msg="Cannot register a second handler with different options. Make sure all handlers for the same event use identical options (wildcard, eventFilters)",
291
+ ~logger,
292
+ )
293
+ }
294
+ }
295
+ })
296
+ }
297
+
298
+ let setContractRegister = (
299
+ ~contractName,
300
+ ~eventName,
301
+ contractRegister,
302
+ ~eventOptions,
303
+ ~logger=Logging.getLogger(),
304
+ ) => {
305
+ withRegistration(_registration => {
306
+ let t = get(~contractName, ~eventName)
307
+ let newContractRegister =
308
+ contractRegister->(
309
+ Utils.magic: Internal.genericContractRegister<
310
+ Internal.genericContractRegisterArgs<'event, 'context>,
311
+ > => Internal.contractRegister
312
+ )
313
+ switch t.contractRegister {
314
+ | None =>
315
+ setEventOptions(~contractName, ~eventName, ~eventOptions, ~logger)
316
+ let t = get(~contractName, ~eventName)
317
+ set(
318
+ ~contractName,
319
+ ~eventName,
320
+ {
321
+ ...t,
322
+ contractRegister: Some(newContractRegister),
323
+ },
324
+ )
325
+ | Some(prevContractRegister) =>
326
+ let incomingEventOptions =
327
+ eventOptions->Belt.Option.map(v =>
328
+ v->(
329
+ Utils.magic: Internal.eventOptions<'eventFilters> => Internal.eventOptions<
330
+ Internal.eventFilters,
331
+ >
332
+ )
333
+ )
334
+ if eventOptionsMatch(t.eventOptions, incomingEventOptions) {
335
+ let composedContractRegister: Internal.contractRegister = async args => {
336
+ await prevContractRegister(args)
337
+ await newContractRegister(args)
338
+ }
339
+ set(
340
+ ~contractName,
341
+ ~eventName,
342
+ {
343
+ ...t,
344
+ contractRegister: Some(composedContractRegister),
345
+ },
346
+ )
347
+ } else {
348
+ raiseDuplicateRegistration(
349
+ ~contractName,
350
+ ~eventName,
351
+ ~msg="Cannot register a second contractRegister with different options. Make sure all handlers for the same event use identical options (wildcard, eventFilters)",
352
+ ~logger,
353
+ )
354
+ }
355
+ }
356
+ })
357
+ }