envio 3.3.0-alpha.7 → 3.3.0-alpha.9
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 +23 -5
- package/package.json +6 -6
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/ChainFetching.res +21 -10
- package/src/ChainFetching.res.mjs +11 -10
- package/src/ChainState.res +401 -171
- package/src/ChainState.res.mjs +270 -104
- package/src/ChainState.resi +19 -7
- package/src/Config.res +11 -27
- package/src/Config.res.mjs +8 -7
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +82 -82
- package/src/CrossChainState.res.mjs +52 -59
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +3 -3
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +14 -9
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +117 -69
- package/src/EventConfigBuilder.res.mjs +71 -34
- package/src/EventProcessing.res +19 -15
- package/src/EventProcessing.res.mjs +13 -12
- package/src/FetchState.res +410 -185
- package/src/FetchState.res.mjs +268 -253
- package/src/HandlerLoader.res +8 -113
- package/src/HandlerLoader.res.mjs +2 -88
- package/src/HandlerRegister.res +518 -144
- package/src/HandlerRegister.res.mjs +285 -133
- package/src/HandlerRegister.resi +24 -8
- package/src/IndexerState.res +6 -3
- package/src/IndexerState.res.mjs +3 -3
- package/src/IndexerState.resi +1 -1
- package/src/IndexingAddresses.res +10 -7
- package/src/IndexingAddresses.res.mjs +8 -7
- package/src/IndexingAddresses.resi +3 -1
- package/src/Internal.res +104 -39
- package/src/Internal.res.mjs +11 -1
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- package/src/Main.res +49 -164
- package/src/Main.res.mjs +39 -104
- package/src/Metrics.res +43 -2
- package/src/Metrics.res.mjs +39 -3
- package/src/Prometheus.res +0 -35
- package/src/Prometheus.res.mjs +33 -68
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateItems.res +20 -7
- package/src/SimulateItems.res.mjs +7 -11
- package/src/TestIndexer.res +1 -1
- package/src/TestIndexer.res.mjs +1 -1
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +19 -12
- package/src/sources/EventRouter.res.mjs +7 -5
- package/src/sources/Evm.res +58 -5
- package/src/sources/Evm.res.mjs +44 -5
- package/src/sources/EvmChain.res +14 -18
- package/src/sources/EvmChain.res.mjs +12 -13
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +8 -3
- package/src/sources/Fuel.res.mjs +5 -4
- package/src/sources/HyperFuelSource.res +31 -40
- package/src/sources/HyperFuelSource.res.mjs +52 -44
- package/src/sources/HyperSync.res +31 -9
- package/src/sources/HyperSync.res.mjs +47 -31
- package/src/sources/HyperSync.resi +10 -3
- package/src/sources/HyperSyncClient.res +15 -4
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +58 -90
- package/src/sources/HyperSyncSource.res.mjs +57 -66
- package/src/sources/RpcSource.res +118 -120
- package/src/sources/RpcSource.res.mjs +92 -74
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +5 -3
- package/src/sources/SimulateSource.res.mjs +12 -4
- package/src/sources/Source.res +19 -5
- package/src/sources/SourceManager.res +60 -10
- package/src/sources/SourceManager.res.mjs +54 -8
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +14 -10
- package/src/sources/Svm.res.mjs +23 -6
- package/src/sources/SvmHyperSyncClient.res +5 -6
- package/src/sources/SvmHyperSyncSource.res +82 -41
- package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
- package/src/sources/TransactionStore.res +6 -107
- package/src/sources/TransactionStore.res.mjs +5 -86
- package/svm.schema.json +19 -0
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)
|
|
@@ -82,7 +66,7 @@ let getInitialChainState = (~chainId: int): option<Persistence.initialChainState
|
|
|
82
66
|
}
|
|
83
67
|
}
|
|
84
68
|
|
|
85
|
-
// Importing `generated` must not trigger `Config.
|
|
69
|
+
// Importing `generated` must not trigger `Config.load()`,
|
|
86
70
|
// so the exported indexer calls this lazily on first `indexer.chains` access.
|
|
87
71
|
let buildChainsObject = (~config: Config.t) => {
|
|
88
72
|
let chainIds = []
|
|
@@ -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.loadWithoutRegistrations()
|
|
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
|
|
@@ -471,13 +358,13 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
471
358
|
// `Api.res` calls `getGlobalIndexer()` at envio-package load, so the keys
|
|
472
359
|
// array is memoized lazily: an early `createEffect` / `S` import that
|
|
473
360
|
// never touches the indexer must not trigger a config parse. The memo is
|
|
474
|
-
// safe because `Config.
|
|
361
|
+
// safe because `Config.load` is itself pure.
|
|
475
362
|
let keysMemo: ref<option<array<string>>> = ref(None)
|
|
476
363
|
let getKeys = () =>
|
|
477
364
|
switch keysMemo.contents {
|
|
478
365
|
| Some(k) => k
|
|
479
366
|
| None => {
|
|
480
|
-
let keys = switch Config.
|
|
367
|
+
let keys = switch Config.load().ecosystem.name {
|
|
481
368
|
| Evm | Fuel => [
|
|
482
369
|
"name",
|
|
483
370
|
"description",
|
|
@@ -505,15 +392,14 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
505
392
|
|
|
506
393
|
let get = (~prop: string) =>
|
|
507
394
|
switch prop {
|
|
508
|
-
| "name" => Config.
|
|
509
|
-
| "description" =>
|
|
510
|
-
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)
|
|
511
397
|
| "chainIds" => {
|
|
512
|
-
let (_, chainIds) = buildChainsObject(~config=Config.
|
|
398
|
+
let (_, chainIds) = buildChainsObject(~config=Config.load())
|
|
513
399
|
chainIds->(Utils.magic: array<int> => unknown)
|
|
514
400
|
}
|
|
515
401
|
| "chains" => {
|
|
516
|
-
let (chains, _) = buildChainsObject(~config=Config.
|
|
402
|
+
let (chains, _) = buildChainsObject(~config=Config.load())
|
|
517
403
|
chains->(Utils.magic: {..} => unknown)
|
|
518
404
|
}
|
|
519
405
|
| "onEvent" => onEventFn->Utils.magic
|
|
@@ -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
|
})
|
|
@@ -657,7 +543,7 @@ type mainArgs = Yargs.parsedArgs<args>
|
|
|
657
543
|
let getEnvioInfo = () => Config.getPublicConfigJson()->Config.stripSensitiveData
|
|
658
544
|
|
|
659
545
|
let migrate = async (~reset) => {
|
|
660
|
-
let config = Config.
|
|
546
|
+
let config = Config.load()
|
|
661
547
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
662
548
|
await persistence->Persistence.init(
|
|
663
549
|
~reset,
|
|
@@ -670,7 +556,7 @@ let migrate = async (~reset) => {
|
|
|
670
556
|
}
|
|
671
557
|
|
|
672
558
|
let dropSchema = async () => {
|
|
673
|
-
let config = Config.
|
|
559
|
+
let config = Config.load()
|
|
674
560
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
675
561
|
await persistence.storage.reset()
|
|
676
562
|
await persistence.storage.close()
|
|
@@ -685,7 +571,7 @@ let start = async (
|
|
|
685
571
|
~reset=false,
|
|
686
572
|
~isTest=false,
|
|
687
573
|
~exitAfterFirstEventBlock=false,
|
|
688
|
-
~patchConfig: option<(Config.t, HandlerRegister.
|
|
574
|
+
~patchConfig: option<(Config.t, HandlerRegister.registrationsByChainId) => Config.t>=?,
|
|
689
575
|
) => {
|
|
690
576
|
let mainArgs: mainArgs = process->argv->Yargs.hideBin->Yargs.yargs->Yargs.argv
|
|
691
577
|
let explicitTui = switch mainArgs.tuiOff {
|
|
@@ -699,31 +585,30 @@ let start = async (
|
|
|
699
585
|
}
|
|
700
586
|
// Initialize persistence first so the exported indexer value contains state from the database
|
|
701
587
|
// when handler files are loaded (they may access the indexer at module top level).
|
|
702
|
-
let
|
|
588
|
+
let config = Config.load()
|
|
703
589
|
// isDevelopmentMode controls whether the indexer stays alive after all
|
|
704
590
|
// chains finish (keepProcessAlive) and whether the console API is exposed.
|
|
705
591
|
// Set by `envio dev` via the public config's `isDev` field; `envio start`
|
|
706
592
|
// leaves it false so the process exits cleanly when indexing completes.
|
|
707
|
-
let isDevelopmentMode = !isTest &&
|
|
593
|
+
let isDevelopmentMode = !isTest && config.isDev
|
|
708
594
|
let persistence = switch persistence {
|
|
709
595
|
| Some(p) => p
|
|
710
|
-
| None => PgStorage.makePersistenceFromConfig(~config
|
|
596
|
+
| None => PgStorage.makePersistenceFromConfig(~config)
|
|
711
597
|
}
|
|
712
|
-
|
|
598
|
+
setGlobalPersistence(persistence)
|
|
713
599
|
await persistence->Persistence.init(
|
|
714
600
|
~reset,
|
|
715
|
-
~chainConfigs=
|
|
601
|
+
~chainConfigs=config.chainMap->ChainMap.values,
|
|
716
602
|
~envioInfo=getEnvioInfo(),
|
|
717
603
|
~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
|
|
718
604
|
~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
|
|
719
605
|
)
|
|
720
606
|
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
)
|
|
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)
|
|
727
612
|
let config = if isTest {
|
|
728
613
|
{...config, shouldRollbackOnReorg: false}
|
|
729
614
|
} else {
|
|
@@ -731,7 +616,7 @@ let start = async (
|
|
|
731
616
|
}
|
|
732
617
|
|
|
733
618
|
let config = switch patchConfig {
|
|
734
|
-
| Some(patchConfig) => patchConfig(config,
|
|
619
|
+
| Some(patchConfig) => patchConfig(config, registrationsByChainId)
|
|
735
620
|
| None => config
|
|
736
621
|
}
|
|
737
622
|
// The single fatal-error handler, invoked once via IndexerState.errorExit.
|
|
@@ -757,7 +642,7 @@ let start = async (
|
|
|
757
642
|
|
|
758
643
|
if !isTest {
|
|
759
644
|
startServer(~persistence, ~isDevelopmentMode, ~getState=() =>
|
|
760
|
-
switch
|
|
645
|
+
switch getIndexerState() {
|
|
761
646
|
| None => Initializing({})
|
|
762
647
|
| Some(state) => {
|
|
763
648
|
let chains =
|
|
@@ -778,7 +663,7 @@ let start = async (
|
|
|
778
663
|
~config,
|
|
779
664
|
~persistence,
|
|
780
665
|
~initialState=persistence->Persistence.getInitializedState,
|
|
781
|
-
~
|
|
666
|
+
~registrationsByChainId,
|
|
782
667
|
~isDevelopmentMode,
|
|
783
668
|
~shouldUseTui,
|
|
784
669
|
~exitAfterFirstEventBlock,
|
|
@@ -787,7 +672,7 @@ let start = async (
|
|
|
787
672
|
if shouldUseTui {
|
|
788
673
|
let _rerender = Tui.start(~getState=() => state)
|
|
789
674
|
}
|
|
790
|
-
|
|
675
|
+
setIndexerState(state)
|
|
791
676
|
state->IndexerLoop.start
|
|
792
677
|
await runUntilFatalError
|
|
793
678
|
}
|
package/src/Main.res.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import * as ChainMap from "./ChainMap.res.mjs";
|
|
|
13
13
|
import * as PgStorage from "./PgStorage.res.mjs";
|
|
14
14
|
import * as ChainState from "./ChainState.res.mjs";
|
|
15
15
|
import * as Prometheus from "./Prometheus.res.mjs";
|
|
16
|
+
import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
|
|
16
17
|
import * as IndexerLoop from "./IndexerLoop.res.mjs";
|
|
17
18
|
import * as Persistence from "./Persistence.res.mjs";
|
|
18
19
|
import * as PromClient from "prom-client";
|
|
@@ -61,28 +62,24 @@ let stateSchema = S$RescriptSchema.union([
|
|
|
61
62
|
}))
|
|
62
63
|
]);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
_every: s.f("_every", S$RescriptSchema.Option.getOr(S$RescriptSchema.option(S$RescriptSchema.intMin(S$RescriptSchema.int, 1, undefined)), 1))
|
|
68
|
-
})));
|
|
65
|
+
function getIndexerState() {
|
|
66
|
+
return EnvioGlobal.value.indexerState;
|
|
67
|
+
}
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
_every: 1
|
|
74
|
-
};
|
|
69
|
+
function setIndexerState(state) {
|
|
70
|
+
EnvioGlobal.value.indexerState = Primitive_option.some(state);
|
|
71
|
+
}
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
73
|
+
function getGlobalPersistence() {
|
|
74
|
+
return EnvioGlobal.value.persistence;
|
|
75
|
+
}
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
77
|
+
function setGlobalPersistence(persistence) {
|
|
78
|
+
EnvioGlobal.value.persistence = Primitive_option.some(persistence);
|
|
79
|
+
}
|
|
83
80
|
|
|
84
81
|
function getInitialChainState(chainId) {
|
|
85
|
-
let persistence =
|
|
82
|
+
let persistence = EnvioGlobal.value.persistence;
|
|
86
83
|
if (persistence === undefined) {
|
|
87
84
|
return;
|
|
88
85
|
}
|
|
@@ -134,7 +131,7 @@ function buildChainsObject(config) {
|
|
|
134
131
|
}), "isRealtime", {
|
|
135
132
|
enumerable: true,
|
|
136
133
|
get: () => {
|
|
137
|
-
let state =
|
|
134
|
+
let state = EnvioGlobal.value.indexerState;
|
|
138
135
|
if (state !== undefined) {
|
|
139
136
|
return IndexerState.isRealtime(Primitive_option.valFromOption(state));
|
|
140
137
|
} else if (Env.updateSyncTimeOnRestart) {
|
|
@@ -162,7 +159,7 @@ function buildChainsObject(config) {
|
|
|
162
159
|
}), "addresses", {
|
|
163
160
|
enumerable: true,
|
|
164
161
|
get: () => {
|
|
165
|
-
let state =
|
|
162
|
+
let state = EnvioGlobal.value.indexerState;
|
|
166
163
|
if (state !== undefined) {
|
|
167
164
|
let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
|
|
168
165
|
let chainState = IndexerState.getChainState(Primitive_option.valFromOption(state), chain);
|
|
@@ -278,69 +275,9 @@ function getGlobalIndexer() {
|
|
|
278
275
|
HandlerRegister.throwIfFinishedRegistration("~internalAndWillBeRemovedSoon_onRollbackCommit");
|
|
279
276
|
RollbackCommit.register(callback);
|
|
280
277
|
};
|
|
281
|
-
let extractRange = (filter, name, ecosystem) => {
|
|
282
|
-
try {
|
|
283
|
-
let inner = S$RescriptSchema.parseOrThrow(filter, ecosystem.onBlockFilterSchema);
|
|
284
|
-
if (inner !== undefined) {
|
|
285
|
-
return S$RescriptSchema.parseOrThrow(Primitive_option.valFromOption(inner), blockRangeSchema);
|
|
286
|
-
} else {
|
|
287
|
-
return defaultBlockRange;
|
|
288
|
-
}
|
|
289
|
-
} catch (raw_exn) {
|
|
290
|
-
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
291
|
-
if (exn.RE_EXN_ID === S$RescriptSchema.Raised) {
|
|
292
|
-
return Stdlib_JsError.throwWithMessage(`\`indexer.` + ecosystem.onBlockMethodName + `("` + name + `")\` \`where\` returned an invalid filter: ` + Utils.prettifyExn(exn._1));
|
|
293
|
-
}
|
|
294
|
-
throw exn;
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
278
|
let onBlockFn = (rawOptions, handler) => {
|
|
298
279
|
HandlerRegister.throwIfFinishedRegistration("onBlock");
|
|
299
|
-
|
|
300
|
-
let ecosystem = config.ecosystem;
|
|
301
|
-
let match = buildChainsObject(config);
|
|
302
|
-
let chains = match[0];
|
|
303
|
-
let name = rawOptions.name;
|
|
304
|
-
let logger = Logging.createChild({
|
|
305
|
-
onBlock: name
|
|
306
|
-
});
|
|
307
|
-
let w = rawOptions.where;
|
|
308
|
-
let where = w === undefined || w === null ? undefined : (
|
|
309
|
-
typeof w === "function" ? rawOptions.where : Stdlib_JsError.throwWithMessage(`\`indexer.` + ecosystem.onBlockMethodName + `("` + name + `")\` expected \`where\` to be a function or omitted, but got ` + typeof w + `.`)
|
|
310
|
-
);
|
|
311
|
-
let matchedAny = {
|
|
312
|
-
contents: false
|
|
313
|
-
};
|
|
314
|
-
ChainMap.values(config.chainMap).forEach(chainConfig => {
|
|
315
|
-
let chainId = chainConfig.id;
|
|
316
|
-
let chainObj = chains[chainId.toString()];
|
|
317
|
-
let result = where !== undefined ? where({
|
|
318
|
-
chain: chainObj
|
|
319
|
-
}) : true;
|
|
320
|
-
let match = result === true ? [
|
|
321
|
-
true,
|
|
322
|
-
defaultBlockRange
|
|
323
|
-
] : (
|
|
324
|
-
result === false ? [
|
|
325
|
-
false,
|
|
326
|
-
defaultBlockRange
|
|
327
|
-
] : (
|
|
328
|
-
typeof result === "object" && !Array.isArray(result) && result !== null ? [
|
|
329
|
-
true,
|
|
330
|
-
extractRange(result, name, ecosystem)
|
|
331
|
-
] : Stdlib_JsError.throwWithMessage(`\`indexer.` + ecosystem.onBlockMethodName + `("` + name + `")\` \`where\` predicate returned an invalid value of type ` + typeof result + `. Expected boolean or a filter object.`)
|
|
332
|
-
)
|
|
333
|
-
);
|
|
334
|
-
if (!match[0]) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
let range = match[1];
|
|
338
|
-
matchedAny.contents = true;
|
|
339
|
-
HandlerRegister.registerOnBlock(name, chainId, range._every, range._gte, range._lte, handler);
|
|
340
|
-
});
|
|
341
|
-
if (!matchedAny.contents) {
|
|
342
|
-
return Logging.childWarn(logger, `\`indexer.` + ecosystem.onBlockMethodName + `\` matched 0 chains. Check the \`where\` predicate.`);
|
|
343
|
-
}
|
|
280
|
+
HandlerRegister.registerOnBlock(rawOptions.name, rawOptions.where, handler, config => buildChainsObject(config)[0]);
|
|
344
281
|
};
|
|
345
282
|
let keysMemo = {
|
|
346
283
|
contents: undefined
|
|
@@ -350,7 +287,7 @@ function getGlobalIndexer() {
|
|
|
350
287
|
if (k !== undefined) {
|
|
351
288
|
return k;
|
|
352
289
|
}
|
|
353
|
-
let match = Config.
|
|
290
|
+
let match = Config.load().ecosystem.name;
|
|
354
291
|
let keys;
|
|
355
292
|
let exit = 0;
|
|
356
293
|
switch (match) {
|
|
@@ -388,15 +325,15 @@ function getGlobalIndexer() {
|
|
|
388
325
|
let get = prop => {
|
|
389
326
|
switch (prop) {
|
|
390
327
|
case "chainIds" :
|
|
391
|
-
return buildChainsObject(Config.
|
|
328
|
+
return buildChainsObject(Config.load())[1];
|
|
392
329
|
case "chains" :
|
|
393
|
-
return buildChainsObject(Config.
|
|
330
|
+
return buildChainsObject(Config.load())[0];
|
|
394
331
|
case "contractRegister" :
|
|
395
332
|
return contractRegisterFn;
|
|
396
333
|
case "description" :
|
|
397
|
-
return Config.
|
|
334
|
+
return Config.load().description;
|
|
398
335
|
case "name" :
|
|
399
|
-
return Config.
|
|
336
|
+
return Config.load().name;
|
|
400
337
|
case "onEvent" :
|
|
401
338
|
return onEventFn;
|
|
402
339
|
case "onInstruction" :
|
|
@@ -478,7 +415,7 @@ function startServer(getState, persistence, isDevelopmentMode) {
|
|
|
478
415
|
});
|
|
479
416
|
app.get("/metrics", (_req, res) => {
|
|
480
417
|
res.set("Content-Type", PromClient.register.contentType);
|
|
481
|
-
Metrics.collect(
|
|
418
|
+
Metrics.collect(EnvioGlobal.value.indexerState).then(metrics => res.end(metrics));
|
|
482
419
|
});
|
|
483
420
|
app.get("/metrics/runtime", (_req, res) => {
|
|
484
421
|
res.set("Content-Type", runtimeRegistry.contentType);
|
|
@@ -501,14 +438,14 @@ function getEnvioInfo() {
|
|
|
501
438
|
}
|
|
502
439
|
|
|
503
440
|
async function migrate(reset) {
|
|
504
|
-
let config = Config.
|
|
441
|
+
let config = Config.load();
|
|
505
442
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
506
443
|
await Persistence.init(persistence, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset);
|
|
507
444
|
return await persistence.storage.close();
|
|
508
445
|
}
|
|
509
446
|
|
|
510
447
|
async function dropSchema() {
|
|
511
|
-
let config = Config.
|
|
448
|
+
let config = Config.load();
|
|
512
449
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
513
450
|
await persistence.storage.reset();
|
|
514
451
|
return await persistence.storage.close();
|
|
@@ -526,14 +463,12 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
526
463
|
let shouldUseTui = isTest ? false : (
|
|
527
464
|
explicitTui !== undefined ? explicitTui : !Envio.isNonInteractive()
|
|
528
465
|
);
|
|
529
|
-
let
|
|
530
|
-
let isDevelopmentMode = !isTest &&
|
|
531
|
-
let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(
|
|
532
|
-
|
|
533
|
-
await Persistence.init(persistence$1, ChainMap.values(
|
|
534
|
-
let
|
|
535
|
-
let registrations = match[1];
|
|
536
|
-
let config = match[0];
|
|
466
|
+
let config = Config.load();
|
|
467
|
+
let isDevelopmentMode = !isTest && config.isDev;
|
|
468
|
+
let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
|
|
469
|
+
EnvioGlobal.value.persistence = Primitive_option.some(persistence$1);
|
|
470
|
+
await Persistence.init(persistence$1, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset);
|
|
471
|
+
let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
|
|
537
472
|
let config$1 = isTest ? ({
|
|
538
473
|
name: config.name,
|
|
539
474
|
description: config.description,
|
|
@@ -555,7 +490,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
555
490
|
allEntities: config.allEntities,
|
|
556
491
|
allEnums: config.allEnums
|
|
557
492
|
}) : config;
|
|
558
|
-
let config$2 = patchConfig !== undefined ? patchConfig(config$1,
|
|
493
|
+
let config$2 = patchConfig !== undefined ? patchConfig(config$1, registrationsByChainId) : config$1;
|
|
559
494
|
let onErrorReject = {
|
|
560
495
|
contents: undefined
|
|
561
496
|
};
|
|
@@ -575,7 +510,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
575
510
|
Prometheus.RollbackEnabled.set(config$2.shouldRollbackOnReorg);
|
|
576
511
|
if (!isTest) {
|
|
577
512
|
startServer(() => {
|
|
578
|
-
let state =
|
|
513
|
+
let state = EnvioGlobal.value.indexerState;
|
|
579
514
|
if (state === undefined) {
|
|
580
515
|
return {
|
|
581
516
|
status: "initializing"
|
|
@@ -593,11 +528,11 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
593
528
|
};
|
|
594
529
|
}, persistence$1, isDevelopmentMode);
|
|
595
530
|
}
|
|
596
|
-
let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1),
|
|
531
|
+
let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, onError);
|
|
597
532
|
if (shouldUseTui) {
|
|
598
533
|
Tui.start(() => state);
|
|
599
534
|
}
|
|
600
|
-
|
|
535
|
+
EnvioGlobal.value.indexerState = Primitive_option.some(state);
|
|
601
536
|
IndexerLoop.start(state);
|
|
602
537
|
return await runUntilFatalError;
|
|
603
538
|
}
|
|
@@ -605,10 +540,10 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
605
540
|
export {
|
|
606
541
|
chainDataSchema,
|
|
607
542
|
stateSchema,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
543
|
+
getIndexerState,
|
|
544
|
+
setIndexerState,
|
|
545
|
+
getGlobalPersistence,
|
|
546
|
+
setGlobalPersistence,
|
|
612
547
|
getInitialChainState,
|
|
613
548
|
buildChainsObject,
|
|
614
549
|
getGlobalIndexer,
|