envio 3.0.0-alpha.21 → 3.0.0-alpha.23
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 +3 -3
- package/bin.mjs +2 -48
- package/evm.schema.json +67 -0
- package/fuel.schema.json +67 -0
- package/index.d.ts +822 -38
- package/index.js +5 -3
- package/package.json +10 -8
- package/rescript.json +5 -9
- package/src/Address.res +4 -5
- package/src/Address.res.mjs +9 -12
- package/src/Api.res +15 -0
- package/src/Api.res.mjs +20 -0
- package/src/Batch.res +32 -34
- package/src/Batch.res.mjs +172 -187
- package/src/Bin.res +89 -0
- package/src/Bin.res.mjs +97 -0
- package/src/ChainFetcher.res +33 -57
- package/src/ChainFetcher.res.mjs +197 -227
- package/src/ChainManager.res +6 -14
- package/src/ChainManager.res.mjs +74 -85
- package/src/ChainMap.res +14 -16
- package/src/ChainMap.res.mjs +38 -38
- package/src/Config.res +193 -135
- package/src/Config.res.mjs +566 -592
- package/src/Core.res +182 -0
- package/src/Core.res.mjs +207 -0
- package/src/Ecosystem.res +25 -4
- package/src/Ecosystem.res.mjs +12 -13
- package/src/Env.res +20 -13
- package/src/Env.res.mjs +124 -113
- package/src/EnvSafe.res +269 -0
- package/src/EnvSafe.res.mjs +296 -0
- package/src/EnvSafe.resi +18 -0
- package/src/Envio.res +37 -26
- package/src/Envio.res.mjs +59 -60
- package/src/ErrorHandling.res +2 -2
- package/src/ErrorHandling.res.mjs +15 -15
- package/src/EventConfigBuilder.res +219 -81
- package/src/EventConfigBuilder.res.mjs +259 -202
- package/src/EventProcessing.res +27 -38
- package/src/EventProcessing.res.mjs +165 -183
- package/src/EventUtils.res +11 -11
- package/src/EventUtils.res.mjs +21 -22
- package/src/EvmTypes.res +0 -1
- package/src/EvmTypes.res.mjs +5 -5
- package/src/FetchState.res +360 -256
- package/src/FetchState.res.mjs +958 -914
- package/src/GlobalState.res +365 -351
- package/src/GlobalState.res.mjs +958 -992
- package/src/GlobalStateManager.res +1 -2
- package/src/GlobalStateManager.res.mjs +36 -44
- package/src/HandlerLoader.res +107 -23
- package/src/HandlerLoader.res.mjs +128 -38
- package/src/HandlerRegister.res +127 -103
- package/src/HandlerRegister.res.mjs +164 -164
- package/src/HandlerRegister.resi +12 -4
- package/src/Hasura.res +35 -22
- package/src/Hasura.res.mjs +158 -167
- package/src/InMemoryStore.res +20 -27
- package/src/InMemoryStore.res.mjs +64 -80
- package/src/InMemoryTable.res +34 -39
- package/src/InMemoryTable.res.mjs +165 -170
- package/src/Internal.res +52 -33
- package/src/Internal.res.mjs +84 -81
- package/src/LazyLoader.res.mjs +55 -61
- package/src/LoadLayer.res +77 -78
- package/src/LoadLayer.res.mjs +160 -189
- package/src/LoadManager.res +16 -21
- package/src/LoadManager.res.mjs +79 -84
- package/src/LogSelection.res +236 -68
- package/src/LogSelection.res.mjs +211 -141
- package/src/Logging.res +13 -9
- package/src/Logging.res.mjs +130 -143
- package/src/Main.res +430 -51
- package/src/Main.res.mjs +530 -271
- package/src/Persistence.res +80 -84
- package/src/Persistence.res.mjs +131 -132
- package/src/PgStorage.res +294 -167
- package/src/PgStorage.res.mjs +799 -817
- package/src/Prometheus.res +50 -58
- package/src/Prometheus.res.mjs +345 -373
- package/src/ReorgDetection.res +22 -24
- package/src/ReorgDetection.res.mjs +100 -106
- package/src/SafeCheckpointTracking.res +7 -7
- package/src/SafeCheckpointTracking.res.mjs +40 -43
- package/src/SimulateItems.res +41 -49
- package/src/SimulateItems.res.mjs +257 -272
- package/src/Sink.res +2 -2
- package/src/Sink.res.mjs +22 -26
- package/src/TableIndices.res +1 -2
- package/src/TableIndices.res.mjs +42 -48
- package/src/TestIndexer.res +196 -189
- package/src/TestIndexer.res.mjs +536 -536
- package/src/TestIndexerProxyStorage.res +16 -16
- package/src/TestIndexerProxyStorage.res.mjs +99 -122
- package/src/TestIndexerWorker.res +4 -0
- package/src/TestIndexerWorker.res.mjs +7 -0
- package/src/Throttler.res +3 -3
- package/src/Throttler.res.mjs +23 -24
- package/src/Time.res +1 -1
- package/src/Time.res.mjs +18 -21
- package/src/TopicFilter.res +3 -3
- package/src/TopicFilter.res.mjs +29 -30
- package/src/UserContext.res +93 -54
- package/src/UserContext.res.mjs +197 -182
- package/src/Utils.res +141 -86
- package/src/Utils.res.mjs +334 -295
- package/src/bindings/BigDecimal.res +0 -2
- package/src/bindings/BigDecimal.res.mjs +19 -23
- package/src/bindings/ClickHouse.res +28 -27
- package/src/bindings/ClickHouse.res.mjs +243 -240
- package/src/bindings/DateFns.res +11 -11
- package/src/bindings/DateFns.res.mjs +7 -7
- package/src/bindings/EventSource.res.mjs +2 -2
- package/src/bindings/Express.res +2 -5
- package/src/bindings/Hrtime.res +2 -2
- package/src/bindings/Hrtime.res.mjs +30 -32
- package/src/bindings/Lodash.res.mjs +1 -1
- package/src/bindings/NodeJs.res +14 -9
- package/src/bindings/NodeJs.res.mjs +20 -20
- package/src/bindings/Pino.res +8 -10
- package/src/bindings/Pino.res.mjs +40 -43
- package/src/bindings/Postgres.res +7 -5
- package/src/bindings/Postgres.res.mjs +9 -9
- package/src/bindings/PromClient.res +17 -2
- package/src/bindings/PromClient.res.mjs +30 -7
- package/src/bindings/SDSL.res.mjs +2 -2
- package/src/bindings/Viem.res +4 -4
- package/src/bindings/Viem.res.mjs +20 -22
- package/src/bindings/Vitest.res +1 -1
- package/src/bindings/Vitest.res.mjs +2 -2
- package/src/bindings/WebSocket.res +1 -1
- package/src/db/EntityHistory.res +9 -3
- package/src/db/EntityHistory.res.mjs +84 -59
- package/src/db/InternalTable.res +62 -60
- package/src/db/InternalTable.res.mjs +271 -203
- package/src/db/Schema.res +1 -2
- package/src/db/Schema.res.mjs +28 -32
- package/src/db/Table.res +28 -27
- package/src/db/Table.res.mjs +276 -292
- package/src/sources/EventRouter.res +21 -16
- package/src/sources/EventRouter.res.mjs +55 -57
- package/src/sources/Evm.res +17 -1
- package/src/sources/Evm.res.mjs +16 -8
- package/src/sources/EvmChain.res +15 -17
- package/src/sources/EvmChain.res.mjs +40 -42
- package/src/sources/Fuel.res +14 -1
- package/src/sources/Fuel.res.mjs +16 -8
- package/src/sources/FuelSDK.res +1 -1
- package/src/sources/FuelSDK.res.mjs +6 -8
- package/src/sources/HyperFuel.res +8 -10
- package/src/sources/HyperFuel.res.mjs +113 -123
- package/src/sources/HyperFuelClient.res.mjs +6 -7
- package/src/sources/HyperFuelSource.res +19 -20
- package/src/sources/HyperFuelSource.res.mjs +339 -356
- package/src/sources/HyperSync.res +11 -13
- package/src/sources/HyperSync.res.mjs +206 -220
- package/src/sources/HyperSyncClient.res +5 -7
- package/src/sources/HyperSyncClient.res.mjs +70 -75
- package/src/sources/HyperSyncHeightStream.res +8 -9
- package/src/sources/HyperSyncHeightStream.res.mjs +78 -86
- package/src/sources/HyperSyncJsonApi.res +18 -15
- package/src/sources/HyperSyncJsonApi.res.mjs +201 -231
- package/src/sources/HyperSyncSource.res +17 -21
- package/src/sources/HyperSyncSource.res.mjs +268 -290
- package/src/sources/Rpc.res +5 -5
- package/src/sources/Rpc.res.mjs +168 -192
- package/src/sources/RpcSource.res +166 -167
- package/src/sources/RpcSource.res.mjs +972 -1046
- package/src/sources/RpcWebSocketHeightStream.res +10 -11
- package/src/sources/RpcWebSocketHeightStream.res.mjs +131 -145
- package/src/sources/SimulateSource.res +1 -1
- package/src/sources/SimulateSource.res.mjs +35 -38
- package/src/sources/Source.res +1 -1
- package/src/sources/Source.res.mjs +3 -3
- package/src/sources/SourceManager.res +39 -20
- package/src/sources/SourceManager.res.mjs +340 -371
- package/src/sources/SourceManager.resi +2 -1
- package/src/sources/Svm.res +12 -5
- package/src/sources/Svm.res.mjs +44 -41
- package/src/tui/Tui.res +23 -12
- package/src/tui/Tui.res.mjs +292 -290
- package/src/tui/bindings/Ink.res +2 -4
- package/src/tui/bindings/Ink.res.mjs +35 -41
- package/src/tui/components/BufferedProgressBar.res +7 -7
- package/src/tui/components/BufferedProgressBar.res.mjs +46 -46
- package/src/tui/components/CustomHooks.res +1 -2
- package/src/tui/components/CustomHooks.res.mjs +102 -122
- package/src/tui/components/Messages.res +1 -2
- package/src/tui/components/Messages.res.mjs +38 -42
- package/src/tui/components/SyncETA.res +10 -11
- package/src/tui/components/SyncETA.res.mjs +178 -196
- package/src/tui/components/TuiData.res +1 -1
- package/src/tui/components/TuiData.res.mjs +7 -6
- package/src/vendored/Rest.res +52 -66
- package/src/vendored/Rest.res.mjs +324 -364
- package/svm.schema.json +67 -0
- package/src/Address.gen.ts +0 -8
- package/src/Config.gen.ts +0 -19
- package/src/Envio.gen.ts +0 -55
- package/src/EvmTypes.gen.ts +0 -6
- package/src/InMemoryStore.gen.ts +0 -6
- package/src/Internal.gen.ts +0 -64
- package/src/PgStorage.gen.ts +0 -10
- package/src/PgStorage.res.d.mts +0 -5
- package/src/Types.ts +0 -56
- package/src/bindings/BigDecimal.gen.ts +0 -14
- package/src/bindings/BigDecimal.res.d.mts +0 -5
- package/src/bindings/BigInt.gen.ts +0 -10
- package/src/bindings/BigInt.res +0 -70
- package/src/bindings/BigInt.res.d.mts +0 -5
- package/src/bindings/BigInt.res.mjs +0 -154
- package/src/bindings/Ethers.res.d.mts +0 -5
- package/src/bindings/Pino.gen.ts +0 -17
- package/src/bindings/Postgres.gen.ts +0 -8
- package/src/bindings/Postgres.res.d.mts +0 -5
- package/src/bindings/Promise.res +0 -67
- package/src/bindings/Promise.res.mjs +0 -26
- package/src/db/InternalTable.gen.ts +0 -36
- package/src/sources/HyperSyncClient.gen.ts +0 -19
package/src/LoadManager.res.mjs
CHANGED
|
@@ -1,79 +1,74 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
-
import * as
|
|
5
|
-
import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";
|
|
4
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
let Call = {};
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
let Group = {};
|
|
10
9
|
|
|
11
10
|
function make() {
|
|
12
11
|
return {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
groups: {},
|
|
13
|
+
isCollecting: false
|
|
14
|
+
};
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
async function schedule(loadManager) {
|
|
19
18
|
loadManager.isCollecting = true;
|
|
20
19
|
await Promise.resolve();
|
|
21
20
|
loadManager.isCollecting = false;
|
|
22
|
-
|
|
23
|
-
Object.keys(groups).forEach(async
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return ;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
if (!Utils.$$Array.isEmpty(inputsToLoad)) {
|
|
43
|
-
try {
|
|
44
|
-
await group.load(inputsToLoad, (function (inputKey, exn) {
|
|
45
|
-
var call = calls[inputKey];
|
|
46
|
-
call.exn = exn;
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
catch (raw_exn){
|
|
50
|
-
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
51
|
-
var exn$1 = Utils.prettifyExn(exn);
|
|
52
|
-
Belt_Array.forEach(currentInputKeys, (function (inputKey) {
|
|
53
|
-
var call = calls[inputKey];
|
|
54
|
-
call.exn = exn$1;
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
21
|
+
let groups = loadManager.groups;
|
|
22
|
+
Object.keys(groups).forEach(async key => {
|
|
23
|
+
let group = groups[key];
|
|
24
|
+
let calls = group.calls;
|
|
25
|
+
let inputsToLoad = [];
|
|
26
|
+
let currentInputKeys = [];
|
|
27
|
+
Utils.Dict.forEachWithKey(calls, (call, inputKey) => {
|
|
28
|
+
if (!call.isLoading) {
|
|
29
|
+
call.isLoading = true;
|
|
30
|
+
currentInputKeys.push(inputKey);
|
|
31
|
+
if (!group.hasInMemory(inputKey)) {
|
|
32
|
+
inputsToLoad.push(call.input);
|
|
33
|
+
return;
|
|
34
|
+
} else {
|
|
35
|
+
return;
|
|
57
36
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (!Utils.$$Array.isEmpty(inputsToLoad)) {
|
|
40
|
+
try {
|
|
41
|
+
await group.load(inputsToLoad, (inputKey, exn) => {
|
|
42
|
+
let call = calls[inputKey];
|
|
43
|
+
call.exn = exn;
|
|
44
|
+
});
|
|
45
|
+
} catch (raw_exn) {
|
|
46
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
47
|
+
let exn$1 = Utils.prettifyExn(exn);
|
|
48
|
+
currentInputKeys.forEach(inputKey => {
|
|
49
|
+
let call = calls[inputKey];
|
|
50
|
+
call.exn = exn$1;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (Utils.$$Array.isEmpty(currentInputKeys)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
currentInputKeys.forEach(inputKey => {
|
|
58
|
+
let call = calls[inputKey];
|
|
59
|
+
Utils.Dict.deleteInPlace(calls, inputKey);
|
|
60
|
+
let exn = call.exn;
|
|
61
|
+
if (exn !== undefined) {
|
|
62
|
+
return call.reject(Utils.prettifyExn(exn));
|
|
63
|
+
} else {
|
|
64
|
+
return call.resolve(group.getUnsafeInMemory(inputKey));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
let latestGroup = groups[key];
|
|
68
|
+
if (Utils.Dict.isEmpty(latestGroup.calls)) {
|
|
69
|
+
return Utils.Dict.deleteInPlace(groups, key);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
function noopHasher(input) {
|
|
@@ -81,17 +76,17 @@ function noopHasher(input) {
|
|
|
81
76
|
}
|
|
82
77
|
|
|
83
78
|
function call(loadManager, input, key, load, hasher, shouldGroup, hasInMemory, getUnsafeInMemory) {
|
|
84
|
-
|
|
79
|
+
let inputKey = hasher === noopHasher ? input : hasher(input);
|
|
85
80
|
if (!shouldGroup && hasInMemory(inputKey)) {
|
|
86
81
|
return Promise.resolve(getUnsafeInMemory(inputKey));
|
|
87
82
|
}
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
let group = loadManager.groups[key];
|
|
84
|
+
let group$1;
|
|
90
85
|
if (group !== undefined) {
|
|
91
86
|
group$1 = group;
|
|
92
87
|
} else {
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
let g_calls = {};
|
|
89
|
+
let g = {
|
|
95
90
|
calls: g_calls,
|
|
96
91
|
load: load,
|
|
97
92
|
getUnsafeInMemory: getUnsafeInMemory,
|
|
@@ -100,21 +95,21 @@ function call(loadManager, input, key, load, hasher, shouldGroup, hasInMemory, g
|
|
|
100
95
|
loadManager.groups[key] = g;
|
|
101
96
|
group$1 = g;
|
|
102
97
|
}
|
|
103
|
-
|
|
98
|
+
let c = group$1.calls[inputKey];
|
|
104
99
|
if (c !== undefined) {
|
|
105
100
|
return c.promise;
|
|
106
101
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
102
|
+
let promise = new Promise((resolve, reject) => {
|
|
103
|
+
let call = {
|
|
104
|
+
input: input,
|
|
105
|
+
resolve: resolve,
|
|
106
|
+
reject: reject,
|
|
107
|
+
exn: undefined,
|
|
108
|
+
promise: null,
|
|
109
|
+
isLoading: false
|
|
110
|
+
};
|
|
111
|
+
group$1.calls[inputKey] = call;
|
|
112
|
+
});
|
|
118
113
|
group$1.calls[inputKey].promise = promise;
|
|
119
114
|
if (!loadManager.isCollecting) {
|
|
120
115
|
schedule(loadManager);
|
|
@@ -123,11 +118,11 @@ function call(loadManager, input, key, load, hasher, shouldGroup, hasInMemory, g
|
|
|
123
118
|
}
|
|
124
119
|
|
|
125
120
|
export {
|
|
126
|
-
Call
|
|
127
|
-
Group
|
|
128
|
-
make
|
|
129
|
-
schedule
|
|
130
|
-
noopHasher
|
|
131
|
-
call
|
|
121
|
+
Call,
|
|
122
|
+
Group,
|
|
123
|
+
make,
|
|
124
|
+
schedule,
|
|
125
|
+
noopHasher,
|
|
126
|
+
call,
|
|
132
127
|
}
|
|
133
128
|
/* Utils Not a pure module */
|
package/src/LogSelection.res
CHANGED
|
@@ -12,7 +12,7 @@ let makeTopicSelection = (~topic0, ~topic1=[], ~topic2=[], ~topic3=[]) =>
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
let hasFilters = ({topic1, topic2, topic3}: Internal.topicSelection) => {
|
|
15
|
-
[topic1, topic2, topic3]->
|
|
15
|
+
[topic1, topic2, topic3]->Array.find(topic => !Utils.Array.isEmpty(topic))->Belt.Option.isSome
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -26,10 +26,10 @@ let compressTopicSelections = (topicSelections: array<Internal.topicSelection>)
|
|
|
26
26
|
|
|
27
27
|
topicSelections->Belt.Array.forEach(selection => {
|
|
28
28
|
if selection->hasFilters {
|
|
29
|
-
selectionsWithFilters->
|
|
29
|
+
selectionsWithFilters->Array.push(selection)->ignore
|
|
30
30
|
} else {
|
|
31
31
|
selection.topic0->Belt.Array.forEach(topic0 => {
|
|
32
|
-
topic0sOfSelectionsWithoutFilters->
|
|
32
|
+
topic0sOfSelectionsWithoutFilters->Array.push(topic0)->ignore
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
})
|
|
@@ -60,6 +60,95 @@ let make = (~addresses, ~topicSelections) => {
|
|
|
60
60
|
type parsedEventFilters = {
|
|
61
61
|
getEventFiltersOrThrow: ChainMap.Chain.t => Internal.eventFilters,
|
|
62
62
|
filterByAddresses: bool,
|
|
63
|
+
// `_gte` from the top-level `block` filter of the user's `where`,
|
|
64
|
+
// resolved at build time (per-chain via the `probeChainId`). The
|
|
65
|
+
// caller uses this to override the per-event `startBlock` — a
|
|
66
|
+
// `where`-derived startBlock always wins over contract-level
|
|
67
|
+
// config, so users can widen or narrow individual event ranges
|
|
68
|
+
// without touching `config.yaml`.
|
|
69
|
+
startBlock: option<int>,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Inner schema for the event `block` filter chunk: `{_gte?}`.
|
|
73
|
+
// `S.strict` rejects `_lte` / `_every` — those are stride/range concepts
|
|
74
|
+
// that only make sense for `onBlock` handlers, not event filters. Typos
|
|
75
|
+
// like `_gt` surface through the same strict-schema error path.
|
|
76
|
+
type eventBlockRange = {_gte: option<int>}
|
|
77
|
+
let eventBlockRangeSchema: S.t<eventBlockRange> = S.object(s => {
|
|
78
|
+
_gte: s.field("_gte", S.option(S.int)),
|
|
79
|
+
})->S.strict
|
|
80
|
+
|
|
81
|
+
// Extract the per-event `startBlock` from a `where` result (or static
|
|
82
|
+
// value). Two-stage parse mirroring `onBlock`: the ecosystem schema
|
|
83
|
+
// strips the outer `block.number` / `block.height` wrapper, then
|
|
84
|
+
// `eventBlockRangeSchema` validates `{_gte?}` strictly — `_lte` and
|
|
85
|
+
// `_every` are rejected with a user-friendly message pointing users
|
|
86
|
+
// at `onBlock` for stride/endBlock semantics.
|
|
87
|
+
//
|
|
88
|
+
// Returns `None` for boolean `where` results, missing `block`, and
|
|
89
|
+
// `block.number: {}` (no `_gte`). Wraps schema errors with the
|
|
90
|
+
// contract/event context so the call-site is obvious in the log.
|
|
91
|
+
let extractStartBlock = (
|
|
92
|
+
where: JSON.t,
|
|
93
|
+
~onEventBlockFilterSchema: S.t<option<unknown>>,
|
|
94
|
+
~contractName: string,
|
|
95
|
+
): option<int> => {
|
|
96
|
+
// `where` may be a bool at runtime even though the static type is
|
|
97
|
+
// `JSON.t` — the user callbacks can return `true`/`false` to keep/skip
|
|
98
|
+
// a chain, and the value reaches here unwrapped. Detect with
|
|
99
|
+
// `typeof` instead of an identity-equal check so ReScript doesn't
|
|
100
|
+
// constant-fold the comparison away for the `JSON.t` nominal type.
|
|
101
|
+
if typeof(where) === #boolean {
|
|
102
|
+
None
|
|
103
|
+
} else {
|
|
104
|
+
try {
|
|
105
|
+
switch where->S.parseOrThrow(onEventBlockFilterSchema) {
|
|
106
|
+
| None => None
|
|
107
|
+
| Some(inner) => (inner->S.parseOrThrow(eventBlockRangeSchema))._gte
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
| S.Raised(exn) =>
|
|
111
|
+
JsError.throwWithMessage(
|
|
112
|
+
`Invalid where configuration for ${contractName}. \`block\` filter is invalid: ${exn
|
|
113
|
+
->Utils.prettifyExn
|
|
114
|
+
->(
|
|
115
|
+
Utils.magic: exn => string
|
|
116
|
+
)}. Only \`_gte\` is supported on event filters — use \`indexer.onBlock\` for \`_lte\` or \`_every\`.`,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build the runtime `chain` argument passed into a `where` callback.
|
|
123
|
+
// Exposes `chain.id` and `chain.<ContractName>.addresses` as plain values
|
|
124
|
+
// on a normal (Object.prototype) JS object. `Dict` is used so the
|
|
125
|
+
// contract name can be a dynamic property key without defineProperty
|
|
126
|
+
// ceremony.
|
|
127
|
+
let makeChainArg = (~contractName: string, ~chainId: int, ~addresses: array<Address.t>) => {
|
|
128
|
+
let chainObj = Dict.make()
|
|
129
|
+
chainObj->Dict.set("id", chainId->Obj.magic)
|
|
130
|
+
chainObj->Dict.set(contractName, {"addresses": addresses}->Obj.magic)
|
|
131
|
+
chainObj
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Build the detection-time `chain` argument. `chain.<ContractName>.addresses`
|
|
135
|
+
// is a getter so the runtime can tell whether the callback actually reads
|
|
136
|
+
// it; the contract sub-object itself is built via `defineProperty` only
|
|
137
|
+
// because its `addresses` field needs the getter — the enclosing chainObj
|
|
138
|
+
// is a plain JS object.
|
|
139
|
+
let makeDetectionChainArg = (
|
|
140
|
+
~contractName: string,
|
|
141
|
+
~chainId: int,
|
|
142
|
+
~getAddresses: unit => array<Address.t>,
|
|
143
|
+
) => {
|
|
144
|
+
let contractObj = Utils.Object.createNullObject()
|
|
145
|
+
contractObj
|
|
146
|
+
->Utils.Object.defineProperty("addresses", {enumerable: true, get: getAddresses})
|
|
147
|
+
->ignore
|
|
148
|
+
let chainObj = Dict.make()
|
|
149
|
+
chainObj->Dict.set("id", chainId->Obj.magic)
|
|
150
|
+
chainObj->Dict.set(contractName, contractObj->Obj.magic)
|
|
151
|
+
chainObj
|
|
63
152
|
}
|
|
64
153
|
|
|
65
154
|
let parseEventFiltersOrThrow = {
|
|
@@ -67,14 +156,18 @@ let parseEventFiltersOrThrow = {
|
|
|
67
156
|
let noopGetter = _ => emptyTopics
|
|
68
157
|
|
|
69
158
|
(
|
|
70
|
-
~eventFilters: option<
|
|
159
|
+
~eventFilters: option<JSON.t>,
|
|
71
160
|
~sighash,
|
|
72
161
|
~params,
|
|
162
|
+
~contractName: string,
|
|
163
|
+
~probeChainId: int,
|
|
164
|
+
~onEventBlockFilterSchema: S.t<option<unknown>>,
|
|
73
165
|
~topic1=noopGetter,
|
|
74
166
|
~topic2=noopGetter,
|
|
75
167
|
~topic3=noopGetter,
|
|
76
168
|
): parsedEventFilters => {
|
|
77
169
|
let filterByAddresses = ref(false)
|
|
170
|
+
let startBlock = ref(None)
|
|
78
171
|
let topic0 = [sighash->EvmTypes.Hex.fromStringUnsafe]
|
|
79
172
|
let default = {
|
|
80
173
|
Internal.topic0,
|
|
@@ -83,44 +176,97 @@ let parseEventFiltersOrThrow = {
|
|
|
83
176
|
topic3: emptyTopics,
|
|
84
177
|
}
|
|
85
178
|
|
|
86
|
-
|
|
87
|
-
|
|
179
|
+
// Build a single topic selection from one indexed-param record (the
|
|
180
|
+
// inside of `params`). Validates that the keys are actual indexed
|
|
181
|
+
// parameters of the event — TS type checking doesn't catch this when
|
|
182
|
+
// `where` is a callback.
|
|
183
|
+
let paramsRecordToTopicSelection = (paramsFilter: dict<JSON.t>) => {
|
|
184
|
+
let filterKeys = paramsFilter->Dict.keysToArray
|
|
185
|
+
switch filterKeys {
|
|
186
|
+
| [] => default
|
|
187
|
+
| _ => {
|
|
188
|
+
filterKeys->Array.forEach(key => {
|
|
189
|
+
if params->Array.includes(key)->not {
|
|
190
|
+
JsError.throwWithMessage(
|
|
191
|
+
`Invalid where configuration. The event doesn't have an indexed parameter "${key}" and can't use it for filtering`,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
{
|
|
196
|
+
Internal.topic0,
|
|
197
|
+
topic1: topic1(paramsFilter),
|
|
198
|
+
topic2: topic2(paramsFilter),
|
|
199
|
+
topic3: topic3(paramsFilter),
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Known top-level `where` keys. `block` is a sibling of `params` — its
|
|
206
|
+
// `_gte` promotes to the event's `startBlock` (extracted separately
|
|
207
|
+
// by `extractStartBlock`). Unknown keys are rejected to catch typos
|
|
208
|
+
// like `parmas` or `blocks` at registration time.
|
|
209
|
+
let acceptedWhereKeys = ["params", "block"]
|
|
210
|
+
|
|
211
|
+
// Parse a `where` value (or the result of calling the dynamic callback)
|
|
212
|
+
// into a list of topic selections.
|
|
213
|
+
//
|
|
214
|
+
// Accepted shapes:
|
|
215
|
+
// - `true` → KeepAll → match the event signature with no narrowing
|
|
216
|
+
// - `false` → SkipAll → no events
|
|
217
|
+
// - `{}` (or `{params: undefined}`) → no narrowing
|
|
218
|
+
// - `{params: {...}}` → single AND-conjunction
|
|
219
|
+
// - `{params: [{...}, {...}]}` → OR of multiple AND-conjunctions
|
|
220
|
+
// - `{block: {number: {_gte: N}}}` → no topic narrowing; startBlock only
|
|
221
|
+
// - `{params: ..., block: ...}` → combined
|
|
222
|
+
//
|
|
223
|
+
// The runtime accepts both the function form (the only form ReScript
|
|
224
|
+
// exposes) and a top-level static object form (TypeScript convenience).
|
|
225
|
+
let parse = (where: JSON.t): array<Internal.topicSelection> => {
|
|
226
|
+
if where === Obj.magic(true) {
|
|
88
227
|
[default]
|
|
89
|
-
} else if
|
|
228
|
+
} else if where === Obj.magic(false) {
|
|
90
229
|
[]
|
|
91
230
|
} else {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Js.Exn.raiseError(
|
|
108
|
-
`Invalid event filters configuration. The event doesn't have an indexed parameter "${key}" and can't use it for filtering`,
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
{
|
|
113
|
-
Internal.topic0,
|
|
114
|
-
topic1: topic1(eventFilter),
|
|
115
|
-
topic2: topic2(eventFilter),
|
|
116
|
-
topic3: topic3(eventFilter),
|
|
117
|
-
}
|
|
118
|
-
}
|
|
231
|
+
// A `where` condition is shaped as `{params?: ..., block?: ...}`.
|
|
232
|
+
// `params` carries the indexed-parameter filter record; `block`
|
|
233
|
+
// carries the per-event block-range filter handled by
|
|
234
|
+
// `extractStartBlock`.
|
|
235
|
+
switch where {
|
|
236
|
+
| Object(obj) => {
|
|
237
|
+
// Catch typos (e.g. `parmas:`) and the legacy flat-filter
|
|
238
|
+
// shape (`{from: ...}`) by rejecting any unknown sibling.
|
|
239
|
+
obj
|
|
240
|
+
->Dict.keysToArray
|
|
241
|
+
->Array.forEach(key => {
|
|
242
|
+
if acceptedWhereKeys->Array.includes(key)->not {
|
|
243
|
+
JsError.throwWithMessage(
|
|
244
|
+
`Invalid where configuration. Unknown field "${key}". Indexed parameter filters must be nested under \`params\` and block-range filters under \`block\``,
|
|
245
|
+
)
|
|
119
246
|
}
|
|
247
|
+
})
|
|
248
|
+
switch obj->Dict.get("params") {
|
|
249
|
+
| None => [default]
|
|
250
|
+
| Some(Object(p)) => [paramsRecordToTopicSelection(p)]
|
|
251
|
+
| Some(Array([])) => [default]
|
|
252
|
+
| Some(Array(arr)) =>
|
|
253
|
+
arr->Array.map(item =>
|
|
254
|
+
switch item {
|
|
255
|
+
| Object(p) => paramsRecordToTopicSelection(p)
|
|
256
|
+
| _ =>
|
|
257
|
+
JsError.throwWithMessage(
|
|
258
|
+
"Invalid where configuration. Each entry in `params` must be an object",
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
| Some(_) =>
|
|
263
|
+
JsError.throwWithMessage(
|
|
264
|
+
"Invalid where configuration. Expected `params` to be an object or an array of objects",
|
|
265
|
+
)
|
|
120
266
|
}
|
|
121
|
-
| _ => Js.Exn.raiseError("Invalid event filters configuration. Expected an object")
|
|
122
267
|
}
|
|
123
|
-
|
|
268
|
+
| _ => JsError.throwWithMessage("Invalid where configuration. Expected an object")
|
|
269
|
+
}
|
|
124
270
|
}
|
|
125
271
|
}
|
|
126
272
|
|
|
@@ -130,48 +276,69 @@ let parseEventFiltersOrThrow = {
|
|
|
130
276
|
_ => static
|
|
131
277
|
}
|
|
132
278
|
| Some(eventFilters) =>
|
|
133
|
-
if
|
|
134
|
-
let fn = eventFilters->(Utils.magic:
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
filterByAddresses := true
|
|
155
|
-
[]
|
|
156
|
-
},
|
|
279
|
+
if typeof(eventFilters) === #function {
|
|
280
|
+
let fn = eventFilters->(Utils.magic: JSON.t => Internal.onEventWhereArgs<_> => JSON.t)
|
|
281
|
+
// Determine whether the callback uses addresses by probing it with
|
|
282
|
+
// a detection chain arg whose `chain.<ContractName>.addresses` getter
|
|
283
|
+
// flips a flag. The probe uses this chain's real configured id, so
|
|
284
|
+
// handlers that branch on `chain.id` are exercised along the path
|
|
285
|
+
// they take for this chain. Event configs are built per-chain, so
|
|
286
|
+
// each chain gets a `filterByAddresses` verdict that matches its
|
|
287
|
+
// own callback behaviour.
|
|
288
|
+
//
|
|
289
|
+
// The probe result is also reused to extract the per-event
|
|
290
|
+
// `startBlock` (from `where.block`) for this chain — a second
|
|
291
|
+
// invocation would risk observing different state for callbacks
|
|
292
|
+
// that close over mutable references.
|
|
293
|
+
let probedResult = try {
|
|
294
|
+
let chain = makeDetectionChainArg(
|
|
295
|
+
~contractName,
|
|
296
|
+
~chainId=probeChainId,
|
|
297
|
+
~getAddresses=() => {
|
|
298
|
+
filterByAddresses := true
|
|
299
|
+
[]
|
|
157
300
|
},
|
|
158
301
|
)
|
|
159
|
-
|
|
302
|
+
Some(fn({chain: chain->Obj.magic}))
|
|
160
303
|
} catch {
|
|
161
|
-
| _ =>
|
|
304
|
+
| _ => None
|
|
305
|
+
}
|
|
306
|
+
switch probedResult {
|
|
307
|
+
| Some(result) =>
|
|
308
|
+
startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, result)
|
|
309
|
+
| None => ()
|
|
162
310
|
}
|
|
163
311
|
if filterByAddresses.contents {
|
|
164
312
|
chain => Internal.Dynamic(
|
|
165
|
-
addresses =>
|
|
313
|
+
addresses => {
|
|
314
|
+
let chainArg = makeChainArg(
|
|
315
|
+
~contractName,
|
|
316
|
+
~chainId=chain->ChainMap.Chain.toChainId,
|
|
317
|
+
~addresses,
|
|
318
|
+
)
|
|
319
|
+
fn({chain: chainArg->Obj.magic})->parse
|
|
320
|
+
},
|
|
166
321
|
)
|
|
167
322
|
} else {
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
323
|
+
// No probed chain referenced the contract — cache as Static
|
|
324
|
+
// per chain to avoid recomputing topic selections each batch.
|
|
325
|
+
// The addresses getter throws: if a code path the probe didn't
|
|
326
|
+
// exercise reads `chain.<Contract>.addresses` at runtime, silent
|
|
327
|
+
// [] would produce wrong topics — throw a user-friendly error
|
|
328
|
+
// instead so the user rewrites the callback to surface the
|
|
329
|
+
// dependency up-front.
|
|
330
|
+
chain => {
|
|
331
|
+
let chainId = chain->ChainMap.Chain.toChainId
|
|
332
|
+
let chainArg = makeDetectionChainArg(~contractName, ~chainId, ~getAddresses=() =>
|
|
333
|
+
JsError.throwWithMessage(
|
|
334
|
+
`Invalid where configuration. Event callback for contract "${contractName}" read \`chain.${contractName}.addresses\` at runtime but the probe didn't detect the access on chainId ${chainId->Int.toString}. Move the \`chain.${contractName}.addresses\` read above any \`chain.id\` branching so the probe picks up the dependency and switches to the dynamic fetch path.`,
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
Internal.Static(fn({chain: chainArg->Obj.magic})->parse)
|
|
338
|
+
}
|
|
173
339
|
}
|
|
174
340
|
} else {
|
|
341
|
+
startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, eventFilters)
|
|
175
342
|
let static: Internal.eventFilters = Static(eventFilters->parse)
|
|
176
343
|
_ => static
|
|
177
344
|
}
|
|
@@ -180,6 +347,7 @@ let parseEventFiltersOrThrow = {
|
|
|
180
347
|
{
|
|
181
348
|
getEventFiltersOrThrow,
|
|
182
349
|
filterByAddresses: filterByAddresses.contents,
|
|
350
|
+
startBlock: startBlock.contents,
|
|
183
351
|
}
|
|
184
352
|
}
|
|
185
353
|
}
|