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.
- package/README.md +164 -30
- package/bin.mjs +49 -0
- package/evm.schema.json +79 -169
- package/fuel.schema.json +50 -21
- package/index.d.ts +497 -1
- package/index.js +4 -0
- package/package.json +42 -31
- package/rescript.json +4 -1
- package/src/Batch.res +11 -8
- package/src/Batch.res.mjs +11 -9
- package/src/ChainFetcher.res +531 -0
- package/src/ChainFetcher.res.mjs +339 -0
- package/src/ChainManager.res +190 -0
- package/src/ChainManager.res.mjs +166 -0
- package/src/Change.res +3 -3
- package/src/Config.gen.ts +19 -0
- package/src/Config.res +737 -22
- package/src/Config.res.mjs +703 -26
- package/src/{Indexer.res → Ctx.res} +1 -1
- package/src/Ecosystem.res +9 -124
- package/src/Ecosystem.res.mjs +19 -160
- package/src/Env.res +30 -74
- package/src/Env.res.mjs +25 -87
- package/src/Envio.gen.ts +3 -1
- package/src/Envio.res +20 -9
- package/src/EventProcessing.res +469 -0
- package/src/EventProcessing.res.mjs +337 -0
- package/src/EvmTypes.gen.ts +6 -0
- package/src/EvmTypes.res +1 -0
- package/src/FetchState.res +1256 -639
- package/src/FetchState.res.mjs +1135 -612
- package/src/GlobalState.res +1190 -0
- package/src/GlobalState.res.mjs +1183 -0
- package/src/GlobalStateManager.res +68 -0
- package/src/GlobalStateManager.res.mjs +75 -0
- package/src/GlobalStateManager.resi +7 -0
- package/src/HandlerLoader.res +89 -0
- package/src/HandlerLoader.res.mjs +79 -0
- package/src/HandlerRegister.res +357 -0
- package/src/HandlerRegister.res.mjs +299 -0
- package/src/{EventRegister.resi → HandlerRegister.resi} +13 -13
- package/src/Hasura.res +111 -175
- package/src/Hasura.res.mjs +88 -150
- package/src/InMemoryStore.res +1 -1
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/InMemoryTable.res +1 -1
- package/src/InMemoryTable.res.mjs +1 -1
- package/src/Internal.gen.ts +4 -0
- package/src/Internal.res +230 -12
- package/src/Internal.res.mjs +115 -1
- package/src/LoadLayer.res +444 -0
- package/src/LoadLayer.res.mjs +296 -0
- package/src/LoadLayer.resi +32 -0
- package/src/LogSelection.res +33 -27
- package/src/LogSelection.res.mjs +6 -0
- package/src/Logging.res +21 -7
- package/src/Logging.res.mjs +16 -8
- package/src/Main.res +377 -0
- package/src/Main.res.mjs +339 -0
- package/src/Persistence.res +7 -21
- package/src/Persistence.res.mjs +3 -3
- package/src/PgStorage.gen.ts +10 -0
- package/src/PgStorage.res +116 -69
- package/src/PgStorage.res.d.mts +5 -0
- package/src/PgStorage.res.mjs +93 -50
- package/src/Prometheus.res +294 -224
- package/src/Prometheus.res.mjs +353 -340
- package/src/ReorgDetection.res +6 -10
- package/src/ReorgDetection.res.mjs +6 -6
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +2 -2
- package/src/Sink.res +4 -2
- package/src/Sink.res.mjs +2 -1
- package/src/TableIndices.res +0 -1
- package/src/TestIndexer.res +692 -0
- package/src/TestIndexer.res.mjs +527 -0
- package/src/TestIndexerProxyStorage.res +205 -0
- package/src/TestIndexerProxyStorage.res.mjs +151 -0
- package/src/TopicFilter.res +1 -1
- package/src/Types.ts +1 -1
- package/src/UserContext.res +424 -0
- package/src/UserContext.res.mjs +279 -0
- package/src/Utils.res +97 -26
- package/src/Utils.res.mjs +91 -44
- package/src/bindings/BigInt.res +10 -0
- package/src/bindings/BigInt.res.mjs +15 -0
- package/src/bindings/ClickHouse.res +120 -23
- package/src/bindings/ClickHouse.res.mjs +118 -28
- package/src/bindings/DateFns.res +74 -0
- package/src/bindings/DateFns.res.mjs +22 -0
- package/src/bindings/EventSource.res +8 -1
- package/src/bindings/EventSource.res.mjs +8 -1
- package/src/bindings/Express.res +1 -0
- package/src/bindings/Hrtime.res +14 -1
- package/src/bindings/Hrtime.res.mjs +22 -2
- package/src/bindings/Hrtime.resi +4 -0
- package/src/bindings/Lodash.res +0 -1
- package/src/bindings/NodeJs.res +49 -3
- package/src/bindings/NodeJs.res.mjs +11 -3
- package/src/bindings/Pino.res +24 -10
- package/src/bindings/Pino.res.mjs +14 -8
- package/src/bindings/Postgres.gen.ts +8 -0
- package/src/bindings/Postgres.res +5 -1
- package/src/bindings/Postgres.res.d.mts +5 -0
- package/src/bindings/PromClient.res +0 -10
- package/src/bindings/PromClient.res.mjs +0 -3
- package/src/bindings/Vitest.res +142 -0
- package/src/bindings/Vitest.res.mjs +9 -0
- package/src/bindings/WebSocket.res +27 -0
- package/src/bindings/WebSocket.res.mjs +2 -0
- package/src/bindings/Yargs.res +8 -0
- package/src/bindings/Yargs.res.mjs +2 -0
- package/src/db/EntityHistory.res +7 -7
- package/src/db/EntityHistory.res.mjs +9 -9
- package/src/db/InternalTable.res +59 -111
- package/src/db/InternalTable.res.mjs +73 -104
- package/src/db/Table.res +27 -8
- package/src/db/Table.res.mjs +25 -14
- package/src/sources/Evm.res +84 -0
- package/src/sources/Evm.res.mjs +105 -0
- package/src/sources/EvmChain.res +94 -0
- package/src/sources/EvmChain.res.mjs +60 -0
- package/src/sources/Fuel.res +19 -34
- package/src/sources/Fuel.res.mjs +34 -16
- package/src/sources/FuelSDK.res +38 -0
- package/src/sources/FuelSDK.res.mjs +29 -0
- package/src/sources/HyperFuel.res +2 -2
- package/src/sources/HyperFuel.resi +1 -1
- package/src/sources/HyperFuelClient.res +2 -2
- package/src/sources/HyperFuelSource.res +33 -13
- package/src/sources/HyperFuelSource.res.mjs +24 -16
- package/src/sources/HyperSync.res +36 -6
- package/src/sources/HyperSync.res.mjs +9 -7
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncClient.res +1 -1
- package/src/sources/HyperSyncHeightStream.res +47 -116
- package/src/sources/HyperSyncHeightStream.res.mjs +46 -73
- package/src/sources/HyperSyncSource.res +118 -139
- package/src/sources/HyperSyncSource.res.mjs +104 -121
- package/src/sources/Rpc.res +86 -14
- package/src/sources/Rpc.res.mjs +101 -9
- package/src/sources/RpcSource.res +621 -364
- package/src/sources/RpcSource.res.mjs +843 -410
- package/src/sources/RpcWebSocketHeightStream.res +181 -0
- package/src/sources/RpcWebSocketHeightStream.res.mjs +196 -0
- package/src/sources/Source.res +7 -5
- package/src/sources/SourceManager.res +325 -225
- package/src/sources/SourceManager.res.mjs +314 -171
- package/src/sources/SourceManager.resi +17 -6
- package/src/sources/Svm.res +81 -0
- package/src/sources/Svm.res.mjs +90 -0
- package/src/tui/Tui.res +247 -0
- package/src/tui/Tui.res.mjs +337 -0
- package/src/tui/bindings/Ink.res +371 -0
- package/src/tui/bindings/Ink.res.mjs +72 -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 +122 -0
- package/src/tui/components/CustomHooks.res.mjs +179 -0
- package/src/tui/components/Messages.res +41 -0
- package/src/tui/components/Messages.res.mjs +75 -0
- package/src/tui/components/SyncETA.res +174 -0
- package/src/tui/components/SyncETA.res.mjs +263 -0
- package/src/tui/components/TuiData.res +47 -0
- package/src/tui/components/TuiData.res.mjs +34 -0
- package/svm.schema.json +112 -0
- package/bin.js +0 -48
- package/src/EventRegister.res +0 -241
- package/src/EventRegister.res.mjs +0 -240
- package/src/bindings/Ethers.gen.ts +0 -14
- package/src/bindings/Ethers.res +0 -204
- package/src/bindings/Ethers.res.mjs +0 -130
- /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,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
|
+
}
|