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
package/src/Config.res
CHANGED
|
@@ -8,6 +8,7 @@ type sourceSyncOptions = {
|
|
|
8
8
|
backoffMillis?: int,
|
|
9
9
|
queryTimeoutMillis?: int,
|
|
10
10
|
fallbackStallTimeout?: int,
|
|
11
|
+
pollingInterval?: int,
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
type contract = {
|
|
@@ -16,15 +17,47 @@ type contract = {
|
|
|
16
17
|
addresses: array<Address.t>,
|
|
17
18
|
events: array<Internal.eventConfig>,
|
|
18
19
|
startBlock: option<int>,
|
|
20
|
+
// EVM-specific: event sighashes for HyperSync queries
|
|
21
|
+
eventSignatures: array<string>,
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
type codegenContract = {
|
|
25
|
+
name: string,
|
|
26
|
+
addresses: array<string>,
|
|
27
|
+
events: array<Internal.eventConfig>,
|
|
28
|
+
startBlock: option<int>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Source config is now parsed from internal.config.json and sources are created lazily
|
|
32
|
+
type codegenChain = {
|
|
33
|
+
id: int,
|
|
34
|
+
contracts: array<codegenContract>,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Source config parsed from internal.config.json - sources are created lazily in ChainFetcher
|
|
38
|
+
type evmRpcConfig = {
|
|
39
|
+
url: string,
|
|
40
|
+
sourceFor: Source.sourceFor,
|
|
41
|
+
syncConfig: option<sourceSyncOptions>,
|
|
42
|
+
ws: option<string>,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type sourceConfig =
|
|
46
|
+
| EvmSourceConfig({hypersync: option<string>, rpcs: array<evmRpcConfig>})
|
|
47
|
+
| FuelSourceConfig({hypersync: string})
|
|
48
|
+
| SvmSourceConfig({rpc: string})
|
|
49
|
+
// For tests: pass custom sources directly
|
|
50
|
+
| CustomSources(array<Source.t>)
|
|
51
|
+
|
|
21
52
|
type chain = {
|
|
53
|
+
name: string,
|
|
22
54
|
id: int,
|
|
23
55
|
startBlock: int,
|
|
24
56
|
endBlock?: int,
|
|
25
57
|
maxReorgDepth: int,
|
|
58
|
+
blockLag: int,
|
|
26
59
|
contracts: array<contract>,
|
|
27
|
-
|
|
60
|
+
sourceConfig: sourceConfig,
|
|
28
61
|
}
|
|
29
62
|
|
|
30
63
|
type sourceSync = {
|
|
@@ -35,11 +68,21 @@ type sourceSync = {
|
|
|
35
68
|
backoffMillis: int,
|
|
36
69
|
queryTimeoutMillis: int,
|
|
37
70
|
fallbackStallTimeout: int,
|
|
71
|
+
pollingInterval: int,
|
|
38
72
|
}
|
|
39
73
|
|
|
40
74
|
type multichain = | @as("ordered") Ordered | @as("unordered") Unordered
|
|
41
75
|
|
|
76
|
+
type contractHandler = {
|
|
77
|
+
name: string,
|
|
78
|
+
handler: option<string>,
|
|
79
|
+
}
|
|
80
|
+
|
|
42
81
|
type t = {
|
|
82
|
+
name: string,
|
|
83
|
+
description: option<string>,
|
|
84
|
+
handlers: string,
|
|
85
|
+
contractHandlers: array<contractHandler>,
|
|
43
86
|
shouldRollbackOnReorg: bool,
|
|
44
87
|
shouldSaveFullHistory: bool,
|
|
45
88
|
multichain: multichain,
|
|
@@ -51,31 +94,652 @@ type t = {
|
|
|
51
94
|
batchSize: int,
|
|
52
95
|
lowercaseAddresses: bool,
|
|
53
96
|
addContractNameToContractNameMapping: dict<string>,
|
|
97
|
+
userEntitiesByName: dict<Internal.entityConfig>,
|
|
98
|
+
userEntities: array<Internal.entityConfig>,
|
|
99
|
+
allEntities: array<Internal.entityConfig>,
|
|
100
|
+
allEnums: array<Table.enumConfig<Table.enum>>,
|
|
54
101
|
}
|
|
55
102
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
~
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
103
|
+
module DynamicContractRegistry = {
|
|
104
|
+
let name = "dynamic_contract_registry"
|
|
105
|
+
let index = -1
|
|
106
|
+
|
|
107
|
+
let makeId = (~chainId, ~contractAddress) => {
|
|
108
|
+
chainId->Belt.Int.toString ++ "-" ++ contractAddress->Address.toString
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@genType
|
|
112
|
+
type t = {
|
|
113
|
+
id: string,
|
|
114
|
+
@as("chain_id") chainId: int,
|
|
115
|
+
@as("registering_event_block_number") registeringEventBlockNumber: int,
|
|
116
|
+
@as("registering_event_log_index") registeringEventLogIndex: int,
|
|
117
|
+
@as("registering_event_block_timestamp") registeringEventBlockTimestamp: int,
|
|
118
|
+
@as("registering_event_contract_name") registeringEventContractName: string,
|
|
119
|
+
@as("registering_event_name") registeringEventName: string,
|
|
120
|
+
@as("registering_event_src_address") registeringEventSrcAddress: Address.t,
|
|
121
|
+
@as("contract_address") contractAddress: Address.t,
|
|
122
|
+
@as("contract_name") contractName: string,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let schema = S.schema(s => {
|
|
126
|
+
id: s.matches(S.string),
|
|
127
|
+
chainId: s.matches(S.int),
|
|
128
|
+
registeringEventBlockNumber: s.matches(S.int),
|
|
129
|
+
registeringEventLogIndex: s.matches(S.int),
|
|
130
|
+
registeringEventContractName: s.matches(S.string),
|
|
131
|
+
registeringEventName: s.matches(S.string),
|
|
132
|
+
registeringEventSrcAddress: s.matches(Address.schema),
|
|
133
|
+
registeringEventBlockTimestamp: s.matches(S.int),
|
|
134
|
+
contractAddress: s.matches(Address.schema),
|
|
135
|
+
contractName: s.matches(S.string),
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
let rowsSchema = S.array(schema)
|
|
139
|
+
|
|
140
|
+
let table = Table.mkTable(
|
|
141
|
+
name,
|
|
142
|
+
~fields=[
|
|
143
|
+
Table.mkField("id", String, ~isPrimaryKey=true, ~fieldSchema=S.string),
|
|
144
|
+
Table.mkField("chain_id", Int32, ~fieldSchema=S.int),
|
|
145
|
+
Table.mkField("registering_event_block_number", Int32, ~fieldSchema=S.int),
|
|
146
|
+
Table.mkField("registering_event_log_index", Int32, ~fieldSchema=S.int),
|
|
147
|
+
Table.mkField("registering_event_block_timestamp", Int32, ~fieldSchema=S.int),
|
|
148
|
+
Table.mkField("registering_event_contract_name", String, ~fieldSchema=S.string),
|
|
149
|
+
Table.mkField("registering_event_name", String, ~fieldSchema=S.string),
|
|
150
|
+
Table.mkField("registering_event_src_address", String, ~fieldSchema=Address.schema),
|
|
151
|
+
Table.mkField("contract_address", String, ~fieldSchema=Address.schema),
|
|
152
|
+
Table.mkField("contract_name", String, ~fieldSchema=S.string),
|
|
153
|
+
],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
external castToInternal: t => Internal.entity = "%identity"
|
|
157
|
+
|
|
158
|
+
let entityConfig = {
|
|
159
|
+
name,
|
|
160
|
+
index,
|
|
161
|
+
schema,
|
|
162
|
+
rowsSchema,
|
|
163
|
+
table,
|
|
164
|
+
}->Internal.fromGenericEntityConfig
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Types for parsing source config from internal.config.json
|
|
168
|
+
type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("live") Live
|
|
169
|
+
|
|
170
|
+
let rpcSourceForSchema = S.enum([Sync, Fallback, Live])
|
|
171
|
+
|
|
172
|
+
let rpcConfigSchema = S.schema(s =>
|
|
173
|
+
{
|
|
174
|
+
"url": s.matches(S.string),
|
|
175
|
+
"for": s.matches(rpcSourceForSchema),
|
|
176
|
+
"ws": s.matches(S.option(S.string)),
|
|
177
|
+
"initialBlockInterval": s.matches(S.option(S.int)),
|
|
178
|
+
"backoffMultiplicative": s.matches(S.option(S.float)),
|
|
179
|
+
"accelerationAdditive": s.matches(S.option(S.int)),
|
|
180
|
+
"intervalCeiling": s.matches(S.option(S.int)),
|
|
181
|
+
"backoffMillis": s.matches(S.option(S.int)),
|
|
182
|
+
"fallbackStallTimeout": s.matches(S.option(S.int)),
|
|
183
|
+
"queryTimeoutMillis": s.matches(S.option(S.int)),
|
|
184
|
+
"pollingInterval": s.matches(S.option(S.int)),
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
let publicConfigChainSchema = S.schema(s =>
|
|
189
|
+
{
|
|
190
|
+
"id": s.matches(S.int),
|
|
191
|
+
"startBlock": s.matches(S.int),
|
|
192
|
+
"endBlock": s.matches(S.option(S.int)),
|
|
193
|
+
"maxReorgDepth": s.matches(S.option(S.int)),
|
|
194
|
+
"blockLag": s.matches(S.option(S.int)),
|
|
195
|
+
// EVM/Fuel source config (hypersync for EVM, hyperfuel for Fuel)
|
|
196
|
+
"hypersync": s.matches(S.option(S.string)),
|
|
197
|
+
"rpcs": s.matches(S.option(S.array(rpcConfigSchema))),
|
|
198
|
+
// SVM source config
|
|
199
|
+
"rpc": s.matches(S.option(S.string)),
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
let contractEventItemSchema = S.schema(s =>
|
|
204
|
+
{
|
|
205
|
+
"event": s.matches(S.string),
|
|
206
|
+
"name": s.matches(S.string),
|
|
207
|
+
"blockFields": s.matches(S.option(S.array(S.string))),
|
|
208
|
+
"transactionFields": s.matches(S.option(S.array(S.string))),
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
let contractConfigSchema = S.schema(s =>
|
|
213
|
+
{
|
|
214
|
+
"abi": s.matches(S.json(~validate=false)),
|
|
215
|
+
"handler": s.matches(S.option(S.string)),
|
|
216
|
+
// EVM-specific: event signatures for HyperSync queries
|
|
217
|
+
"events": s.matches(S.option(S.array(contractEventItemSchema))),
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
let publicConfigEcosystemSchema = S.schema(s =>
|
|
222
|
+
{
|
|
223
|
+
"chains": s.matches(S.dict(publicConfigChainSchema)),
|
|
224
|
+
"contracts": s.matches(S.option(S.dict(contractConfigSchema))),
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
type addressFormat = | @as("lowercase") Lowercase | @as("checksum") Checksum
|
|
229
|
+
|
|
230
|
+
let publicConfigEvmSchema = S.schema(s =>
|
|
231
|
+
{
|
|
232
|
+
"chains": s.matches(S.dict(publicConfigChainSchema)),
|
|
233
|
+
"contracts": s.matches(S.option(S.dict(contractConfigSchema))),
|
|
234
|
+
"addressFormat": s.matches(S.option(S.enum([Lowercase, Checksum]))),
|
|
235
|
+
"globalBlockFields": s.matches(S.option(S.array(Internal.evmBlockFieldSchema))),
|
|
236
|
+
"globalTransactionFields": s.matches(S.option(S.array(Internal.evmTransactionFieldSchema))),
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
let multichainSchema = S.enum([Ordered, Unordered])
|
|
241
|
+
|
|
242
|
+
let compositeIndexFieldSchema = S.schema(s =>
|
|
243
|
+
{
|
|
244
|
+
"fieldName": s.matches(S.string),
|
|
245
|
+
"direction": s.matches(S.string),
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
let derivedFieldSchema = S.schema(s =>
|
|
250
|
+
{
|
|
251
|
+
"fieldName": s.matches(S.string),
|
|
252
|
+
"derivedFromEntity": s.matches(S.string),
|
|
253
|
+
"derivedFromField": s.matches(S.string),
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
let propertySchema = S.schema(s =>
|
|
258
|
+
{
|
|
259
|
+
"name": s.matches(S.string),
|
|
260
|
+
"type": s.matches(S.string),
|
|
261
|
+
"isNullable": s.matches(S.option(S.bool)),
|
|
262
|
+
"isArray": s.matches(S.option(S.bool)),
|
|
263
|
+
"isIndex": s.matches(S.option(S.bool)),
|
|
264
|
+
"linkedEntity": s.matches(S.option(S.string)),
|
|
265
|
+
"enum": s.matches(S.option(S.string)),
|
|
266
|
+
"entity": s.matches(S.option(S.string)),
|
|
267
|
+
"precision": s.matches(S.option(S.int)),
|
|
268
|
+
"scale": s.matches(S.option(S.int)),
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
let entityJsonSchema = S.schema(s =>
|
|
273
|
+
{
|
|
274
|
+
"name": s.matches(S.string),
|
|
275
|
+
"properties": s.matches(S.array(propertySchema)),
|
|
276
|
+
"derivedFields": s.matches(S.option(S.array(derivedFieldSchema))),
|
|
277
|
+
"compositeIndices": s.matches(S.option(S.array(S.array(compositeIndexFieldSchema)))),
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Table.enum>>) => {
|
|
282
|
+
let typ = prop["type"]
|
|
283
|
+
let isNullable = prop["isNullable"]->Option.getWithDefault(false)
|
|
284
|
+
let isArray = prop["isArray"]->Option.getWithDefault(false)
|
|
285
|
+
let isIndex = prop["isIndex"]->Option.getWithDefault(false)
|
|
286
|
+
|
|
287
|
+
let (fieldType, baseSchema) = switch typ {
|
|
288
|
+
| "string" => (Table.String, S.string->S.toUnknown)
|
|
289
|
+
| "boolean" => (Table.Boolean, S.bool->S.toUnknown)
|
|
290
|
+
| "int" => (Table.Int32, S.int->S.toUnknown)
|
|
291
|
+
| "bigint" => (Table.BigInt({precision: ?prop["precision"]}), BigInt.schema->S.toUnknown)
|
|
292
|
+
| "bigdecimal" => (
|
|
293
|
+
Table.BigDecimal({
|
|
294
|
+
config: ?prop["precision"]->Option.map(p => (p, prop["scale"]->Option.getWithDefault(0))),
|
|
295
|
+
}),
|
|
296
|
+
BigDecimal.schema->S.toUnknown,
|
|
297
|
+
)
|
|
298
|
+
| "float" => (Table.Number, S.float->S.toUnknown)
|
|
299
|
+
| "serial" => (Table.Serial, S.int->S.toUnknown)
|
|
300
|
+
| "json" => (Table.Json, S.json(~validate=false)->S.toUnknown)
|
|
301
|
+
| "date" => (Table.Date, Utils.Schema.dbDate->S.toUnknown)
|
|
302
|
+
| "enum" => {
|
|
303
|
+
let enumName = prop["enum"]->Option.getExn
|
|
304
|
+
let enumConfig =
|
|
305
|
+
enumConfigsByName
|
|
306
|
+
->Js.Dict.get(enumName)
|
|
307
|
+
->Option.getExn
|
|
308
|
+
(Table.Enum({config: enumConfig}), enumConfig.schema->S.toUnknown)
|
|
309
|
+
}
|
|
310
|
+
| "entity" => {
|
|
311
|
+
let entityName = prop["entity"]->Option.getExn
|
|
312
|
+
(Table.Entity({name: entityName}), S.string->S.toUnknown)
|
|
313
|
+
}
|
|
314
|
+
| other => Js.Exn.raiseError("Unknown field type in entity config: " ++ other)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let fieldSchema = if isArray {
|
|
318
|
+
S.array(baseSchema)->S.toUnknown
|
|
319
|
+
} else {
|
|
320
|
+
baseSchema
|
|
321
|
+
}
|
|
322
|
+
let fieldSchema = if isNullable {
|
|
323
|
+
S.null(fieldSchema)->S.toUnknown
|
|
324
|
+
} else {
|
|
325
|
+
fieldSchema
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
(fieldType, fieldSchema, isNullable, isArray, isIndex)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let parseEnumsFromJson = (enumsJson: dict<array<string>>): array<Table.enumConfig<Table.enum>> => {
|
|
332
|
+
enumsJson
|
|
333
|
+
->Js.Dict.entries
|
|
334
|
+
->Array.map(((name, variants)) =>
|
|
335
|
+
Table.makeEnumConfig(~name, ~variants)->Table.fromGenericEnumConfig
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let parseEntitiesFromJson = (
|
|
340
|
+
entitiesJson: array<'entityJson>,
|
|
341
|
+
~enumConfigsByName: dict<Table.enumConfig<Table.enum>>,
|
|
342
|
+
): array<Internal.entityConfig> => {
|
|
343
|
+
entitiesJson->Array.mapWithIndex((index, entityJson) => {
|
|
344
|
+
let entityName = entityJson["name"]
|
|
345
|
+
|
|
346
|
+
let fields: array<Table.fieldOrDerived> = entityJson["properties"]->Array.map(prop => {
|
|
347
|
+
let (fieldType, fieldSchema, isNullable, isArray, isIndex) = getFieldTypeAndSchema(
|
|
348
|
+
prop,
|
|
349
|
+
~enumConfigsByName,
|
|
350
|
+
)
|
|
351
|
+
Table.mkField(
|
|
352
|
+
prop["name"],
|
|
353
|
+
fieldType,
|
|
354
|
+
~fieldSchema,
|
|
355
|
+
~isPrimaryKey=prop["name"] === "id",
|
|
356
|
+
~isNullable,
|
|
357
|
+
~isArray,
|
|
358
|
+
~isIndex,
|
|
359
|
+
~linkedEntity=?prop["linkedEntity"],
|
|
360
|
+
)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
let derivedFields: array<Table.fieldOrDerived> =
|
|
364
|
+
entityJson["derivedFields"]
|
|
365
|
+
->Option.getWithDefault([])
|
|
366
|
+
->Array.map(df =>
|
|
367
|
+
Table.mkDerivedFromField(
|
|
368
|
+
df["fieldName"],
|
|
369
|
+
~derivedFromEntity=df["derivedFromEntity"],
|
|
370
|
+
~derivedFromField=df["derivedFromField"],
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
let compositeIndices =
|
|
375
|
+
entityJson["compositeIndices"]
|
|
376
|
+
->Option.getWithDefault([])
|
|
377
|
+
->Array.map(ci =>
|
|
378
|
+
ci->Array.map(
|
|
379
|
+
f => {
|
|
380
|
+
Table.fieldName: f["fieldName"],
|
|
381
|
+
direction: f["direction"] == "Asc" ? Table.Asc : Table.Desc,
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
let table = Table.mkTable(
|
|
387
|
+
entityName,
|
|
388
|
+
~fields=Array.concat(fields, derivedFields),
|
|
389
|
+
~compositeIndices,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
// Build schema dynamically from properties
|
|
393
|
+
// Use db field names (with _id suffix for linked entities) as schema locations
|
|
394
|
+
// to match the database column names used in Table.toSqlParams
|
|
395
|
+
let schema = S.schema(s => {
|
|
396
|
+
let dict = Js.Dict.empty()
|
|
397
|
+
entityJson["properties"]->Array.forEach(
|
|
398
|
+
prop => {
|
|
399
|
+
let (_, fieldSchema, _, _, _) = getFieldTypeAndSchema(prop, ~enumConfigsByName)
|
|
400
|
+
let dbFieldName = switch prop["linkedEntity"] {
|
|
401
|
+
| Some(_) => prop["name"] ++ "_id"
|
|
402
|
+
| None => prop["name"]
|
|
403
|
+
}
|
|
404
|
+
dict->Js.Dict.set(dbFieldName, s.matches(fieldSchema))
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
dict
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
{
|
|
411
|
+
Internal.name: entityName,
|
|
412
|
+
index,
|
|
413
|
+
schema: schema->(Utils.magic: S.t<dict<unknown>> => S.t<Internal.entity>),
|
|
414
|
+
rowsSchema: S.array(schema)->(
|
|
415
|
+
Utils.magic: S.t<array<dict<unknown>>> => S.t<array<Internal.entity>>
|
|
416
|
+
),
|
|
417
|
+
table,
|
|
418
|
+
}->Internal.fromGenericEntityConfig
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let publicConfigSchema = S.schema(s =>
|
|
423
|
+
{
|
|
424
|
+
"name": s.matches(S.string),
|
|
425
|
+
"description": s.matches(S.option(S.string)),
|
|
426
|
+
"handlers": s.matches(S.option(S.string)),
|
|
427
|
+
"multichain": s.matches(S.option(multichainSchema)),
|
|
428
|
+
"fullBatchSize": s.matches(S.option(S.int)),
|
|
429
|
+
"rollbackOnReorg": s.matches(S.option(S.bool)),
|
|
430
|
+
"saveFullHistory": s.matches(S.option(S.bool)),
|
|
431
|
+
"rawEvents": s.matches(S.option(S.bool)),
|
|
432
|
+
"evm": s.matches(S.option(publicConfigEvmSchema)),
|
|
433
|
+
"fuel": s.matches(S.option(publicConfigEcosystemSchema)),
|
|
434
|
+
"svm": s.matches(S.option(publicConfigEcosystemSchema)),
|
|
435
|
+
"enums": s.matches(S.option(S.dict(S.array(S.string)))),
|
|
436
|
+
"entities": s.matches(S.option(S.array(entityJsonSchema))),
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
// Always-included block fields (number, timestamp, hash) are not in the JSON;
|
|
441
|
+
// they're prepended at runtime so they're always present.
|
|
442
|
+
let alwaysIncludedBlockFields: array<Internal.evmBlockField> = [Number, Timestamp, Hash]
|
|
443
|
+
|
|
444
|
+
// Enrich EVM event configs with field selections from the JSON config.
|
|
445
|
+
// Mutates the event configs in-place to set selectedBlockFields/selectedTransactionFields.
|
|
446
|
+
let enrichEvmFieldSelections = (
|
|
447
|
+
events: array<Internal.eventConfig>,
|
|
448
|
+
~jsonEvents: option<array<_>>,
|
|
449
|
+
~globalBlockFieldsSet: Utils.Set.t<Internal.evmBlockField>,
|
|
450
|
+
~globalTransactionFieldsSet: Utils.Set.t<Internal.evmTransactionField>,
|
|
451
|
+
) => {
|
|
452
|
+
// Build a lookup by event name for events with per-event field overrides
|
|
453
|
+
let fieldsByName: Js.Dict.t<_> = Js.Dict.empty()
|
|
454
|
+
switch jsonEvents {
|
|
455
|
+
| Some(jes) =>
|
|
456
|
+
jes->Array.forEach(je => {
|
|
457
|
+
let name = je["name"]
|
|
458
|
+
if je["blockFields"] != None || je["transactionFields"] != None {
|
|
459
|
+
fieldsByName->Js.Dict.set(name, je)
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
| None => ()
|
|
463
|
+
}
|
|
464
|
+
events->Js.Array2.forEachi((event, i) => {
|
|
465
|
+
let evmEvent = event->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
|
|
466
|
+
let (selectedBlockFields, selectedTransactionFields) = switch fieldsByName->Js.Dict.get(
|
|
467
|
+
evmEvent.name,
|
|
468
|
+
) {
|
|
469
|
+
| Some(je) => (
|
|
470
|
+
switch je["blockFields"] {
|
|
471
|
+
| Some(fields) =>
|
|
472
|
+
// Prepend always-included block fields for per-event overrides too
|
|
473
|
+
Utils.Set.fromArray(
|
|
474
|
+
Array.concat(
|
|
475
|
+
alwaysIncludedBlockFields,
|
|
476
|
+
fields->(Utils.magic: array<string> => array<Internal.evmBlockField>),
|
|
477
|
+
),
|
|
478
|
+
)
|
|
479
|
+
| None => globalBlockFieldsSet
|
|
480
|
+
},
|
|
481
|
+
switch je["transactionFields"] {
|
|
482
|
+
| Some(fields) =>
|
|
483
|
+
Utils.Set.fromArray(
|
|
484
|
+
fields->(Utils.magic: array<string> => array<Internal.evmTransactionField>),
|
|
485
|
+
)
|
|
486
|
+
| None => globalTransactionFieldsSet
|
|
487
|
+
},
|
|
488
|
+
)
|
|
489
|
+
| None => (globalBlockFieldsSet, globalTransactionFieldsSet)
|
|
490
|
+
}
|
|
491
|
+
events->Js.Array2.unsafe_set(
|
|
492
|
+
i,
|
|
493
|
+
{...evmEvent, selectedBlockFields, selectedTransactionFields}->(
|
|
494
|
+
Utils.magic: Internal.evmEventConfig => Internal.eventConfig
|
|
495
|
+
),
|
|
496
|
+
)
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let fromPublic = (
|
|
501
|
+
publicConfigJson: Js.Json.t,
|
|
502
|
+
~codegenChains: array<codegenChain>=[],
|
|
66
503
|
~maxAddrInPartition=5000,
|
|
67
504
|
) => {
|
|
68
|
-
//
|
|
69
|
-
|
|
505
|
+
// Parse public config
|
|
506
|
+
let publicConfig = try publicConfigJson->S.parseOrThrow(publicConfigSchema) catch {
|
|
507
|
+
| S.Raised(exn) =>
|
|
70
508
|
Js.Exn.raiseError(
|
|
71
|
-
|
|
509
|
+
`Invalid internal.config.ts: ${exn->Utils.prettifyExn->(Utils.magic: exn => string)}`,
|
|
72
510
|
)
|
|
73
511
|
}
|
|
74
512
|
|
|
513
|
+
// Determine ecosystem from publicConfig (extract just chains for unified handling)
|
|
514
|
+
let (publicChainsConfig, ecosystemName) = switch (
|
|
515
|
+
publicConfig["evm"],
|
|
516
|
+
publicConfig["fuel"],
|
|
517
|
+
publicConfig["svm"],
|
|
518
|
+
) {
|
|
519
|
+
| (Some(ecosystemConfig), None, None) => (ecosystemConfig["chains"], Ecosystem.Evm)
|
|
520
|
+
| (None, Some(ecosystemConfig), None) => (ecosystemConfig["chains"], Ecosystem.Fuel)
|
|
521
|
+
| (None, None, Some(ecosystemConfig)) => (ecosystemConfig["chains"], Ecosystem.Svm)
|
|
522
|
+
| (None, None, None) =>
|
|
523
|
+
Js.Exn.raiseError("Invalid indexer config: No ecosystem configured (evm, fuel, or svm)")
|
|
524
|
+
| _ =>
|
|
525
|
+
Js.Exn.raiseError(
|
|
526
|
+
"Invalid indexer config: Multiple ecosystems are not supported for a single indexer",
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Extract EVM-specific options with defaults
|
|
531
|
+
let lowercaseAddresses = switch publicConfig["evm"] {
|
|
532
|
+
| Some(evm) => evm["addressFormat"]->Option.getWithDefault(Checksum) == Lowercase
|
|
533
|
+
| None => false
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Parse ABIs from public config
|
|
537
|
+
let publicContractsConfig = switch (ecosystemName, publicConfig["evm"], publicConfig["fuel"]) {
|
|
538
|
+
| (Ecosystem.Evm, Some(evm), _) => evm["contracts"]
|
|
539
|
+
| (Ecosystem.Fuel, _, Some(fuel)) => fuel["contracts"]
|
|
540
|
+
| _ => None
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Create global field selection Sets once (shared across events without per-event overrides)
|
|
544
|
+
let (globalBlockFieldsSet, globalTransactionFieldsSet) = switch publicConfig["evm"] {
|
|
545
|
+
| Some(evm) => (
|
|
546
|
+
Utils.Set.fromArray(
|
|
547
|
+
Array.concat(
|
|
548
|
+
alwaysIncludedBlockFields,
|
|
549
|
+
evm["globalBlockFields"]->Option.getWithDefault([]),
|
|
550
|
+
),
|
|
551
|
+
),
|
|
552
|
+
Utils.Set.fromArray(evm["globalTransactionFields"]->Option.getWithDefault([])),
|
|
553
|
+
)
|
|
554
|
+
| None => (Utils.Set.fromArray(alwaysIncludedBlockFields), Utils.Set.make())
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Store ABI and event signatures for each contract
|
|
558
|
+
let contractsWithAbis: Js.Dict.t<(EvmTypes.Abi.t, array<string>)> = Js.Dict.empty()
|
|
559
|
+
// Per-event field selection overrides, keyed by capitalized contract name
|
|
560
|
+
let jsonEventsByContract: Js.Dict.t<array<_>> = Js.Dict.empty()
|
|
561
|
+
switch publicContractsConfig {
|
|
562
|
+
| Some(contractsDict) =>
|
|
563
|
+
contractsDict
|
|
564
|
+
->Js.Dict.entries
|
|
565
|
+
->Array.forEach(((contractName, contractConfig)) => {
|
|
566
|
+
let capitalizedName = contractName->Utils.String.capitalize
|
|
567
|
+
let abi = contractConfig["abi"]->(Utils.magic: Js.Json.t => EvmTypes.Abi.t)
|
|
568
|
+
let eventSignatures = switch contractConfig["events"] {
|
|
569
|
+
| Some(events) => events->Array.map(eventItem => eventItem["event"])
|
|
570
|
+
| None => []
|
|
571
|
+
}
|
|
572
|
+
contractsWithAbis->Js.Dict.set(capitalizedName, (abi, eventSignatures))
|
|
573
|
+
switch contractConfig["events"] {
|
|
574
|
+
| Some(events) => jsonEventsByContract->Js.Dict.set(capitalizedName, events)
|
|
575
|
+
| None => ()
|
|
576
|
+
}
|
|
577
|
+
})
|
|
578
|
+
| None => ()
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Index codegenChains by id for efficient lookup
|
|
582
|
+
let codegenChainById = Js.Dict.empty()
|
|
583
|
+
codegenChains->Array.forEach(codegenChain => {
|
|
584
|
+
codegenChainById->Js.Dict.set(codegenChain.id->Int.toString, codegenChain)
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
// Create a dictionary to store merged contracts with ABIs by chain id
|
|
588
|
+
let contractsByChainId: Js.Dict.t<array<contract>> = Js.Dict.empty()
|
|
589
|
+
codegenChains->Array.forEach(codegenChain => {
|
|
590
|
+
let mergedContracts = codegenChain.contracts->Array.map(codegenContract => {
|
|
591
|
+
switch contractsWithAbis->Js.Dict.get(codegenContract.name) {
|
|
592
|
+
| Some((abi, eventSignatures)) =>
|
|
593
|
+
// Parse addresses based on ecosystem and address format
|
|
594
|
+
let parsedAddresses = codegenContract.addresses->Array.map(
|
|
595
|
+
addressString => {
|
|
596
|
+
switch ecosystemName {
|
|
597
|
+
| Ecosystem.Evm =>
|
|
598
|
+
if lowercaseAddresses {
|
|
599
|
+
addressString->Address.Evm.fromStringLowercaseOrThrow
|
|
600
|
+
} else {
|
|
601
|
+
addressString->Address.Evm.fromStringOrThrow
|
|
602
|
+
}
|
|
603
|
+
| Ecosystem.Fuel | Ecosystem.Svm => addressString->Address.unsafeFromString
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
// Enrich EVM event configs with field selections from JSON config
|
|
609
|
+
if ecosystemName == Ecosystem.Evm {
|
|
610
|
+
enrichEvmFieldSelections(
|
|
611
|
+
codegenContract.events,
|
|
612
|
+
~jsonEvents=jsonEventsByContract->Js.Dict.get(codegenContract.name),
|
|
613
|
+
~globalBlockFieldsSet,
|
|
614
|
+
~globalTransactionFieldsSet,
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
// Convert codegenContract to contract by adding abi and eventSignatures
|
|
618
|
+
{
|
|
619
|
+
name: codegenContract.name,
|
|
620
|
+
abi,
|
|
621
|
+
addresses: parsedAddresses,
|
|
622
|
+
events: codegenContract.events,
|
|
623
|
+
startBlock: codegenContract.startBlock,
|
|
624
|
+
eventSignatures,
|
|
625
|
+
}
|
|
626
|
+
| None =>
|
|
627
|
+
Js.Exn.raiseError(
|
|
628
|
+
`Contract "${codegenContract.name}" is missing ABI in public config (internal.config.ts)`,
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
})
|
|
632
|
+
contractsByChainId->Js.Dict.set(codegenChain.id->Int.toString, mergedContracts)
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
// Helper to convert parsed RPC config to evmRpcConfig
|
|
636
|
+
let parseRpcSourceFor = (sourceFor: rpcSourceFor): Source.sourceFor => {
|
|
637
|
+
switch sourceFor {
|
|
638
|
+
| Sync => Source.Sync
|
|
639
|
+
| Fallback => Source.Fallback
|
|
640
|
+
| Live => Source.Live
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Merge codegenChains with names from publicConfig
|
|
645
|
+
let chains =
|
|
646
|
+
publicChainsConfig
|
|
647
|
+
->Js.Dict.keys
|
|
648
|
+
->Js.Array2.map(chainName => {
|
|
649
|
+
let publicChainConfig = publicChainsConfig->Js.Dict.unsafeGet(chainName)
|
|
650
|
+
let chainId = publicChainConfig["id"]
|
|
651
|
+
let codegenChain = switch codegenChainById->Js.Dict.get(chainId->Int.toString) {
|
|
652
|
+
| Some(c) => c
|
|
653
|
+
| None =>
|
|
654
|
+
Js.Exn.raiseError(`Chain with id ${chainId->Int.toString} not found in codegen chains`)
|
|
655
|
+
}
|
|
656
|
+
let mergedContracts = switch contractsByChainId->Js.Dict.get(chainId->Int.toString) {
|
|
657
|
+
| Some(contracts) => contracts
|
|
658
|
+
| None =>
|
|
659
|
+
Js.Exn.raiseError(
|
|
660
|
+
`Contracts for chain with id ${chainId->Int.toString} not found in merged contracts`,
|
|
661
|
+
)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Build sourceConfig from the parsed chain config
|
|
665
|
+
let sourceConfig = switch ecosystemName {
|
|
666
|
+
| Ecosystem.Evm =>
|
|
667
|
+
let rpcs =
|
|
668
|
+
publicChainConfig["rpcs"]
|
|
669
|
+
->Option.getWithDefault([])
|
|
670
|
+
->Array.map((rpcConfig): evmRpcConfig => {
|
|
671
|
+
// Build syncConfig from flattened fields
|
|
672
|
+
let initialBlockInterval = rpcConfig["initialBlockInterval"]
|
|
673
|
+
let backoffMultiplicative = rpcConfig["backoffMultiplicative"]
|
|
674
|
+
let accelerationAdditive = rpcConfig["accelerationAdditive"]
|
|
675
|
+
let intervalCeiling = rpcConfig["intervalCeiling"]
|
|
676
|
+
let backoffMillis = rpcConfig["backoffMillis"]
|
|
677
|
+
let queryTimeoutMillis = rpcConfig["queryTimeoutMillis"]
|
|
678
|
+
let fallbackStallTimeout = rpcConfig["fallbackStallTimeout"]
|
|
679
|
+
let pollingInterval = rpcConfig["pollingInterval"]
|
|
680
|
+
let hasSyncConfig =
|
|
681
|
+
initialBlockInterval->Option.isSome ||
|
|
682
|
+
backoffMultiplicative->Option.isSome ||
|
|
683
|
+
accelerationAdditive->Option.isSome ||
|
|
684
|
+
intervalCeiling->Option.isSome ||
|
|
685
|
+
backoffMillis->Option.isSome ||
|
|
686
|
+
queryTimeoutMillis->Option.isSome ||
|
|
687
|
+
fallbackStallTimeout->Option.isSome ||
|
|
688
|
+
pollingInterval->Option.isSome
|
|
689
|
+
let syncConfig: option<sourceSyncOptions> = if hasSyncConfig {
|
|
690
|
+
Some({
|
|
691
|
+
?initialBlockInterval,
|
|
692
|
+
?backoffMultiplicative,
|
|
693
|
+
?accelerationAdditive,
|
|
694
|
+
?intervalCeiling,
|
|
695
|
+
?backoffMillis,
|
|
696
|
+
?queryTimeoutMillis,
|
|
697
|
+
?fallbackStallTimeout,
|
|
698
|
+
?pollingInterval,
|
|
699
|
+
})
|
|
700
|
+
} else {
|
|
701
|
+
None
|
|
702
|
+
}
|
|
703
|
+
{
|
|
704
|
+
url: rpcConfig["url"],
|
|
705
|
+
sourceFor: parseRpcSourceFor(rpcConfig["for"]),
|
|
706
|
+
syncConfig,
|
|
707
|
+
ws: rpcConfig["ws"],
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
EvmSourceConfig({hypersync: publicChainConfig["hypersync"], rpcs})
|
|
711
|
+
| Ecosystem.Fuel =>
|
|
712
|
+
switch publicChainConfig["hypersync"] {
|
|
713
|
+
| Some(hypersync) => FuelSourceConfig({hypersync: hypersync})
|
|
714
|
+
| None => Js.Exn.raiseError(`Chain ${chainName} is missing hypersync endpoint in config`)
|
|
715
|
+
}
|
|
716
|
+
| Ecosystem.Svm =>
|
|
717
|
+
switch publicChainConfig["rpc"] {
|
|
718
|
+
| Some(rpc) => SvmSourceConfig({rpc: rpc})
|
|
719
|
+
| None => Js.Exn.raiseError(`Chain ${chainName} is missing rpc endpoint in config`)
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
{
|
|
724
|
+
name: chainName,
|
|
725
|
+
id: codegenChain.id,
|
|
726
|
+
startBlock: publicChainConfig["startBlock"],
|
|
727
|
+
endBlock: ?publicChainConfig["endBlock"],
|
|
728
|
+
maxReorgDepth: switch ecosystemName {
|
|
729
|
+
| Ecosystem.Evm => publicChainConfig["maxReorgDepth"]->Option.getWithDefault(200)
|
|
730
|
+
// Fuel doesn't have reorgs, SVM reorg handling is not supported
|
|
731
|
+
| Ecosystem.Fuel | Ecosystem.Svm => 0
|
|
732
|
+
},
|
|
733
|
+
blockLag: publicChainConfig["blockLag"]->Option.getWithDefault(0),
|
|
734
|
+
contracts: mergedContracts,
|
|
735
|
+
sourceConfig,
|
|
736
|
+
}
|
|
737
|
+
})
|
|
738
|
+
|
|
75
739
|
let chainMap =
|
|
76
740
|
chains
|
|
77
|
-
->Js.Array2.map(
|
|
78
|
-
(ChainMap.Chain.makeUnsafe(~chainId=
|
|
741
|
+
->Js.Array2.map(chain => {
|
|
742
|
+
(ChainMap.Chain.makeUnsafe(~chainId=chain.id), chain)
|
|
79
743
|
})
|
|
80
744
|
->ChainMap.fromArrayUnsafe
|
|
81
745
|
|
|
@@ -88,20 +752,71 @@ let make = (
|
|
|
88
752
|
})
|
|
89
753
|
})
|
|
90
754
|
|
|
91
|
-
let ecosystem =
|
|
755
|
+
let ecosystem = switch ecosystemName {
|
|
756
|
+
| Ecosystem.Evm => Evm.ecosystem
|
|
757
|
+
| Ecosystem.Fuel => Fuel.ecosystem
|
|
758
|
+
| Ecosystem.Svm => Svm.ecosystem
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Parse enums and entities from JSON config
|
|
762
|
+
let allEnums =
|
|
763
|
+
publicConfig["enums"]
|
|
764
|
+
->Option.getWithDefault(Js.Dict.empty())
|
|
765
|
+
->parseEnumsFromJson
|
|
766
|
+
|
|
767
|
+
let enumConfigsByName =
|
|
768
|
+
allEnums
|
|
769
|
+
->Js.Array2.map(enumConfig => (enumConfig.name, enumConfig))
|
|
770
|
+
->Js.Dict.fromArray
|
|
771
|
+
|
|
772
|
+
let userEntities =
|
|
773
|
+
publicConfig["entities"]
|
|
774
|
+
->Option.getWithDefault([])
|
|
775
|
+
->parseEntitiesFromJson(~enumConfigsByName)
|
|
776
|
+
|
|
777
|
+
let allEntities = userEntities->Js.Array2.concat([DynamicContractRegistry.entityConfig])
|
|
778
|
+
|
|
779
|
+
let userEntitiesByName =
|
|
780
|
+
userEntities
|
|
781
|
+
->Js.Array2.map(entityConfig => {
|
|
782
|
+
(entityConfig.name, entityConfig)
|
|
783
|
+
})
|
|
784
|
+
->Js.Dict.fromArray
|
|
785
|
+
|
|
786
|
+
// Extract contract handlers from the public config
|
|
787
|
+
let contractHandlers = switch publicContractsConfig {
|
|
788
|
+
| Some(contractsDict) =>
|
|
789
|
+
contractsDict
|
|
790
|
+
->Js.Dict.entries
|
|
791
|
+
->Js.Array2.map(((contractName, contractConfig)) => {
|
|
792
|
+
{
|
|
793
|
+
name: contractName->Utils.String.capitalize,
|
|
794
|
+
handler: contractConfig["handler"],
|
|
795
|
+
}
|
|
796
|
+
})
|
|
797
|
+
| None => []
|
|
798
|
+
}
|
|
92
799
|
|
|
93
800
|
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
801
|
+
name: publicConfig["name"],
|
|
802
|
+
description: publicConfig["description"],
|
|
803
|
+
handlers: publicConfig["handlers"]->Option.getWithDefault("src/handlers"),
|
|
804
|
+
contractHandlers,
|
|
805
|
+
shouldRollbackOnReorg: publicConfig["rollbackOnReorg"]->Option.getWithDefault(true),
|
|
806
|
+
shouldSaveFullHistory: publicConfig["saveFullHistory"]->Option.getWithDefault(false),
|
|
807
|
+
multichain: publicConfig["multichain"]->Option.getWithDefault(Unordered),
|
|
97
808
|
chainMap,
|
|
98
809
|
defaultChain: chains->Array.get(0),
|
|
99
|
-
enableRawEvents,
|
|
810
|
+
enableRawEvents: publicConfig["rawEvents"]->Option.getWithDefault(false),
|
|
100
811
|
ecosystem,
|
|
101
812
|
maxAddrInPartition,
|
|
102
|
-
batchSize,
|
|
813
|
+
batchSize: publicConfig["fullBatchSize"]->Option.getWithDefault(5000),
|
|
103
814
|
lowercaseAddresses,
|
|
104
815
|
addContractNameToContractNameMapping,
|
|
816
|
+
userEntitiesByName,
|
|
817
|
+
userEntities,
|
|
818
|
+
allEntities,
|
|
819
|
+
allEnums,
|
|
105
820
|
}
|
|
106
821
|
}
|
|
107
822
|
|