envio 3.3.0-alpha.8 → 3.3.0-rc.0
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/index.d.ts +19 -0
- package/licenses/CLA.md +35 -0
- package/licenses/EULA.md +67 -0
- package/licenses/LICENSE.md +45 -0
- package/licenses/README.md +35 -0
- package/package.json +9 -8
- package/src/Batch.res.mjs +1 -1
- package/src/BatchProcessing.res +0 -7
- package/src/BatchProcessing.res.mjs +1 -8
- package/src/ChainFetching.res +30 -18
- package/src/ChainFetching.res.mjs +23 -13
- package/src/ChainState.res +205 -59
- package/src/ChainState.res.mjs +130 -27
- package/src/ChainState.resi +16 -1
- package/src/Config.res +9 -5
- package/src/Config.res.mjs +2 -2
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +12 -0
- package/src/CrossChainState.res +147 -39
- package/src/CrossChainState.res.mjs +42 -10
- package/src/EffectState.res +100 -0
- package/src/EffectState.res.mjs +74 -0
- package/src/EffectState.resi +32 -0
- package/src/Envio.res +25 -7
- package/src/Envio.res.mjs +15 -19
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +30 -56
- package/src/EventConfigBuilder.res.mjs +24 -30
- package/src/EventProcessing.res +8 -5
- package/src/EventProcessing.res.mjs +2 -1
- package/src/FetchState.res +842 -474
- package/src/FetchState.res.mjs +545 -356
- package/src/HandlerLoader.res +1 -1
- package/src/HandlerLoader.res.mjs +1 -1
- package/src/HandlerRegister.res +478 -305
- package/src/HandlerRegister.res.mjs +277 -209
- package/src/HandlerRegister.resi +11 -6
- package/src/InMemoryStore.res +14 -26
- package/src/InMemoryStore.res.mjs +6 -19
- package/src/IndexerState.res +15 -39
- package/src/IndexerState.res.mjs +18 -29
- package/src/IndexerState.resi +2 -10
- package/src/Internal.res +121 -18
- package/src/Internal.res.mjs +96 -2
- package/src/LoadLayer.res +44 -24
- package/src/LoadLayer.res.mjs +43 -20
- package/src/LoadLayer.resi +1 -0
- package/src/LogSelection.res +92 -217
- package/src/LogSelection.res.mjs +95 -184
- package/src/Main.res +29 -143
- package/src/Main.res.mjs +25 -88
- package/src/Metrics.res +1 -1
- package/src/Metrics.res.mjs +1 -1
- package/src/Persistence.res +12 -3
- package/src/PgStorage.res +155 -82
- package/src/PgStorage.res.mjs +130 -77
- package/src/Prometheus.res +20 -10
- package/src/Prometheus.res.mjs +22 -10
- package/src/PruneStaleHistory.res +146 -35
- package/src/PruneStaleHistory.res.mjs +114 -20
- package/src/RawEvent.res +2 -2
- package/src/Rollback.res +32 -13
- package/src/Rollback.res.mjs +24 -11
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateDeadInputTracker.res +1 -1
- package/src/SimulateItems.res +39 -7
- package/src/SimulateItems.res.mjs +28 -9
- package/src/TestIndexer.res +2 -2
- package/src/TestIndexer.res.mjs +2 -2
- package/src/TopicFilter.res +1 -25
- package/src/TopicFilter.res.mjs +0 -74
- package/src/UserContext.res +62 -23
- package/src/UserContext.res.mjs +26 -6
- package/src/Writing.res +60 -21
- package/src/Writing.res.mjs +27 -9
- package/src/bindings/NodeJs.res +5 -0
- package/src/bindings/Viem.res +0 -5
- package/src/sources/EventRouter.res +0 -21
- package/src/sources/EventRouter.res.mjs +0 -12
- package/src/sources/Evm.res +4 -1
- package/src/sources/Evm.res.mjs +1 -1
- package/src/sources/EvmChain.res +2 -27
- package/src/sources/EvmChain.res.mjs +2 -23
- package/src/sources/EvmRpcClient.res +58 -23
- package/src/sources/EvmRpcClient.res.mjs +11 -5
- package/src/sources/Fuel.res +3 -1
- package/src/sources/Fuel.res.mjs +1 -1
- package/src/sources/HyperSync.res +9 -38
- package/src/sources/HyperSync.res.mjs +16 -28
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +88 -11
- package/src/sources/HyperSyncClient.res.mjs +39 -6
- package/src/sources/HyperSyncSource.res +18 -210
- package/src/sources/HyperSyncSource.res.mjs +9 -137
- package/src/sources/Rpc.res +0 -32
- package/src/sources/Rpc.res.mjs +1 -46
- package/src/sources/RpcSource.res +129 -533
- package/src/sources/RpcSource.res.mjs +161 -379
- package/src/sources/SimulateSource.res +37 -19
- package/src/sources/SimulateSource.res.mjs +27 -10
- package/src/sources/SourceManager.res +4 -14
- package/src/sources/SourceManager.res.mjs +3 -9
- package/src/sources/SourceManager.resi +0 -2
- package/src/sources/SvmHyperSyncSource.res +13 -3
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
package/src/LogSelection.res.mjs
CHANGED
|
@@ -1,79 +1,28 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as TopicFilter from "./TopicFilter.res.mjs";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
7
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
7
8
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
8
9
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let topic1 = topic1Opt !== undefined ? topic1Opt : [];
|
|
14
|
-
let topic2 = topic2Opt !== undefined ? topic2Opt : [];
|
|
15
|
-
let topic3 = topic3Opt !== undefined ? topic3Opt : [];
|
|
16
|
-
if (Utils.$$Array.isEmpty(topic0)) {
|
|
17
|
-
return {
|
|
18
|
-
TAG: "Error",
|
|
19
|
-
_0: {
|
|
20
|
-
RE_EXN_ID: MissingRequiredTopic0
|
|
21
|
-
}
|
|
22
|
-
};
|
|
11
|
+
function materializeTopicFilter(filter, addresses) {
|
|
12
|
+
if (filter.TAG === "Values") {
|
|
13
|
+
return filter._0;
|
|
23
14
|
} else {
|
|
24
|
-
return
|
|
25
|
-
TAG: "Ok",
|
|
26
|
-
_0: {
|
|
27
|
-
topic0: topic0,
|
|
28
|
-
topic1: topic1,
|
|
29
|
-
topic2: topic2,
|
|
30
|
-
topic3: topic3
|
|
31
|
-
}
|
|
32
|
-
};
|
|
15
|
+
return addresses.map(TopicFilter.fromAddress);
|
|
33
16
|
}
|
|
34
17
|
}
|
|
35
18
|
|
|
36
|
-
function
|
|
37
|
-
return
|
|
38
|
-
param.
|
|
39
|
-
param.
|
|
40
|
-
param.
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function compressTopicSelections(topicSelections) {
|
|
45
|
-
let topic0sOfSelectionsWithoutFilters = [];
|
|
46
|
-
let selectionsWithFilters = [];
|
|
47
|
-
topicSelections.forEach(selection => {
|
|
48
|
-
if (hasFilters(selection)) {
|
|
49
|
-
selectionsWithFilters.push(selection);
|
|
50
|
-
} else {
|
|
51
|
-
selection.topic0.forEach(topic0 => {
|
|
52
|
-
topic0sOfSelectionsWithoutFilters.push(topic0);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
if (topic0sOfSelectionsWithoutFilters.length === 0) {
|
|
57
|
-
return selectionsWithFilters;
|
|
58
|
-
}
|
|
59
|
-
let selectionWithoutFilters_topic1 = [];
|
|
60
|
-
let selectionWithoutFilters_topic2 = [];
|
|
61
|
-
let selectionWithoutFilters_topic3 = [];
|
|
62
|
-
let selectionWithoutFilters = {
|
|
63
|
-
topic0: topic0sOfSelectionsWithoutFilters,
|
|
64
|
-
topic1: selectionWithoutFilters_topic1,
|
|
65
|
-
topic2: selectionWithoutFilters_topic2,
|
|
66
|
-
topic3: selectionWithoutFilters_topic3
|
|
67
|
-
};
|
|
68
|
-
return [selectionWithoutFilters].concat(selectionsWithFilters);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function make(addresses, topicSelections) {
|
|
72
|
-
let topicSelections$1 = compressTopicSelections(topicSelections);
|
|
73
|
-
return {
|
|
74
|
-
addresses: addresses,
|
|
75
|
-
topicSelections: topicSelections$1
|
|
76
|
-
};
|
|
19
|
+
function materializeTopicSelections(topicSelections, addresses) {
|
|
20
|
+
return topicSelections.map(param => ({
|
|
21
|
+
topic0: param.topic0,
|
|
22
|
+
topic1: materializeTopicFilter(param.topic1, addresses),
|
|
23
|
+
topic2: materializeTopicFilter(param.topic2, addresses),
|
|
24
|
+
topic3: materializeTopicFilter(param.topic3, addresses)
|
|
25
|
+
}));
|
|
77
26
|
}
|
|
78
27
|
|
|
79
28
|
let eventBlockRangeSchema = S$RescriptSchema.strict(S$RescriptSchema.object(s => ({
|
|
@@ -100,16 +49,7 @@ function extractStartBlock(where, onEventBlockFilterSchema, contractName) {
|
|
|
100
49
|
}
|
|
101
50
|
}
|
|
102
51
|
|
|
103
|
-
function makeChainArg(contractName, chainId,
|
|
104
|
-
let chainObj = {};
|
|
105
|
-
chainObj["id"] = chainId;
|
|
106
|
-
chainObj[contractName] = {
|
|
107
|
-
addresses: addresses
|
|
108
|
-
};
|
|
109
|
-
return chainObj;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function makeDetectionChainArg(contractName, chainId, getAddresses) {
|
|
52
|
+
function makeChainArg(contractName, chainId, getAddresses) {
|
|
113
53
|
let contractObj = Object.create(null);
|
|
114
54
|
Object.defineProperty(contractObj, "addresses", {
|
|
115
55
|
enumerable: true,
|
|
@@ -133,85 +73,78 @@ let makeAddressesProbe = (function (contractName) {
|
|
|
133
73
|
return new Proxy([], {get: trap});
|
|
134
74
|
});
|
|
135
75
|
|
|
136
|
-
function extractAddressFilterGroupsOrThrow(result, probe, contractName) {
|
|
137
|
-
let groups = [];
|
|
138
|
-
let scanGroup = paramsObj => {
|
|
139
|
-
let names = [];
|
|
140
|
-
Utils.Dict.forEachWithKey(paramsObj, (value, key) => {
|
|
141
|
-
if (value === probe) {
|
|
142
|
-
names.push(key);
|
|
143
|
-
paramsObj[key] = [];
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
if (!Utils.$$Array.isEmpty(names)) {
|
|
148
|
-
groups.push(names);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
if (typeof result === "object" && result !== null && !Array.isArray(result)) {
|
|
153
|
-
let match = result["params"];
|
|
154
|
-
if (match !== undefined) {
|
|
155
|
-
if (Array.isArray(match)) {
|
|
156
|
-
match.forEach(item => {
|
|
157
|
-
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
158
|
-
return scanGroup(item);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
} else {
|
|
162
|
-
switch (typeof match) {
|
|
163
|
-
case "object" :
|
|
164
|
-
scanGroup(match);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (Utils.$$Array.isEmpty(groups)) {
|
|
171
|
-
Stdlib_JsError.throwWithMessage(`Invalid where configuration for ` + contractName + `. The callback reads \`chain.` + contractName + `.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.` + contractName + `.addresses } }.`);
|
|
172
|
-
}
|
|
173
|
-
return groups;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
76
|
let emptyTopics = [];
|
|
177
77
|
|
|
178
78
|
function noopGetter(param) {
|
|
179
79
|
return emptyTopics;
|
|
180
80
|
}
|
|
181
81
|
|
|
182
|
-
function
|
|
82
|
+
function parseWhereOrThrow(where, sighash, params, contractName, chainId, onEventBlockFilterSchema, topic1Opt, topic2Opt, topic3Opt) {
|
|
183
83
|
let topic1 = topic1Opt !== undefined ? topic1Opt : noopGetter;
|
|
184
84
|
let topic2 = topic2Opt !== undefined ? topic2Opt : noopGetter;
|
|
185
85
|
let topic3 = topic3Opt !== undefined ? topic3Opt : noopGetter;
|
|
186
|
-
let
|
|
86
|
+
let addressFilterParamGroups = [];
|
|
87
|
+
let readAddresses = {
|
|
187
88
|
contents: false
|
|
188
89
|
};
|
|
189
|
-
let
|
|
190
|
-
let startBlock;
|
|
90
|
+
let addressesSentinel = makeAddressesProbe(contractName);
|
|
191
91
|
let topic0 = [sighash];
|
|
92
|
+
let default_topic1 = {
|
|
93
|
+
TAG: "Values",
|
|
94
|
+
_0: emptyTopics
|
|
95
|
+
};
|
|
96
|
+
let default_topic2 = {
|
|
97
|
+
TAG: "Values",
|
|
98
|
+
_0: emptyTopics
|
|
99
|
+
};
|
|
100
|
+
let default_topic3 = {
|
|
101
|
+
TAG: "Values",
|
|
102
|
+
_0: emptyTopics
|
|
103
|
+
};
|
|
192
104
|
let $$default = {
|
|
193
105
|
topic0: topic0,
|
|
194
|
-
topic1:
|
|
195
|
-
topic2:
|
|
196
|
-
topic3:
|
|
106
|
+
topic1: default_topic1,
|
|
107
|
+
topic2: default_topic2,
|
|
108
|
+
topic3: default_topic3
|
|
197
109
|
};
|
|
198
|
-
let
|
|
199
|
-
|
|
200
|
-
|
|
110
|
+
let topicFilterAt = (paramsFilter, index, getter) => {
|
|
111
|
+
let value = Stdlib_Option.flatMap(params[index], name => paramsFilter[name]);
|
|
112
|
+
if (value !== undefined && value === addressesSentinel) {
|
|
113
|
+
return {
|
|
114
|
+
TAG: "ContractAddresses",
|
|
115
|
+
contractName: contractName
|
|
116
|
+
};
|
|
201
117
|
} else {
|
|
202
|
-
Utils.Dict.forEachWithKey(paramsFilter, (param, key) => {
|
|
203
|
-
if (!params.includes(key)) {
|
|
204
|
-
return Stdlib_JsError.throwWithMessage(`Invalid where configuration. The event doesn't have an indexed parameter "` + key + `" and can't use it for filtering`);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
118
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
topic2: topic2(paramsFilter),
|
|
211
|
-
topic3: topic3(paramsFilter)
|
|
119
|
+
TAG: "Values",
|
|
120
|
+
_0: getter(paramsFilter)
|
|
212
121
|
};
|
|
213
122
|
}
|
|
214
123
|
};
|
|
124
|
+
let paramsRecordToTopicSelection = paramsFilter => {
|
|
125
|
+
if (Utils.Dict.isEmpty(paramsFilter)) {
|
|
126
|
+
return $$default;
|
|
127
|
+
}
|
|
128
|
+
let sentinelParamNames = [];
|
|
129
|
+
Utils.Dict.forEachWithKey(paramsFilter, (value, key) => {
|
|
130
|
+
if (!params.includes(key)) {
|
|
131
|
+
Stdlib_JsError.throwWithMessage(`Invalid where configuration. The event doesn't have an indexed parameter "` + key + `" and can't use it for filtering`);
|
|
132
|
+
}
|
|
133
|
+
if (value === addressesSentinel) {
|
|
134
|
+
sentinelParamNames.push(key);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
if (!Utils.$$Array.isEmpty(sentinelParamNames)) {
|
|
139
|
+
addressFilterParamGroups.push(sentinelParamNames);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
topic0: topic0,
|
|
143
|
+
topic1: topicFilterAt(paramsFilter, 0, topic1),
|
|
144
|
+
topic2: topicFilterAt(paramsFilter, 1, topic2),
|
|
145
|
+
topic3: topicFilterAt(paramsFilter, 2, topic3)
|
|
146
|
+
};
|
|
147
|
+
};
|
|
215
148
|
let acceptedWhereKeys = [
|
|
216
149
|
"params",
|
|
217
150
|
"block"
|
|
@@ -258,73 +191,51 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
|
|
|
258
191
|
return Stdlib_JsError.throwWithMessage("Invalid where configuration. Expected `params` to be an object or an array of objects");
|
|
259
192
|
}
|
|
260
193
|
};
|
|
261
|
-
let
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
let chain =
|
|
266
|
-
|
|
267
|
-
return
|
|
194
|
+
let match;
|
|
195
|
+
if (where !== undefined) {
|
|
196
|
+
let whereValue;
|
|
197
|
+
if (typeof where === "function") {
|
|
198
|
+
let chain = makeChainArg(contractName, chainId, () => {
|
|
199
|
+
readAddresses.contents = true;
|
|
200
|
+
return addressesSentinel;
|
|
268
201
|
});
|
|
269
|
-
|
|
202
|
+
whereValue = where({
|
|
270
203
|
chain: chain
|
|
271
204
|
});
|
|
272
|
-
if (filterByAddresses.contents) {
|
|
273
|
-
addressFilterParamGroups = extractAddressFilterGroupsOrThrow(probedResult, addressesProbe, contractName);
|
|
274
|
-
}
|
|
275
|
-
startBlock = extractStartBlock(probedResult, onEventBlockFilterSchema, contractName);
|
|
276
|
-
getEventFiltersOrThrow = filterByAddresses.contents ? chain => ({
|
|
277
|
-
TAG: "Dynamic",
|
|
278
|
-
_0: addresses => {
|
|
279
|
-
let chainArg = makeChainArg(contractName, chain, addresses);
|
|
280
|
-
return parse(eventFilters({
|
|
281
|
-
chain: chainArg
|
|
282
|
-
}));
|
|
283
|
-
}
|
|
284
|
-
}) : chain => {
|
|
285
|
-
let chainArg = makeDetectionChainArg(contractName, chain, () => Stdlib_JsError.throwWithMessage(`Invalid where configuration. Event callback for contract "` + contractName + `" read \`chain.` + contractName + `.addresses\` at runtime but the probe didn't detect the access on chainId ` + chain.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.`));
|
|
286
|
-
return {
|
|
287
|
-
TAG: "Static",
|
|
288
|
-
_0: parse(eventFilters({
|
|
289
|
-
chain: chainArg
|
|
290
|
-
}))
|
|
291
|
-
};
|
|
292
|
-
};
|
|
293
205
|
} else {
|
|
294
|
-
|
|
295
|
-
let $$static = {
|
|
296
|
-
TAG: "Static",
|
|
297
|
-
_0: parse(eventFilters)
|
|
298
|
-
};
|
|
299
|
-
getEventFiltersOrThrow = param => $$static;
|
|
206
|
+
whereValue = where;
|
|
300
207
|
}
|
|
208
|
+
let topicSelections = parse(whereValue);
|
|
209
|
+
if (readAddresses.contents && Utils.$$Array.isEmpty(addressFilterParamGroups)) {
|
|
210
|
+
Stdlib_JsError.throwWithMessage(`Invalid where configuration for ` + contractName + `. The callback reads \`chain.` + contractName + `.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.` + contractName + `.addresses } }.`);
|
|
211
|
+
}
|
|
212
|
+
match = [
|
|
213
|
+
topicSelections,
|
|
214
|
+
extractStartBlock(whereValue, onEventBlockFilterSchema, contractName)
|
|
215
|
+
];
|
|
301
216
|
} else {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
getEventFiltersOrThrow = param => $$static$1;
|
|
217
|
+
match = [
|
|
218
|
+
[$$default],
|
|
219
|
+
undefined
|
|
220
|
+
];
|
|
307
221
|
}
|
|
308
222
|
return {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
223
|
+
resolvedWhere: {
|
|
224
|
+
topicSelections: match[0],
|
|
225
|
+
startBlock: match[1]
|
|
226
|
+
},
|
|
227
|
+
filterByAddresses: !Utils.$$Array.isEmpty(addressFilterParamGroups),
|
|
228
|
+
addressFilterParamGroups: addressFilterParamGroups
|
|
313
229
|
};
|
|
314
230
|
}
|
|
315
231
|
|
|
316
232
|
export {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
hasFilters,
|
|
320
|
-
compressTopicSelections,
|
|
321
|
-
make,
|
|
233
|
+
materializeTopicFilter,
|
|
234
|
+
materializeTopicSelections,
|
|
322
235
|
eventBlockRangeSchema,
|
|
323
236
|
extractStartBlock,
|
|
324
237
|
makeChainArg,
|
|
325
|
-
makeDetectionChainArg,
|
|
326
238
|
makeAddressesProbe,
|
|
327
|
-
|
|
328
|
-
parseEventFiltersOrThrow,
|
|
239
|
+
parseWhereOrThrow,
|
|
329
240
|
}
|
|
330
241
|
/* eventBlockRangeSchema Not a pure module */
|
package/src/Main.res
CHANGED
|
@@ -39,40 +39,24 @@ let stateSchema = S.union([
|
|
|
39
39
|
})),
|
|
40
40
|
])
|
|
41
41
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_lte: option<int>,
|
|
50
|
-
_every: int,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// `S.strict` rejects unknown fields so typos like `_gt` / `_evry` surface
|
|
54
|
-
// with a readable schema error pointing at the offending key, instead of
|
|
55
|
-
// silently registering a broken filter. `_every` defaults to 1 inside the
|
|
56
|
-
// schema so the caller always sees a plain `int`, and `intMin(1)` rejects
|
|
57
|
-
// zero/negative strides — `(blockNumber - startBlock) % 0` would crash and
|
|
58
|
-
// any negative stride would never match.
|
|
59
|
-
let blockRangeSchema: S.t<blockRange> = S.object(s => {
|
|
60
|
-
_gte: s.field("_gte", S.option(S.int)),
|
|
61
|
-
_lte: s.field("_lte", S.option(S.int)),
|
|
62
|
-
_every: s.field("_every", S.option(S.int->S.intMin(1))->S.Option.getOr(1)),
|
|
63
|
-
})->S.strict
|
|
64
|
-
|
|
65
|
-
let defaultBlockRange: blockRange = {_gte: None, _lte: None, _every: 1}
|
|
66
|
-
|
|
67
|
-
let indexerStateRef: ref<option<IndexerState.t>> = ref(None)
|
|
42
|
+
// Runtime state lives in the process-wide `EnvioGlobal` record (shared
|
|
43
|
+
// across duplicate envio module instances); the slots are opaque there, so
|
|
44
|
+
// cast them to the real types here.
|
|
45
|
+
let getIndexerState = () =>
|
|
46
|
+
EnvioGlobal.value.indexerState->(Utils.magic: option<unknown> => option<IndexerState.t>)
|
|
47
|
+
let setIndexerState = (state: IndexerState.t) =>
|
|
48
|
+
EnvioGlobal.value.indexerState = Some(state->(Utils.magic: IndexerState.t => unknown))
|
|
68
49
|
|
|
69
50
|
// Persistence is set by Main.start before handler modules load, so that
|
|
70
51
|
// the exported indexer value can lazily expose DB state (startBlock,
|
|
71
52
|
// endBlock, isRealtime, dynamic contract addresses) once it's ready.
|
|
72
|
-
let
|
|
53
|
+
let getGlobalPersistence = () =>
|
|
54
|
+
EnvioGlobal.value.persistence->(Utils.magic: option<unknown> => option<Persistence.t>)
|
|
55
|
+
let setGlobalPersistence = (persistence: Persistence.t) =>
|
|
56
|
+
EnvioGlobal.value.persistence = Some(persistence->(Utils.magic: Persistence.t => unknown))
|
|
73
57
|
|
|
74
58
|
let getInitialChainState = (~chainId: int): option<Persistence.initialChainState> => {
|
|
75
|
-
switch
|
|
59
|
+
switch getGlobalPersistence() {
|
|
76
60
|
| Some(persistence) =>
|
|
77
61
|
switch persistence.storageStatus {
|
|
78
62
|
| Ready(initialState) => initialState.chains->Array.find(c => c.id === chainId)
|
|
@@ -130,7 +114,7 @@ let buildChainsObject = (~config: Config.t) => {
|
|
|
130
114
|
{
|
|
131
115
|
enumerable: true,
|
|
132
116
|
get: () => {
|
|
133
|
-
switch
|
|
117
|
+
switch getIndexerState() {
|
|
134
118
|
| Some(state) => state->IndexerState.isRealtime
|
|
135
119
|
// Before the global state is available (eg during handler
|
|
136
120
|
// module load after resume), derive from persistence: every chain
|
|
@@ -164,7 +148,7 @@ let buildChainsObject = (~config: Config.t) => {
|
|
|
164
148
|
{
|
|
165
149
|
enumerable: true,
|
|
166
150
|
get: () => {
|
|
167
|
-
switch
|
|
151
|
+
switch getIndexerState() {
|
|
168
152
|
| Some(state) => {
|
|
169
153
|
let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
|
|
170
154
|
let chainState = state->IndexerState.getChainState(~chain)
|
|
@@ -345,121 +329,24 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
345
329
|
let _ = RollbackCommit.register(callback->(Utils.magic: 'a => RollbackCommit.callback))
|
|
346
330
|
}
|
|
347
331
|
|
|
348
|
-
// Two-stage parse: first the ecosystem-specific outer schema unwraps the
|
|
349
|
-
// wrapper (`block.number` / `block.height` / `slot`) and surfaces the
|
|
350
|
-
// inner chunk as raw `unknown`; then the shared `blockRangeSchema`
|
|
351
|
-
// validates the `{_gte?, _lte?, _every?}` fields. Keeping the inner
|
|
352
|
-
// validation in one place means typos and shape mismatches surface with
|
|
353
|
-
// the same user-friendly error regardless of ecosystem.
|
|
354
|
-
let extractRange = (filter: unknown, ~name, ~ecosystem: Ecosystem.t): blockRange =>
|
|
355
|
-
try {
|
|
356
|
-
switch filter->S.parseOrThrow(ecosystem.onBlockFilterSchema) {
|
|
357
|
-
| None => defaultBlockRange
|
|
358
|
-
| Some(inner) => inner->S.parseOrThrow(blockRangeSchema)
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
| S.Raised(exn) =>
|
|
362
|
-
JsError.throwWithMessage(
|
|
363
|
-
`\`indexer.${ecosystem.onBlockMethodName}("${name}")\` \`where\` returned an invalid filter: ${exn
|
|
364
|
-
->Utils.prettifyExn
|
|
365
|
-
->(Utils.magic: exn => string)}`,
|
|
366
|
-
)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// `where` is evaluated once per configured chain at registration time.
|
|
370
|
-
// Decoded ranges/stride feed directly into `HandlerRegister.registerOnBlock`
|
|
371
|
-
// so the fetcher's `(blockNumber - handlerStartBlock) % interval === 0`
|
|
372
|
-
// math at `FetchState.res:619` stays untouched.
|
|
373
332
|
let onBlockFn = (rawOptions: 'a, handler: 'b) => {
|
|
374
333
|
HandlerRegister.throwIfFinishedRegistration(~methodName="onBlock")
|
|
375
|
-
let config = Config.load()
|
|
376
|
-
let ecosystem = config.ecosystem
|
|
377
334
|
let raw =
|
|
378
335
|
rawOptions->(
|
|
379
336
|
Utils.magic: 'a => {
|
|
380
337
|
"name": string,
|
|
381
|
-
"where":
|
|
338
|
+
"where": unknown,
|
|
382
339
|
}
|
|
383
340
|
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// Normalize undefined/null to None up front so the per-chain loop below
|
|
394
|
-
// can't accidentally call `null` as a predicate (ReScript treats a JS
|
|
395
|
-
// `null` value as `Some(null)` when the field is typed as option).
|
|
396
|
-
let where = switch raw["where"]->(Utils.magic: option<'a> => unknown) {
|
|
397
|
-
| w if w === %raw(`undefined`) || w === %raw(`null`) => None
|
|
398
|
-
| w if typeof(w) === #function => Some(raw["where"]->Option.getUnsafe)
|
|
399
|
-
| w =>
|
|
400
|
-
JsError.throwWithMessage(
|
|
401
|
-
`\`indexer.${ecosystem.onBlockMethodName}("${name}")\` expected \`where\` to be a function or omitted, but got ${(typeof(
|
|
402
|
-
w,
|
|
403
|
-
) :> string)}.`,
|
|
404
|
-
)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
let matchedAny = ref(false)
|
|
408
|
-
|
|
409
|
-
config.chainMap
|
|
410
|
-
->ChainMap.values
|
|
411
|
-
->Array.forEach(chainConfig => {
|
|
412
|
-
let chainId = chainConfig.id
|
|
413
|
-
let chainObj = chainsDict->Dict.getUnsafe(chainId->Int.toString)
|
|
414
|
-
|
|
415
|
-
// Predicate returns `true` → match with no filter; `false` → skip;
|
|
416
|
-
// any plain object → structured filter. `undefined`/`null` returns
|
|
417
|
-
// are rejected — the TS type excludes `void`, so a missing return is
|
|
418
|
-
// a user bug we surface early rather than silently match-all.
|
|
419
|
-
let result = switch where {
|
|
420
|
-
| None => %raw(`true`)
|
|
421
|
-
| Some(predicate) => predicate({chain: chainObj})
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
let (shouldRegister, range) = if result === %raw(`true`) {
|
|
425
|
-
(true, defaultBlockRange)
|
|
426
|
-
} else if result === %raw(`false`) {
|
|
427
|
-
(false, defaultBlockRange)
|
|
428
|
-
} else if typeof(result) === #object && !(result->Array.isArray) && result !== %raw(`null`) {
|
|
429
|
-
(true, extractRange(result, ~name, ~ecosystem))
|
|
430
|
-
} else {
|
|
431
|
-
// Reject numbers, strings, functions, arrays, undefined, null —
|
|
432
|
-
// anything that isn't bool or a plain object would silently
|
|
433
|
-
// misregister.
|
|
434
|
-
JsError.throwWithMessage(
|
|
435
|
-
`\`indexer.${ecosystem.onBlockMethodName}("${name}")\` \`where\` predicate returned an invalid value of type ${(typeof(
|
|
436
|
-
result,
|
|
437
|
-
) :> string)}. Expected boolean or a filter object.`,
|
|
438
|
-
)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if shouldRegister {
|
|
442
|
-
matchedAny := true
|
|
443
|
-
HandlerRegister.registerOnBlock(
|
|
444
|
-
~name,
|
|
445
|
-
~chainId,
|
|
446
|
-
~interval=range._every,
|
|
447
|
-
~startBlock=range._gte,
|
|
448
|
-
~endBlock=range._lte,
|
|
449
|
-
~handler=typedHandler,
|
|
450
|
-
)
|
|
451
|
-
}
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
// Catches misconfigured `where` predicates that return `false` for every
|
|
455
|
-
// configured chain — the handler would otherwise never fire with no hint.
|
|
456
|
-
// Includes the ecosystem-specific method name so SVM users see "onSlot"
|
|
457
|
-
// and don't get confused looking for a "Block handler" they never wrote.
|
|
458
|
-
if !matchedAny.contents {
|
|
459
|
-
logger->Logging.childWarn(
|
|
460
|
-
`\`indexer.${ecosystem.onBlockMethodName}\` matched 0 chains. Check the \`where\` predicate.`,
|
|
461
|
-
)
|
|
462
|
-
}
|
|
341
|
+
HandlerRegister.registerOnBlock(
|
|
342
|
+
~name=raw["name"],
|
|
343
|
+
~where=raw["where"],
|
|
344
|
+
~handler=handler->(Utils.magic: 'b => Internal.onBlockArgs => promise<unit>),
|
|
345
|
+
~getChainsObject=config => {
|
|
346
|
+
let (chains, _) = buildChainsObject(~config)
|
|
347
|
+
chains->(Utils.magic: {..} => dict<unknown>)
|
|
348
|
+
},
|
|
349
|
+
)
|
|
463
350
|
}
|
|
464
351
|
|
|
465
352
|
// Ecosystem-specific surface: EVM/Fuel expose event + block handlers; SVM
|
|
@@ -506,8 +393,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
506
393
|
let get = (~prop: string) =>
|
|
507
394
|
switch prop {
|
|
508
395
|
| "name" => Config.load().name->(Utils.magic: string => unknown)
|
|
509
|
-
| "description" =>
|
|
510
|
-
Config.load().description->(Utils.magic: option<string> => unknown)
|
|
396
|
+
| "description" => Config.load().description->(Utils.magic: option<string> => unknown)
|
|
511
397
|
| "chainIds" => {
|
|
512
398
|
let (_, chainIds) = buildChainsObject(~config=Config.load())
|
|
513
399
|
chainIds->(Utils.magic: array<int> => unknown)
|
|
@@ -616,7 +502,7 @@ let startServer = (~getState, ~persistence: Persistence.t, ~isDevelopmentMode: b
|
|
|
616
502
|
app->get("/metrics", (_req, res) => {
|
|
617
503
|
res->set("Content-Type", PromClient.defaultRegister->PromClient.getContentType)
|
|
618
504
|
let _ =
|
|
619
|
-
Metrics.collect(~state=
|
|
505
|
+
Metrics.collect(~state=getIndexerState())->Promise.thenResolve(metrics =>
|
|
620
506
|
res->endWithData(metrics)
|
|
621
507
|
)
|
|
622
508
|
})
|
|
@@ -709,7 +595,7 @@ let start = async (
|
|
|
709
595
|
| Some(p) => p
|
|
710
596
|
| None => PgStorage.makePersistenceFromConfig(~config)
|
|
711
597
|
}
|
|
712
|
-
|
|
598
|
+
setGlobalPersistence(persistence)
|
|
713
599
|
await persistence->Persistence.init(
|
|
714
600
|
~reset,
|
|
715
601
|
~chainConfigs=config.chainMap->ChainMap.values,
|
|
@@ -756,7 +642,7 @@ let start = async (
|
|
|
756
642
|
|
|
757
643
|
if !isTest {
|
|
758
644
|
startServer(~persistence, ~isDevelopmentMode, ~getState=() =>
|
|
759
|
-
switch
|
|
645
|
+
switch getIndexerState() {
|
|
760
646
|
| None => Initializing({})
|
|
761
647
|
| Some(state) => {
|
|
762
648
|
let chains =
|
|
@@ -786,7 +672,7 @@ let start = async (
|
|
|
786
672
|
if shouldUseTui {
|
|
787
673
|
let _rerender = Tui.start(~getState=() => state)
|
|
788
674
|
}
|
|
789
|
-
|
|
675
|
+
setIndexerState(state)
|
|
790
676
|
state->IndexerLoop.start
|
|
791
677
|
await runUntilFatalError
|
|
792
678
|
}
|