envio 3.3.0-alpha.1 → 3.3.0-alpha.10
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/evm.schema.json +10 -0
- package/index.d.ts +45 -25
- package/package.json +7 -7
- package/src/Address.res +5 -2
- package/src/Address.res.mjs +3 -1
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/Batch.res +0 -6
- package/src/Batch.res.mjs +1 -12
- package/src/BatchProcessing.res +7 -12
- package/src/BatchProcessing.res.mjs +8 -7
- package/src/Bin.res +3 -0
- package/src/Bin.res.mjs +4 -0
- package/src/ChainFetching.res +43 -9
- package/src/ChainFetching.res.mjs +34 -15
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +605 -151
- package/src/ChainState.res.mjs +427 -104
- package/src/ChainState.resi +72 -4
- package/src/Config.res +61 -33
- package/src/Config.res.mjs +50 -10
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +14 -3
- package/src/CrossChainState.res +113 -89
- package/src/CrossChainState.res.mjs +64 -60
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +5 -6
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +17 -11
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +177 -88
- package/src/EventConfigBuilder.res.mjs +99 -42
- package/src/EventProcessing.res +74 -28
- package/src/EventProcessing.res.mjs +55 -29
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/FetchState.res +544 -356
- package/src/FetchState.res.mjs +412 -471
- package/src/HandlerLoader.res +8 -104
- package/src/HandlerLoader.res.mjs +2 -81
- package/src/HandlerRegister.res +518 -144
- package/src/HandlerRegister.res.mjs +285 -133
- package/src/HandlerRegister.resi +24 -8
- package/src/IndexerState.res +10 -3
- package/src/IndexerState.res.mjs +11 -4
- package/src/IndexerState.resi +2 -1
- package/src/IndexingAddresses.res +108 -0
- package/src/IndexingAddresses.res.mjs +101 -0
- package/src/IndexingAddresses.resi +34 -0
- package/src/Internal.res +155 -46
- package/src/Internal.res.mjs +30 -2
- package/src/LoadLayer.res +5 -5
- package/src/LoadLayer.res.mjs +6 -6
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- package/src/Main.res +71 -236
- package/src/Main.res.mjs +60 -145
- package/src/Metrics.res +74 -0
- package/src/Metrics.res.mjs +75 -0
- package/src/PgStorage.res +4 -4
- package/src/PgStorage.res.mjs +5 -5
- package/src/Prometheus.res +10 -63
- package/src/Prometheus.res.mjs +124 -171
- package/src/PruneStaleHistory.res +2 -2
- package/src/PruneStaleHistory.res.mjs +3 -3
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/Rollback.res +3 -3
- package/src/Rollback.res.mjs +4 -4
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateDeadInputTracker.res +85 -0
- package/src/SimulateDeadInputTracker.res.mjs +73 -0
- package/src/SimulateDeadInputTracker.resi +12 -0
- package/src/SimulateItems.res +91 -23
- package/src/SimulateItems.res.mjs +55 -32
- package/src/TestIndexer.res +54 -40
- package/src/TestIndexer.res.mjs +37 -30
- package/src/bindings/Performance.res +7 -0
- package/src/bindings/Performance.res.mjs +21 -0
- package/src/bindings/Performance.resi +7 -0
- package/src/bindings/Viem.res +0 -41
- package/src/bindings/Viem.res.mjs +1 -43
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +36 -33
- package/src/sources/EventRouter.res.mjs +13 -14
- package/src/sources/Evm.res +83 -72
- package/src/sources/Evm.res.mjs +53 -69
- package/src/sources/EvmChain.res +18 -20
- package/src/sources/EvmChain.res.mjs +15 -15
- package/src/sources/EvmRpcClient.res +74 -5
- package/src/sources/EvmRpcClient.res.mjs +13 -4
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +9 -6
- package/src/sources/Fuel.res.mjs +5 -10
- package/src/sources/HyperFuelSource.res +46 -50
- package/src/sources/HyperFuelSource.res.mjs +64 -54
- package/src/sources/HyperSync.res +39 -7
- package/src/sources/HyperSync.res.mjs +57 -37
- package/src/sources/HyperSync.resi +12 -2
- package/src/sources/HyperSyncClient.res +22 -111
- package/src/sources/HyperSyncClient.res.mjs +2 -30
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +94 -107
- package/src/sources/HyperSyncSource.res.mjs +82 -86
- package/src/sources/Rpc.res +15 -47
- package/src/sources/Rpc.res.mjs +25 -56
- package/src/sources/RpcSource.res +320 -467
- package/src/sources/RpcSource.res.mjs +268 -385
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +10 -4
- package/src/sources/SimulateSource.res.mjs +16 -6
- package/src/sources/Source.res +34 -6
- package/src/sources/SourceManager.res +77 -19
- package/src/sources/SourceManager.res.mjs +63 -21
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +31 -16
- package/src/sources/Svm.res.mjs +36 -13
- package/src/sources/SvmHyperSyncClient.res +5 -32
- package/src/sources/SvmHyperSyncSource.res +159 -134
- package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
- package/src/sources/TransactionStore.res +49 -0
- package/src/sources/TransactionStore.res.mjs +30 -0
- package/src/tui/Tui.res +13 -16
- package/src/tui/Tui.res.mjs +12 -14
- package/svm.schema.json +49 -37
- package/src/bindings/Hrtime.res +0 -58
- package/src/bindings/Hrtime.res.mjs +0 -90
- package/src/bindings/Hrtime.resi +0 -30
package/src/Main.res
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
type chainData =
|
|
2
|
-
chainId: float,
|
|
3
|
-
poweredByHyperSync: bool,
|
|
4
|
-
firstEventBlockNumber: option<int>,
|
|
5
|
-
latestProcessedBlock: option<int>,
|
|
6
|
-
timestampCaughtUpToHeadOrEndblock: option<Date.t>,
|
|
7
|
-
numEventsProcessed: float,
|
|
8
|
-
latestFetchedBlockNumber: int,
|
|
9
|
-
// Need this for API backwards compatibility
|
|
10
|
-
@as("currentBlockHeight")
|
|
11
|
-
knownHeight: int,
|
|
12
|
-
numBatchesFetched: int,
|
|
13
|
-
startBlock: int,
|
|
14
|
-
endBlock: option<int>,
|
|
15
|
-
numAddresses: int,
|
|
16
|
-
}
|
|
1
|
+
type chainData = ChainState.chainData
|
|
17
2
|
@tag("status")
|
|
18
3
|
type state =
|
|
19
4
|
| @as("disabled") Disabled({})
|
|
@@ -54,40 +39,24 @@ let stateSchema = S.union([
|
|
|
54
39
|
})),
|
|
55
40
|
])
|
|
56
41
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
_lte: option<int>,
|
|
65
|
-
_every: int,
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// `S.strict` rejects unknown fields so typos like `_gt` / `_evry` surface
|
|
69
|
-
// with a readable schema error pointing at the offending key, instead of
|
|
70
|
-
// silently registering a broken filter. `_every` defaults to 1 inside the
|
|
71
|
-
// schema so the caller always sees a plain `int`, and `intMin(1)` rejects
|
|
72
|
-
// zero/negative strides — `(blockNumber - startBlock) % 0` would crash and
|
|
73
|
-
// any negative stride would never match.
|
|
74
|
-
let blockRangeSchema: S.t<blockRange> = S.object(s => {
|
|
75
|
-
_gte: s.field("_gte", S.option(S.int)),
|
|
76
|
-
_lte: s.field("_lte", S.option(S.int)),
|
|
77
|
-
_every: s.field("_every", S.option(S.int->S.intMin(1))->S.Option.getOr(1)),
|
|
78
|
-
})->S.strict
|
|
79
|
-
|
|
80
|
-
let defaultBlockRange: blockRange = {_gte: None, _lte: None, _every: 1}
|
|
81
|
-
|
|
82
|
-
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))
|
|
83
49
|
|
|
84
50
|
// Persistence is set by Main.start before handler modules load, so that
|
|
85
51
|
// the exported indexer value can lazily expose DB state (startBlock,
|
|
86
52
|
// endBlock, isRealtime, dynamic contract addresses) once it's ready.
|
|
87
|
-
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))
|
|
88
57
|
|
|
89
58
|
let getInitialChainState = (~chainId: int): option<Persistence.initialChainState> => {
|
|
90
|
-
switch
|
|
59
|
+
switch getGlobalPersistence() {
|
|
91
60
|
| Some(persistence) =>
|
|
92
61
|
switch persistence.storageStatus {
|
|
93
62
|
| Ready(initialState) => initialState.chains->Array.find(c => c.id === chainId)
|
|
@@ -97,7 +66,7 @@ let getInitialChainState = (~chainId: int): option<Persistence.initialChainState
|
|
|
97
66
|
}
|
|
98
67
|
}
|
|
99
68
|
|
|
100
|
-
// Importing `generated` must not trigger `Config.
|
|
69
|
+
// Importing `generated` must not trigger `Config.load()`,
|
|
101
70
|
// so the exported indexer calls this lazily on first `indexer.chains` access.
|
|
102
71
|
let buildChainsObject = (~config: Config.t) => {
|
|
103
72
|
let chainIds = []
|
|
@@ -145,7 +114,7 @@ let buildChainsObject = (~config: Config.t) => {
|
|
|
145
114
|
{
|
|
146
115
|
enumerable: true,
|
|
147
116
|
get: () => {
|
|
148
|
-
switch
|
|
117
|
+
switch getIndexerState() {
|
|
149
118
|
| Some(state) => state->IndexerState.isRealtime
|
|
150
119
|
// Before the global state is available (eg during handler
|
|
151
120
|
// module load after resume), derive from persistence: every chain
|
|
@@ -179,22 +148,11 @@ let buildChainsObject = (~config: Config.t) => {
|
|
|
179
148
|
{
|
|
180
149
|
enumerable: true,
|
|
181
150
|
get: () => {
|
|
182
|
-
switch
|
|
151
|
+
switch getIndexerState() {
|
|
183
152
|
| Some(state) => {
|
|
184
153
|
let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
|
|
185
154
|
let chainState = state->IndexerState.getChainState(~chain)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Collect all addresses for this contract name from indexingAddresses
|
|
189
|
-
let addresses = []
|
|
190
|
-
let values = indexingAddresses->Dict.valuesToArray
|
|
191
|
-
for idx in 0 to values->Array.length - 1 {
|
|
192
|
-
let indexingContract = values->Array.getUnsafe(idx)
|
|
193
|
-
if indexingContract.contractName === contract.name {
|
|
194
|
-
addresses->Array.push(indexingContract.address)->ignore
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
addresses
|
|
155
|
+
chainState->ChainState.contractAddresses(~contractName=contract.name)
|
|
198
156
|
}
|
|
199
157
|
// Before the global state is available (eg during handler
|
|
200
158
|
// module load after resume), combine static addresses from config
|
|
@@ -371,121 +329,24 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
371
329
|
let _ = RollbackCommit.register(callback->(Utils.magic: 'a => RollbackCommit.callback))
|
|
372
330
|
}
|
|
373
331
|
|
|
374
|
-
// Two-stage parse: first the ecosystem-specific outer schema unwraps the
|
|
375
|
-
// wrapper (`block.number` / `block.height` / `slot`) and surfaces the
|
|
376
|
-
// inner chunk as raw `unknown`; then the shared `blockRangeSchema`
|
|
377
|
-
// validates the `{_gte?, _lte?, _every?}` fields. Keeping the inner
|
|
378
|
-
// validation in one place means typos and shape mismatches surface with
|
|
379
|
-
// the same user-friendly error regardless of ecosystem.
|
|
380
|
-
let extractRange = (filter: unknown, ~name, ~ecosystem: Ecosystem.t): blockRange =>
|
|
381
|
-
try {
|
|
382
|
-
switch filter->S.parseOrThrow(ecosystem.onBlockFilterSchema) {
|
|
383
|
-
| None => defaultBlockRange
|
|
384
|
-
| Some(inner) => inner->S.parseOrThrow(blockRangeSchema)
|
|
385
|
-
}
|
|
386
|
-
} catch {
|
|
387
|
-
| S.Raised(exn) =>
|
|
388
|
-
JsError.throwWithMessage(
|
|
389
|
-
`\`indexer.${ecosystem.onBlockMethodName}("${name}")\` \`where\` returned an invalid filter: ${exn
|
|
390
|
-
->Utils.prettifyExn
|
|
391
|
-
->(Utils.magic: exn => string)}`,
|
|
392
|
-
)
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// `where` is evaluated once per configured chain at registration time.
|
|
396
|
-
// Decoded ranges/stride feed directly into `HandlerRegister.registerOnBlock`
|
|
397
|
-
// so the fetcher's `(blockNumber - handlerStartBlock) % interval === 0`
|
|
398
|
-
// math at `FetchState.res:619` stays untouched.
|
|
399
332
|
let onBlockFn = (rawOptions: 'a, handler: 'b) => {
|
|
400
333
|
HandlerRegister.throwIfFinishedRegistration(~methodName="onBlock")
|
|
401
|
-
let config = Config.loadWithoutRegistrations()
|
|
402
|
-
let ecosystem = config.ecosystem
|
|
403
334
|
let raw =
|
|
404
335
|
rawOptions->(
|
|
405
336
|
Utils.magic: 'a => {
|
|
406
337
|
"name": string,
|
|
407
|
-
"where":
|
|
338
|
+
"where": unknown,
|
|
408
339
|
}
|
|
409
340
|
)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// Normalize undefined/null to None up front so the per-chain loop below
|
|
420
|
-
// can't accidentally call `null` as a predicate (ReScript treats a JS
|
|
421
|
-
// `null` value as `Some(null)` when the field is typed as option).
|
|
422
|
-
let where = switch raw["where"]->(Utils.magic: option<'a> => unknown) {
|
|
423
|
-
| w if w === %raw(`undefined`) || w === %raw(`null`) => None
|
|
424
|
-
| w if typeof(w) === #function => Some(raw["where"]->Option.getUnsafe)
|
|
425
|
-
| w =>
|
|
426
|
-
JsError.throwWithMessage(
|
|
427
|
-
`\`indexer.${ecosystem.onBlockMethodName}("${name}")\` expected \`where\` to be a function or omitted, but got ${(typeof(
|
|
428
|
-
w,
|
|
429
|
-
) :> string)}.`,
|
|
430
|
-
)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
let matchedAny = ref(false)
|
|
434
|
-
|
|
435
|
-
config.chainMap
|
|
436
|
-
->ChainMap.values
|
|
437
|
-
->Array.forEach(chainConfig => {
|
|
438
|
-
let chainId = chainConfig.id
|
|
439
|
-
let chainObj = chainsDict->Dict.getUnsafe(chainId->Int.toString)
|
|
440
|
-
|
|
441
|
-
// Predicate returns `true` → match with no filter; `false` → skip;
|
|
442
|
-
// any plain object → structured filter. `undefined`/`null` returns
|
|
443
|
-
// are rejected — the TS type excludes `void`, so a missing return is
|
|
444
|
-
// a user bug we surface early rather than silently match-all.
|
|
445
|
-
let result = switch where {
|
|
446
|
-
| None => %raw(`true`)
|
|
447
|
-
| Some(predicate) => predicate({chain: chainObj})
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
let (shouldRegister, range) = if result === %raw(`true`) {
|
|
451
|
-
(true, defaultBlockRange)
|
|
452
|
-
} else if result === %raw(`false`) {
|
|
453
|
-
(false, defaultBlockRange)
|
|
454
|
-
} else if typeof(result) === #object && !(result->Array.isArray) && result !== %raw(`null`) {
|
|
455
|
-
(true, extractRange(result, ~name, ~ecosystem))
|
|
456
|
-
} else {
|
|
457
|
-
// Reject numbers, strings, functions, arrays, undefined, null —
|
|
458
|
-
// anything that isn't bool or a plain object would silently
|
|
459
|
-
// misregister.
|
|
460
|
-
JsError.throwWithMessage(
|
|
461
|
-
`\`indexer.${ecosystem.onBlockMethodName}("${name}")\` \`where\` predicate returned an invalid value of type ${(typeof(
|
|
462
|
-
result,
|
|
463
|
-
) :> string)}. Expected boolean or a filter object.`,
|
|
464
|
-
)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if shouldRegister {
|
|
468
|
-
matchedAny := true
|
|
469
|
-
HandlerRegister.registerOnBlock(
|
|
470
|
-
~name,
|
|
471
|
-
~chainId,
|
|
472
|
-
~interval=range._every,
|
|
473
|
-
~startBlock=range._gte,
|
|
474
|
-
~endBlock=range._lte,
|
|
475
|
-
~handler=typedHandler,
|
|
476
|
-
)
|
|
477
|
-
}
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
// Catches misconfigured `where` predicates that return `false` for every
|
|
481
|
-
// configured chain — the handler would otherwise never fire with no hint.
|
|
482
|
-
// Includes the ecosystem-specific method name so SVM users see "onSlot"
|
|
483
|
-
// and don't get confused looking for a "Block handler" they never wrote.
|
|
484
|
-
if !matchedAny.contents {
|
|
485
|
-
logger->Logging.childWarn(
|
|
486
|
-
`\`indexer.${ecosystem.onBlockMethodName}\` matched 0 chains. Check the \`where\` predicate.`,
|
|
487
|
-
)
|
|
488
|
-
}
|
|
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
|
+
)
|
|
489
350
|
}
|
|
490
351
|
|
|
491
352
|
// Ecosystem-specific surface: EVM/Fuel expose event + block handlers; SVM
|
|
@@ -497,13 +358,13 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
497
358
|
// `Api.res` calls `getGlobalIndexer()` at envio-package load, so the keys
|
|
498
359
|
// array is memoized lazily: an early `createEffect` / `S` import that
|
|
499
360
|
// never touches the indexer must not trigger a config parse. The memo is
|
|
500
|
-
// safe because `Config.
|
|
361
|
+
// safe because `Config.load` is itself pure.
|
|
501
362
|
let keysMemo: ref<option<array<string>>> = ref(None)
|
|
502
363
|
let getKeys = () =>
|
|
503
364
|
switch keysMemo.contents {
|
|
504
365
|
| Some(k) => k
|
|
505
366
|
| None => {
|
|
506
|
-
let keys = switch Config.
|
|
367
|
+
let keys = switch Config.load().ecosystem.name {
|
|
507
368
|
| Evm | Fuel => [
|
|
508
369
|
"name",
|
|
509
370
|
"description",
|
|
@@ -531,15 +392,14 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
531
392
|
|
|
532
393
|
let get = (~prop: string) =>
|
|
533
394
|
switch prop {
|
|
534
|
-
| "name" => Config.
|
|
535
|
-
| "description" =>
|
|
536
|
-
Config.loadWithoutRegistrations().description->(Utils.magic: option<string> => unknown)
|
|
395
|
+
| "name" => Config.load().name->(Utils.magic: string => unknown)
|
|
396
|
+
| "description" => Config.load().description->(Utils.magic: option<string> => unknown)
|
|
537
397
|
| "chainIds" => {
|
|
538
|
-
let (_, chainIds) = buildChainsObject(~config=Config.
|
|
398
|
+
let (_, chainIds) = buildChainsObject(~config=Config.load())
|
|
539
399
|
chainIds->(Utils.magic: array<int> => unknown)
|
|
540
400
|
}
|
|
541
401
|
| "chains" => {
|
|
542
|
-
let (chains, _) = buildChainsObject(~config=Config.
|
|
402
|
+
let (chains, _) = buildChainsObject(~config=Config.load())
|
|
543
403
|
chains->(Utils.magic: {..} => unknown)
|
|
544
404
|
}
|
|
545
405
|
| "onEvent" => onEventFn->Utils.magic
|
|
@@ -642,9 +502,9 @@ let startServer = (~getState, ~persistence: Persistence.t, ~isDevelopmentMode: b
|
|
|
642
502
|
app->get("/metrics", (_req, res) => {
|
|
643
503
|
res->set("Content-Type", PromClient.defaultRegister->PromClient.getContentType)
|
|
644
504
|
let _ =
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
505
|
+
Metrics.collect(~state=getIndexerState())->Promise.thenResolve(metrics =>
|
|
506
|
+
res->endWithData(metrics)
|
|
507
|
+
)
|
|
648
508
|
})
|
|
649
509
|
|
|
650
510
|
app->get("/metrics/runtime", (_req, res) => {
|
|
@@ -683,7 +543,7 @@ type mainArgs = Yargs.parsedArgs<args>
|
|
|
683
543
|
let getEnvioInfo = () => Config.getPublicConfigJson()->Config.stripSensitiveData
|
|
684
544
|
|
|
685
545
|
let migrate = async (~reset) => {
|
|
686
|
-
let config = Config.
|
|
546
|
+
let config = Config.load()
|
|
687
547
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
688
548
|
await persistence->Persistence.init(
|
|
689
549
|
~reset,
|
|
@@ -696,18 +556,22 @@ let migrate = async (~reset) => {
|
|
|
696
556
|
}
|
|
697
557
|
|
|
698
558
|
let dropSchema = async () => {
|
|
699
|
-
let config = Config.
|
|
559
|
+
let config = Config.load()
|
|
700
560
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
701
561
|
await persistence.storage.reset()
|
|
702
562
|
await persistence.storage.close()
|
|
703
563
|
}
|
|
704
564
|
|
|
565
|
+
// Rejection carried by `onError`: the failure is already logged with full
|
|
566
|
+
// context, so callers should act on it (exit / re-throw) without logging again.
|
|
567
|
+
exception FatalError(exn)
|
|
568
|
+
|
|
705
569
|
let start = async (
|
|
706
570
|
~persistence: option<Persistence.t>=?,
|
|
707
571
|
~reset=false,
|
|
708
572
|
~isTest=false,
|
|
709
573
|
~exitAfterFirstEventBlock=false,
|
|
710
|
-
~patchConfig: option<(Config.t, HandlerRegister.
|
|
574
|
+
~patchConfig: option<(Config.t, HandlerRegister.registrationsByChainId) => Config.t>=?,
|
|
711
575
|
) => {
|
|
712
576
|
let mainArgs: mainArgs = process->argv->Yargs.hideBin->Yargs.yargs->Yargs.argv
|
|
713
577
|
let explicitTui = switch mainArgs.tuiOff {
|
|
@@ -721,31 +585,30 @@ let start = async (
|
|
|
721
585
|
}
|
|
722
586
|
// Initialize persistence first so the exported indexer value contains state from the database
|
|
723
587
|
// when handler files are loaded (they may access the indexer at module top level).
|
|
724
|
-
let
|
|
588
|
+
let config = Config.load()
|
|
725
589
|
// isDevelopmentMode controls whether the indexer stays alive after all
|
|
726
590
|
// chains finish (keepProcessAlive) and whether the console API is exposed.
|
|
727
591
|
// Set by `envio dev` via the public config's `isDev` field; `envio start`
|
|
728
592
|
// leaves it false so the process exits cleanly when indexing completes.
|
|
729
|
-
let isDevelopmentMode = !isTest &&
|
|
593
|
+
let isDevelopmentMode = !isTest && config.isDev
|
|
730
594
|
let persistence = switch persistence {
|
|
731
595
|
| Some(p) => p
|
|
732
|
-
| None => PgStorage.makePersistenceFromConfig(~config
|
|
596
|
+
| None => PgStorage.makePersistenceFromConfig(~config)
|
|
733
597
|
}
|
|
734
|
-
|
|
598
|
+
setGlobalPersistence(persistence)
|
|
735
599
|
await persistence->Persistence.init(
|
|
736
600
|
~reset,
|
|
737
|
-
~chainConfigs=
|
|
601
|
+
~chainConfigs=config.chainMap->ChainMap.values,
|
|
738
602
|
~envioInfo=getEnvioInfo(),
|
|
739
603
|
~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
|
|
740
604
|
~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
|
|
741
605
|
)
|
|
742
606
|
|
|
743
|
-
//
|
|
744
|
-
//
|
|
745
|
-
//
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
)
|
|
607
|
+
// Loads user handler files, which register handler/contractRegister/where
|
|
608
|
+
// state into the global `HandlerRegister` registry as a side effect; this
|
|
609
|
+
// returns that state resolved into per-chain registrations. `config` itself
|
|
610
|
+
// is never mutated by registration — it holds only event definitions.
|
|
611
|
+
let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config)
|
|
749
612
|
let config = if isTest {
|
|
750
613
|
{...config, shouldRollbackOnReorg: false}
|
|
751
614
|
} else {
|
|
@@ -753,21 +616,24 @@ let start = async (
|
|
|
753
616
|
}
|
|
754
617
|
|
|
755
618
|
let config = switch patchConfig {
|
|
756
|
-
| Some(patchConfig) => patchConfig(config,
|
|
619
|
+
| Some(patchConfig) => patchConfig(config, registrationsByChainId)
|
|
757
620
|
| None => config
|
|
758
621
|
}
|
|
759
622
|
// The single fatal-error handler, invoked once via IndexerState.errorExit.
|
|
623
|
+
// It logs the failure once (with chain context) and rejects the run wrapped in
|
|
624
|
+
// `FatalError` so callers know it's already logged — `Bin.res` just exits, the
|
|
625
|
+
// test worker unwraps and re-throws it to the parent thread. `runUntilFatalError`
|
|
626
|
+
// only ever rejects: on a clean run it stays pending and the process exits via
|
|
627
|
+
// ExitOnCaughtUp / when the indexer loop drains.
|
|
628
|
+
let onErrorReject = ref(None)
|
|
629
|
+
let runUntilFatalError: promise<unit> = Promise.make((_resolve, reject) =>
|
|
630
|
+
onErrorReject := Some(reject)
|
|
631
|
+
)
|
|
632
|
+
// `onErrorReject` is filled synchronously by `Promise.make` above, before the
|
|
633
|
+
// indexer can run and call `onError`, so it's always present here.
|
|
760
634
|
let onError = (errHandler: ErrorHandling.t) => {
|
|
761
635
|
errHandler->ErrorHandling.log
|
|
762
|
-
|
|
763
|
-
// The TestIndexer runs the indexer in a worker thread and reads the
|
|
764
|
-
// failure off the worker 'error' event. Re-throw the original error
|
|
765
|
-
// outside the promise chain on the next tick so the test sees its real
|
|
766
|
-
// message instead of a generic non-zero exit code.
|
|
767
|
-
NodeJs.setImmediate(() => errHandler->ErrorHandling.raiseExn)
|
|
768
|
-
} else {
|
|
769
|
-
NodeJs.process->NodeJs.exitWithCode(Failure)
|
|
770
|
-
}
|
|
636
|
+
(onErrorReject.contents->Option.getUnsafe)(FatalError(errHandler.exn->Utils.prettifyExn))
|
|
771
637
|
}
|
|
772
638
|
let envioVersion = Utils.EnvioPackage.value.version
|
|
773
639
|
Prometheus.Info.set(~version=envioVersion)
|
|
@@ -776,43 +642,11 @@ let start = async (
|
|
|
776
642
|
|
|
777
643
|
if !isTest {
|
|
778
644
|
startServer(~persistence, ~isDevelopmentMode, ~getState=() =>
|
|
779
|
-
switch
|
|
645
|
+
switch getIndexerState() {
|
|
780
646
|
| None => Initializing({})
|
|
781
647
|
| Some(state) => {
|
|
782
648
|
let chains =
|
|
783
|
-
state
|
|
784
|
-
->IndexerState.chainStates
|
|
785
|
-
->Dict.valuesToArray
|
|
786
|
-
->Array.map(cs => {
|
|
787
|
-
let fetchState = cs->ChainState.fetchState
|
|
788
|
-
let latestFetchedBlockNumber = Pervasives.max(
|
|
789
|
-
FetchState.bufferBlockNumber(fetchState),
|
|
790
|
-
0,
|
|
791
|
-
)
|
|
792
|
-
let knownHeight =
|
|
793
|
-
cs->ChainState.hasProcessedToEndblock
|
|
794
|
-
? fetchState.endBlock->Option.getOr(fetchState.knownHeight)
|
|
795
|
-
: fetchState.knownHeight
|
|
796
|
-
|
|
797
|
-
{
|
|
798
|
-
chainId: (cs->ChainState.chainConfig).id->Int.toFloat,
|
|
799
|
-
poweredByHyperSync: (
|
|
800
|
-
cs->ChainState.sourceManager->SourceManager.getActiveSource
|
|
801
|
-
).poweredByHyperSync,
|
|
802
|
-
latestFetchedBlockNumber,
|
|
803
|
-
knownHeight,
|
|
804
|
-
numBatchesFetched: 0,
|
|
805
|
-
startBlock: fetchState.startBlock,
|
|
806
|
-
endBlock: fetchState.endBlock,
|
|
807
|
-
firstEventBlockNumber: fetchState.firstEventBlock,
|
|
808
|
-
latestProcessedBlock: cs->ChainState.committedProgressBlockNumber === -1
|
|
809
|
-
? None
|
|
810
|
-
: Some(cs->ChainState.committedProgressBlockNumber),
|
|
811
|
-
timestampCaughtUpToHeadOrEndblock: cs->ChainState.timestampCaughtUpToHeadOrEndblock,
|
|
812
|
-
numEventsProcessed: cs->ChainState.numEventsProcessed,
|
|
813
|
-
numAddresses: fetchState->FetchState.numAddresses,
|
|
814
|
-
}
|
|
815
|
-
})
|
|
649
|
+
state->IndexerState.chainStates->Dict.valuesToArray->Array.map(ChainState.toChainData)
|
|
816
650
|
Active({
|
|
817
651
|
envioVersion,
|
|
818
652
|
chains,
|
|
@@ -829,7 +663,7 @@ let start = async (
|
|
|
829
663
|
~config,
|
|
830
664
|
~persistence,
|
|
831
665
|
~initialState=persistence->Persistence.getInitializedState,
|
|
832
|
-
~
|
|
666
|
+
~registrationsByChainId,
|
|
833
667
|
~isDevelopmentMode,
|
|
834
668
|
~shouldUseTui,
|
|
835
669
|
~exitAfterFirstEventBlock,
|
|
@@ -838,6 +672,7 @@ let start = async (
|
|
|
838
672
|
if shouldUseTui {
|
|
839
673
|
let _rerender = Tui.start(~getState=() => state)
|
|
840
674
|
}
|
|
841
|
-
|
|
675
|
+
setIndexerState(state)
|
|
842
676
|
state->IndexerLoop.start
|
|
677
|
+
await runUntilFatalError
|
|
843
678
|
}
|